diff --git a/CHANGELOG.md b/CHANGELOG.md
index d6a7c3e2..6798de9e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,23 +1,24 @@
-# 1.0.0 (2019-11-21)
+# 1.0.0 (2019-11-23)
### 🌟 新功能
* **课题2:** 完成基本构建,js、css打包进html,实现dev跟build环境 ([71a6a19](https://github.com/luoxue-victor/learn_webpack/commit/71a6a19))
* **新增loader配置:** 增加ts、css、less、sass、postcss、babel配置 ([53616f8](https://github.com/luoxue-victor/learn_webpack/commit/53616f8))
+* **性能优化:** 性能优化1 ([0909b33](https://github.com/luoxue-victor/learn_webpack/commit/0909b33))
* **修改了配置文件:** i feat a config ([382c60c](https://github.com/luoxue-victor/learn_webpack/commit/382c60c))
* **all:** 初始化一个项目 ([336cc13](https://github.com/luoxue-victor/learn_webpack/commit/336cc13))
* **asdasdasdas:** 你爱哈的 ([ea12f9c](https://github.com/luoxue-victor/learn_webpack/commit/ea12f9c))
* **autoprefixer:** css 自动添加前缀 ([4e88451](https://github.com/luoxue-victor/learn_webpack/commit/4e88451))
* **config:** add config option ([83c9f1e](https://github.com/luoxue-victor/learn_webpack/commit/83c9f1e))
-* **daasd:** asdasd ([0b6ee85](https://github.com/luoxue-victor/learn_webpack/commit/0b6ee85))
-* **feat:** asdada ([64090c7](https://github.com/luoxue-victor/learn_webpack/commit/64090c7))
+* **feat:** aaaaa ([73abf42](https://github.com/luoxue-victor/learn_webpack/commit/73abf42))
* **init:** 项目初始化 ([d7835fb](https://github.com/luoxue-victor/learn_webpack/commit/d7835fb))
* **sourcemap:** 开启了sourcemap ([5022873](https://github.com/luoxue-victor/learn_webpack/commit/5022873))
* **webpack:** webpack配置 ([9a1d29c](https://github.com/luoxue-victor/learn_webpack/commit/9a1d29c))
* 你好 ([6e534da](https://github.com/luoxue-victor/learn_webpack/commit/6e534da))
* asasdasasd ([8f9515f](https://github.com/luoxue-victor/learn_webpack/commit/8f9515f))
* asd ([d278787](https://github.com/luoxue-victor/learn_webpack/commit/d278787))
-* **feat:** aaaaa ([73abf42](https://github.com/luoxue-victor/learn_webpack/commit/73abf42))
+* **daasd:** asdasd ([0b6ee85](https://github.com/luoxue-victor/learn_webpack/commit/0b6ee85))
+* **feat:** asdada ([64090c7](https://github.com/luoxue-victor/learn_webpack/commit/64090c7))
### 🐛 Bug 修复
diff --git a/README.md b/README.md
index 569fb58a..bee2d452 100644
--- a/README.md
+++ b/README.md
@@ -4,1158 +4,7 @@
本文从下面几个课题来实现
-- 课题 1: css 如何打包进 js?探究 webpack 打包原理。
-- 课题 2:搭建开发环境跟生产环境
-- 课题 3:基础配置(loder,ts、babel、css、less、sass、postcss)等
-- 课时 4:webpack 性能优化1
-
-## 课题 1: js 是如何引用 css 的?
-
-> 打包 src 下的 index.js index.css 到 dist/bundle.js
-
-css 并不能被 webpack 识别,但是可以通过 loader 来将 css 转换成 js
-
-可以分为以下几步实现
-
-1. dev 打包出未压缩文件
-2. build 打包出压缩文件
-3. 将 css 打包进 js 文件
-4. 输出 bundle.js
-5. 使用 webpack-chain 重写配置
-
-### webpack 基础配置
-
-#### 需要的依赖包
-
-package.json
-
-```json
-{
- "scripts": {
- "dev": "cross-env NODE_ENV=development webpack", // 开发环境
- "build": "cross-env NODE_ENV=production webpack" // 生产环境
- },
- "dependencies": {
- "cross-env": "^6.0.3", // 兼容各种环境
- "css-loader": "^3.2.0",
- "rimraf": "^3.0.0", // 删除文件
- "webpack": "^4.41.2"
- },
- "devDependencies": {
- "webpack-cli": "^3.3.10"
- }
-}
-```
-
-#### webpack 基础配置
-
-webpack.config.js
-
-```js
-const path = require('path');
-const rimraf = require('rimraf');
-
-// 删除 dist 目录
-rimraf.sync('dist');
-
-// webpack 配置
-module.exports = {
- entry: './src/index',
- mode: process.env.NODE_ENV,
- output: {
- filename: 'bundle.js',
- path: path.resolve(__dirname, 'dist')
- }
-};
-```
-
-#### css 引入到 js
-
-src/index.js
-
-```js
-const css = require('css-loader!./index.css');
-const a = 100;
-console.log(a, css);
-```
-
-#### 测试 css
-
-src/index.css
-
-```css
-body {
- width: 100%;
- height: 100vh;
- background-color: orange;
-}
-```
-
-### 解析 bundle 如何加载模块
-
-我删掉了一些注释跟一些干扰内容,这样看起来会更清晰一点
-
-- `bundle` 是一个立即执行函数,可以认为它是把所有模块捆绑在一起的一个巨型模块。
-- `webpack` 将所有模块打包成了 `bundle` 的依赖,通过一个对象注入
-- `0 模块` 就是入口
-- `webpack` 通过 `__webpack_require__` 引入模块
-- `__webpack_require__` 就是我们使用的 `require`,被 `webpack` 封装了一层
-
-dist/bundle.js
-
-```js
-(function(modules) {
- function __webpack_require__(moduleId) {
- if (installedModules[moduleId]) {
- return installedModules[moduleId].exports;
- }
- var module = (installedModules[moduleId] = {
- i: moduleId,
- l: false,
- exports: {}
- });
-
- modules[moduleId].call(
- module.exports,
- module,
- module.exports,
- __webpack_require__
- );
-
- module.l = true;
-
- return module.exports;
- }
- return __webpack_require__((__webpack_require__.s = 0));
-})({
- './src/index.js': function(module, exports, __webpack_require__) {
- eval(`
- const css = __webpack_require__("./src/style/index.css")
- const a = 100;
- console.log(a, css)
- `);
- },
-
- './src/style/index.css': function(module, exports, __webpack_require__) {
- eval(`
- exports = module.exports = __webpack_require__("./node_modules/css-loader/dist/runtime/api.js")(false);
- exports.push([module.i, "body {
- width: 100%;
- height: 100vh;
- background-color: orange;
- }", ""]);
- `);
- },
-
- 0: function(module, exports, __webpack_require__) {
- module.exports = __webpack_require__('./src/index.js');
- }
-});
-```
-
-### 动态 import 加载原理
-
-如果我们把 index.js 的 require 改成 import 会发生什么?
-
-我们知道 `import` 跟 `require` 的区别是,`import` 是动态加载只有在用到的时候才会去加载,而 `require` 只要声明了就会加载,`webpack` 遇到了 `require` 就会把它当成一个模块加载到 `bundle` 的依赖里
-
-那么问题来了,如果我们使用了 import 去引用一个模块,它是如何加载的呢?
-
-#### require 改成 import()
-
-src/index.js
-
-```js
-// const css = require('css-loader!./index.css');
-const css = import('css-loader!./index.css');
-const a = 100;
-console.log(a, css);
-```
-
-#### 动态加载打包结果
-
-除了正常的 `bundle` 之外,我们还可以看见一个 `0.boundle.js`
-
-`0.boundle.js` 就是我们的动态加载的 `index.css` 模块
-
-```js
-|-- bundle.js
-|-- 0.boundle.js
-```
-
-#### 动态模块
-
-0.boundle.js
-
-这个文件就是把我们 `import` 的模块放进了一个单独的 `js` 文件中
-
-```js
-(window['webpackJsonp'] = window['webpackJsonp'] || []).push([
- [0],
- {
- './node_modules/css-loader/dist/runtime/api.js': function(
- module,
- exports,
- __webpack_require__
- ) {
- 'use strict';
- eval(`
- ...
- `);
- },
-
- './src/style/index.css': function(module, exports, __webpack_require__) {
- eval(`
- exports = module.exports = __webpack_require__("./node_modules/css-loader/dist/runtime/api.js")(false));
- exports.push([module.i, \`body {
- width: 100%;
- height: 100vh;
- background-color: orange;
- },"\`]
- `);
- }
- }
-]);
-```
-
-#### 动态模块加载逻辑
-
-我们再看下 dist/bundle.js
-
-方便理解,我把大部分代码和注释都删掉了
-
-原理很简单,就是利用的 jsonp 的实现原理加载模块,只是在这里并不是从 server 拿数据而是从其他模块中
-
-1. 调用模块时会在 `window` 上注册一个 `webpackJsonp` 数组,window['webpackJsonp'] = window['webpackJsonp'] || []
-2. 当我们 `import`时,`webpack` 会调用 `__webpack_require__.e(0)` 方法,也就是 `requireEnsure`
-3. `webpack` 会动态创建一个 `script` 标签去加载这个模块,加载成功后会将该模块注入到 `webpackJsonp` 中
-4. `webpackJsonp.push` 会调用 `webpackJsonpCallback` 拿到模块
-5. 模块加载完(then)再使用 `__webpack_require__` 获取模块
-
-```js
-(function(modules) {
- function webpackJsonpCallback(data) {
- var chunkIds = data[0];
- var moreModules = data[1];
- var moduleId,
- chunkId,
- i = 0,
- resolves = [];
- for (; i < chunkIds.length; i++) {
- chunkId = chunkIds[i];
- if (
- Object.prototype.hasOwnProperty.call(installedChunks, chunkId) &&
- installedChunks[chunkId]
- ) {
- resolves.push(installedChunks[chunkId][0]);
- }
- // 模块安装完
- installedChunks[chunkId] = 0;
- }
- for (moduleId in moreModules) {
- if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
- modules[moduleId] = moreModules[moduleId];
- }
- }
- if (parentJsonpFunction) parentJsonpFunction(data);
- while (resolves.length) {
- // 执行所有 promise 的 resolve 函数
- resolves.shift()();
- }
- }
-
- function jsonpScriptSrc(chunkId) {
- return __webpack_require__.p + '' + ({}[chunkId] || chunkId) + '.bundle.js';
- }
-
- function __webpack_require__(moduleId) {
- // ...
- }
-
- __webpack_require__.e = function requireEnsure(chunkId) {
- var promises = [];
- // ...
- var script = document.createElement('script');
- var onScriptComplete;
- script.charset = 'utf-8';
- script.timeout = 120;
- script.src = jsonpScriptSrc(chunkId);
-
- onScriptComplete = function(event) {
- // 处理异常,消除副作用
- // ...
- };
- var timeout = setTimeout(function() {
- onScriptComplete({ type: 'timeout', target: script });
- }, 120000);
- script.onerror = script.onload = onScriptComplete;
- document.head.appendChild(script);
- // ...
- // 动态加载模块
- return Promise.all(promises);
- };
-
- var jsonpArray = (window['webpackJsonp'] = window['webpackJsonp'] || []);
- // 重写数组 push 方法
- jsonpArray.push = webpackJsonpCallback;
- jsonpArray = jsonpArray.slice();
- for (var i = 0; i < jsonpArray.length; i++)
- webpackJsonpCallback(jsonpArray[i]);
-
- return __webpack_require__((__webpack_require__.s = 0));
-})({
- './src/index.js': function(module, exports, __webpack_require__) {
- eval(`
- const css = __webpack_require__.e(0).then(__webpack_require__.t.bind(null, "./src/style/index.css", 7))
- const a = 100;
- console.log(a, css)
- `);
- },
- 0: function(module, exports, __webpack_require__) {
- eval(`module.exports = __webpack_require__("./src/index.js");`);
- }
-});
-```
-
-### 使用 webpack-chain 重写上面配置
-
-我们用 webpack-chain 来写 webpack 的配置,原因是 webpack-chain 的方式更加灵活
-
-官方解释
-
-> `webpack-chain` 尝试通过提供可链式或顺流式的 `API` 创建和修改 `webpack` 配置。`API` 的 `Key` 部分可以由用户指定的名称引用,这有助于跨项目修改配置方式的标准化。
-
-```js
-const path = require('path');
-const rimraf = require('rimraf');
-const Config = require('webpack-chain');
-const config = new Config();
-const resolve = src => {
- return path.join(process.cwd(), src);
-};
-
-// 删除 dist 目录
-rimraf.sync('dist');
-
-config
- // 入口
- .entry('src/index')
- .add(resolve('src/index.js'))
- .end()
- // 模式
- // .mode(process.env.NODE_ENV) 等价下面
- .set('mode', process.env.NODE_ENV)
- // 出口
- .output.path(resolve('dist'))
- .filename('[name].bundle.js');
-
-config.module
- .rule('css')
- .test(/\.css$/)
- .use('css')
- .loader('css-loader');
-
-module.exports = config.toConfig();
-```
-
-### 课时 1 小结
-
-至此课时 1 已经结束了,我们主要做了以下事情
-
-1. webpack 基础配置
-2. 将 css 通过 css-loader 打包进 js 中
-3. 解析 bundle 如何加载模块的
-4. webpack 如何实现的动态加载模块
-
-学习一个工具我们不仅要看懂它的配置,还要对它的原理一起了解,只有学到框架的精髓,我们才能应对如今大前端如此迅猛的发展。
-
----
-
-## 课题 2:搭建可插拔的开发环境跟生产环境
-
-本章提要:
-
-- 需要哪些包?
-- 构建开发环境(devServer)
-- 构建生产环境
-- 自动生成 html
-- 提取 css
-
-### 需要哪些包?
-
-package.json
-
-```json
-{
- "scripts": {
- "dev": "cross-env NODE_ENV=development node build/dev.js",
- "build": "cross-env NODE_ENV=production node build/build.js"
- },
- "dependencies": {
- "@commitlint/config-conventional": "^8.2.0",
- "cross-env": "^6.0.3",
- "css-loader": "^3.2.0",
- "cssnano": "^4.1.10",
- "ora": "^4.0.3",
- "rimraf": "^3.0.0",
- "webpack": "^4.41.2"
- },
- "devDependencies": {
- "extract-text-webpack-plugin": "^3.0.2",
- "html-webpack-plugin": "^3.2.0",
- "mini-css-extract-plugin": "^0.8.0",
- "vue-cli-plugin-commitlint": "^1.0.4",
- "webpack-chain": "^6.0.0",
- "webpack-cli": "^3.3.10",
- "webpack-dev-server": "^3.9.0"
- }
-}
-```
-
-### 目录
-
-```js
-|-- build
- |-- base.js // 公共部分
- |-- build.js
- |-- dev.js
-|-- config
- |-- base.js // 基础配置
- |-- css.js // css 配置
- |-- HtmlWebpackPlugin.js // html 配置
- |-- MiniCssExtractPlugin.js // 提取css
-|-- public // 公共资源
- |-- index.html // html 模版
-|-- src // 开发目录
- |-- style
- |---- index.css
- |-- main.js // 主入口
-```
-
-### 实现可插拔配置
-
-build/base.js
-
-```js
-const { findSync } = require('../lib');
-const Config = require('webpack-chain');
-const config = new Config();
-const files = findSync('config');
-const path = require('path');
-const resolve = p => {
- return path.join(process.cwd(), p);
-};
-
-module.exports = () => {
- const map = new Map();
-
- files.map(_ => {
- const name = _.split('/')
- .pop()
- .replace('.js', '');
- return map.set(name, require(_)(config, resolve));
- });
-
- map.forEach((v, key) => {
- v();
- });
-
- return config;
-};
-```
-
-### 生产环境搭建
-
-build/build.js
-
-```js
-const rimraf = require('rimraf');
-const ora = require('ora');
-const chalk = require('chalk');
-const path = require('path');
-// 删除 dist 目录
-rimraf.sync(path.join(process.cwd(), 'dist'));
-
-const config = require('./base')();
-const webpack = require('webpack');
-const spinner = ora('开始构建项目...');
-spinner.start();
-
-webpack(config.toConfig(), function(err, stats) {
- spinner.stop();
- if (err) throw err;
- process.stdout.write(
- stats.toString({
- colors: true,
- modules: false,
- children: false,
- chunks: false,
- chunkModules: false
- }) + '\n\n'
- );
-
- if (stats.hasErrors()) {
- console.log(chalk.red('构建失败\n'));
- process.exit(1);
- }
-
- console.log(chalk.cyan('build完成\n'));
-});
-```
-
-### 开发环境搭建
-
-build/dev.js
-
-```js
-const config = require('./base')();
-const webpack = require('webpack');
-const chalk = require('chalk');
-const WebpackDevServer = require('webpack-dev-server');
-const port = 8080;
-const publicPath = '/common/';
-
-config.devServer
- .quiet(true)
- .hot(true)
- .https(false)
- .disableHostCheck(true)
- .publicPath(publicPath)
- .clientLogLevel('none');
-
-const compiler = webpack(config.toConfig());
-// 拿到 devServer 参数
-const chainDevServer = compiler.options.devServer;
-const server = new WebpackDevServer(
- compiler,
- Object.assign(chainDevServer, {})
-);
-
-['SIGINT', 'SIGTERM'].forEach(signal => {
- process.on(signal, () => {
- server.close(() => {
- process.exit(0);
- });
- });
-});
-// 监听端口
-server.listen(port);
-
-new Promise(() => {
- compiler.hooks.done.tap('dev', stats => {
- const empty = ' ';
- const common = `App running at:
- - Local: http://127.0.0.1:${port}${publicPath}\n`;
- console.log(chalk.cyan('\n' + empty + common));
- });
-});
-```
-
-### 提取 css
-
-config/css.js
-
-```js
-module.exports = (config, resolve) => {
- return (lang, test) => {
- const baseRule = config.module.rule(lang).test(test);
- const normalRule = baseRule.oneOf('normal');
- applyLoaders(normalRule);
- function applyLoaders(rule) {
- rule
- .use('extract-css-loader')
- .loader(require('mini-css-extract-plugin').loader)
- .options({
- publicPath: './'
- });
- rule
- .use('css-loader')
- .loader('css-loader')
- .options({});
- }
- };
-};
-```
-
-### 自动生成 html
-
-config/HtmlWebpackPlugin.js
-
-```js
-const HtmlWebpackPlugin = require('html-webpack-plugin');
-
-module.exports = (config, resolve) => {
- return () => {
- config.plugin('html').use(HtmlWebpackPlugin, [
- {
- template: 'public/index.html'
- }
- ]);
- };
-};
-```
-
-### css 提取插件
-
-config/MiniCssExtractPlugin.js
-
-```js
-const MiniCssExtractPlugin = require('mini-css-extract-plugin');
-
-module.exports = (config, resolve) => {
- return () => {
- config
- .oneOf('normal')
- .plugin('mini-css-extract')
- .use(MiniCssExtractPlugin);
- };
-};
-```
-
-### 测试文件
-
-#### 测试 html 模板
-
-public/index.html
-
-```html
-
-
-
-
- learn_webpack
-
-
-```
-
-#### 测试 css 模板
-
-src/style/index.css
-
-```css
-.test {
- width: 200px;
- height: 200px;
- color: red;
- background-color: orange;
-}
-```
-
-#### 程序入口
-
-src/main.js
-
-```js
-require('./style/index.css');
-
-const h2 = document.createElement('h2');
-h2.className = 'test';
-h2.innerText = 'test';
-document.body.append(h2);
-```
-
--------------------------------------------------------
-
-## 课题 3:基础配置(loder,ts、babel、css、less、sass、postcss)等
-
-本章提要:
-
-- 配置 babel
-- 使用 babel 配置 ts
-- ts 静态类型检查
-- 友好错误提示插件
-- 配置样式,style,css、less、sass、postcss 等
-- 编译前后 css 对比
-- postcss 配置
-- 配置 autoprefixer
-- 开启 source map
-
-### 目录
-
-增加以下文件
-
-```js
-// 配置目录
-|-- config
- |-- babelLoader.js // babel-loader 配置
- |-- ForkTsChecker.js // ts 静态检查
- |-- FriendlyErrorsWebpackPlugin.js // 友好错误提示
- |-- style
-// 开发目录
-|-- src
- |-- style
- |-- app.css
- |-- index.less // 测试 less
- |-- index.scss // 测试 sass
- |-- index.postcss // 测试 postcss
- |-- ts
- |-- index.ts // 测试 ts
-|-- babel.js
-|-- postcss.config.js // postcss 配置
-|-- tsconfig.json // ts 配置
-// 打包后的目录
-|--dist
- |-- app.bundle.js
- |-- app.css
- |-- index.html
-```
-
-### 配置 babel
-
-config/babelLoader.js
-
-```js
-module.exports = (config, resolve) => {
- const baseRule = config.module.rule('js').test(/.js|.tsx?$/);
- const babelPath = resolve('babel.js');
- const babelConf = require(babelPath);
- const version = require(resolve('node_modules/@babel/core/package.json'))
- .version;
- return () => {
- baseRule
- .use('babel')
- .loader(require.resolve('babel-loader'))
- .options(babelConf({ version }));
- };
-};
-```
-
-### 使用 babel 配置 ts
-
-这里我们使用 `babel` 插件 `@babel/preset-typescript` 将 `ts` 转成 `js,并使用` `ForkTsCheckerWebpackPlugin`、`ForkTsCheckerNotifierWebpackPlugin` 插件进行错误提示。
-
-babel.js
-
-```js
-module.exports = function(api) {
- return {
- presets: [
- [
- '@babel/preset-env',
- {
- targets: {
- chrome: 59,
- edge: 13,
- firefox: 50,
- safari: 8
- }
- }
- ],
- [
- '@babel/preset-typescript',
- {
- allExtensions: true
- }
- ]
- ],
- plugins: [
- '@babel/plugin-transform-typescript',
- 'transform-class-properties',
- '@babel/proposal-object-rest-spread'
- ]
- };
-};
-```
-
-### ts 静态类型检查
-
-```js
-const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
-const ForkTsCheckerNotifierWebpackPlugin = require('fork-ts-checker-notifier-webpack-plugin');
-
-module.exports = (config, resolve) => {
- return () => {
- config.plugin('ts-fork').use(ForkTsCheckerWebpackPlugin, [
- {
- // 将async设为false,可以阻止Webpack的emit以等待类型检查器/linter,并向Webpack的编译添加错误。
- async: false
- }
- ]);
- // 将TypeScript类型检查错误以弹框提示
- // 如果fork-ts-checker-webpack-plugin的async为false时可以不用
- // 否则建议使用,以方便发现错误
- config.plugin('ts-notifier').use(ForkTsCheckerNotifierWebpackPlugin, [
- {
- title: 'TypeScript',
- excludeWarnings: true,
- skipSuccessful: true
- }
- ]);
- };
-};
-```
-
-### 友好错误提示
-
-config/FriendlyErrorsWebpackPlugin.js
-
-```js
-const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
-
-module.exports = (config, resolve) => {
- return () => {
- config.plugin('error').use(FriendlyErrorsWebpackPlugin);
- };
-};
-```
-
-### 配置 style,css、less、sass、postcss 等
-
-```js
-module.exports = (config, resolve) => {
- const createCSSRule = (lang, test, loader, options = {}) => {
- const baseRule = config.module.rule(lang).test(test);
- const normalRule = baseRule.oneOf('normal');
- normalRule
- .use('extract-css-loader')
- .loader(require('mini-css-extract-plugin').loader)
- .options({
- hmr: process.env.NODE_ENV === 'development',
- publicPath: '/'
- });
- normalRule
- .use('css-loader')
- .loader(require.resolve('css-loader'))
- .options({});
- normalRule.use('postcss-loader').loader(require.resolve('postcss-loader'));
- if (loader) {
- const rs = require.resolve(loader);
- normalRule
- .use(loader)
- .loader(rs)
- .options(options);
- }
- };
-
- return () => {
- createCSSRule('css', /\.css$/, 'css-loader', {});
- createCSSRule('less', /\.less$/, 'less-loader', {});
- createCSSRule('scss', /\.scss$/, 'sass-loader', {});
- createCSSRule('postcss', /\.p(ost)?css$/);
- };
-};
-```
-
-### postcss 配置
-
-```js
-module.exports = {
- plugins: {
- 'postcss-px-to-viewport': {
- unitToConvert: 'px',
- viewportWidth: 750,
- unitPrecision: 5,
- propList: ['*'],
- viewportUnit: 'vw',
- fontViewportUnit: 'vw',
- selectorBlackList: [],
- minPixelValue: 1,
- mediaQuery: false,
- replace: true,
- exclude: [],
- landscape: false,
- landscapeUnit: 'vw',
- landscapeWidth: 568
- }
- }
-};
-```
-
-### 编译后 css 对比
-
-src/style/index.less
-
-```less
-/* index.less */
-.test {
- width: 300px;
-}
-```
-
-dist/app.css
-
-```css
-/* index.css */
-.test {
- width: 26.66667vw;
- height: 26.66667vw;
- color: red;
- background-color: orange;
-}
-/* app.css */
-.test {
- font-size: 8vw;
-}
-/* index.less */
-.test {
- width: 40vw;
-}
-
-/* index.scss */
-.test {
- height: 40vw;
-}
-/* index.postcss */
-.test {
- background: green;
- height: 26.66667vw;
-}
-```
-
-### 配置 autoprefixer
-
-自动添加 css 前缀
-
-postcss.config.js
-
-```js
-module.exports = {
- plugins: {
- autoprefixer: {
- overrideBrowserslist: [
- '> 1%',
- 'last 3 versions',
- 'iOS >= 8',
- 'Android >= 4',
- 'Chrome >= 40'
- ]
- }
- }
-};
-```
-
-#### 转换前
-
-```css
-/* index.css */
-.test {
- width: 200px;
- height: 200px;
- color: red;
- display: flex;
- background-color: orange;
-}
-```
-
-#### 转换后
-
-```css
-/* index.css */
-.test {
- width: 26.66667vw;
- height: 26.66667vw;
- color: red;
- display: -webkit-box;
- display: -webkit-flex;
- display: -ms-flexbox;
- display: flex;
- background-color: orange;
-}
-```
-
-### 开启 source map
-
-```js
-config.devtool('cheap-source-map');
-```
-
-```js
-|--dist
- |-- app.bundle.js
- |-- app.bundle.js.map
- |-- app.css
- |-- app.css.map
- |-- index.html
-```
-
-在源文件下会有一行注释,证明开启了 sourcemap
-
-```js
-/*# sourceMappingURL=app.css.map*/
-```
-
----
-
-## 课时 4:webpack 性能优化1
-
-本章讲解
-
-1. 分离 Manifest
-2. Code Splitting(代码分割)
-3. Bundle Splitting(打包分割)
-4. Tree Shaking(删除死代码)
-5. 开启 gzip
-
-### 分离 Manifest
-
-```js
-module.exports = (config, resolve) => {
- return () => {
- config
- .optimization
- .runtimeChunk({
- name: "manifest"
- })
- }
-}
-```
-
-### Code Splitting
-
-1. 使用动态 import 或者 require.ensure 语法,在第一节已经讲解
-2. 使用 `babel-plugin-import` 插件按需引入一些组件库
-
-### Bundle Splitting
-
-将公共的包提取到 `chunk-vendors` 里面,比如你require('vue'),webpack 会将 vue 打包进 chunk-vendors.bundle.js
-
-```js
-module.exports = (config, resolve) => {
- return () => {
- config
- .optimization.splitChunks({
- chunks: 'async',
- minSize: 30000,
- minChunks: 1,
- maxAsyncRequests: 3,
- maxInitialRequests: 3,
- cacheGroups: {
- vendors: {
- name: `chunk-vendors`,
- test: /[\\/]node_modules[\\/]/,
- priority: -10,
- chunks: 'initial'
- },
- common: {
- name: `chunk-common`,
- minChunks: 2,
- priority: -20,
- chunks: 'initial',
- reuseExistingChunk: true
- }
- }
- })
- config.optimization.usedExports(true)
- }
-}
-```
-
-### Tree Shaking
-
-config/optimization.js
-
-```js
-config.optimization.usedExports(true);
-```
-
-src/treeShaking.js
-
-```js
-export function square(x) {
- return x * x;
-}
-
-export function cube(x) {
- return x * x * x;
-}
-```
-
-在 main.js 中只引用了 cube
-
-```js
-import { cube } from './treeShaking';
-
-console.log(cube(2));
-```
-
-#### 未使用 Tree Shaking
-
-```js
-{
- "./src/treeShaking.js": function(
- module,
- __webpack_exports__,
- __webpack_require__
- ) {
- "use strict";
- __webpack_require__.r(__webpack_exports__);
- __webpack_require__.d(__webpack_exports__, "square", function() {
- return square;
- });
- __webpack_require__.d(__webpack_exports__, "cube", function() {
- return cube;
- });
- function square(x) {
- return x * x;
- }
- function cube(x) {
- return x * x * x;
- }
- }
-}
-```
-
-#### 使用了 Tree Shaking
-
-这里只导出了 cube 函数,并没有将 square 导出去
-
-当然你可以看见 square 函数还是在 bundle 里面,但是在压缩的时候就会被干掉了,因为它并没有被引用
-
-```js
-{
- "./src/treeShaking.js": function(
- module,
- __webpack_exports__,
- __webpack_require__
- ) {
- "use strict";
- __webpack_require__.d(__webpack_exports__, "a", function() {
- return cube;
- });
- function square(x) {
- return x * x;
- }
- function cube(x) {
- return x * x * x;
- }
- }
-}
-```
-
-只有当函数给定输入后,产生相应的输出,且不修改任何外部的东西,才可以安全做shaking的操作
-
-如何使用tree-shaking?
-
-1. 确保代码是es6格式,即 export,import
-2. package.json中,设置 sideEffects
-3. 确保 tree-shaking 的函数没有副作用
-4. babelrc中设置presets [["@babel/preset-env", { "modules": false }]] 禁止转换模块,交由webpack进行模块化处理
-5. 结合uglifyjs-webpack-plugin
-
-其实在 `webpack4` 我们根本不需要做这些操作了,因为 `webpack` 在生产环境已经帮我们默认添加好了,开箱即用!
-
-### 开启 gzip
-
-CompressionWebpackPlugin.js
-
-```js
-const CompressionWebpackPlugin = require('compression-webpack-plugin');
-
-module.exports = (config, resolve) => {
- return () => {
- config.plugin('CompressionWebpackPlugin').use(CompressionWebpackPlugin, [
- {
- algorithm: 'gzip',
- test: /\.js(\?.*)?$/i,
- threshold: 10240,
- minRatio: 0.8
- }
- ]);
- };
-};
-```
-
-## 课时 5:webpack 性能优化2
-
-
-
+- 课题 1: [css 如何打包进 js?探究 webpack 打包原理](./docs/课时1.md)。
+- 课题 2:[搭建开发环境跟生产环境](./docs/课时2.md)
+- 课题 3:[基础配置(loder,ts、babel、css、less、sass、postcss)等](./docs/课时3.md)
+- 课时 4:[webpack 性能优化 1](./docs/课时4.md)
diff --git "a/docs/\350\257\276\346\227\2661.md" "b/docs/\350\257\276\346\227\2661.md"
new file mode 100644
index 00000000..9e3674cd
--- /dev/null
+++ "b/docs/\350\257\276\346\227\2661.md"
@@ -0,0 +1,361 @@
+## 课题 1: js 是如何引用 css 的?
+
+> 打包 src 下的 index.js index.css 到 dist/bundle.js
+
+css 并不能被 webpack 识别,但是可以通过 loader 来将 css 转换成 js
+
+可以分为以下几步实现
+
+1. webpack 基础配置
+2. build 模式打包出压缩文件
+3. 将 css 打包进 js 文件
+4. 输出 bundle.js
+5. 使用 webpack-chain 重写配置
+
+### webpack 基础配置
+
+#### 需要的依赖包
+
+package.json
+
+```json
+{
+ "scripts": {
+ "dev": "cross-env NODE_ENV=development webpack", // 开发环境
+ "build": "cross-env NODE_ENV=production webpack" // 生产环境
+ },
+ "dependencies": {
+ "cross-env": "^6.0.3", // 兼容各种环境
+ "css-loader": "^3.2.0",
+ "rimraf": "^3.0.0", // 删除文件
+ "webpack": "^4.41.2"
+ },
+ "devDependencies": {
+ "webpack-cli": "^3.3.10"
+ }
+}
+```
+
+#### webpack 基础配置
+
+webpack.config.js
+
+```js
+const path = require('path');
+const rimraf = require('rimraf');
+
+// 删除 dist 目录
+rimraf.sync('dist');
+
+// webpack 配置
+module.exports = {
+ entry: './src/index',
+ mode: process.env.NODE_ENV,
+ output: {
+ filename: 'bundle.js',
+ path: path.resolve(__dirname, 'dist')
+ }
+};
+```
+
+#### css 引入到 js
+
+src/index.js
+
+```js
+const css = require('css-loader!./index.css');
+const a = 100;
+console.log(a, css);
+```
+
+#### 测试 css
+
+src/index.css
+
+```css
+body {
+ width: 100%;
+ height: 100vh;
+ background-color: orange;
+}
+```
+
+### 解析 bundle 如何加载模块
+
+我删掉了一些注释跟一些干扰内容,这样看起来会更清晰一点
+
+- `bundle` 是一个立即执行函数,可以认为它是把所有模块捆绑在一起的一个巨型模块。
+- `webpack` 将所有模块打包成了 `bundle` 的依赖,通过一个对象注入
+- `0 模块` 就是入口
+- `webpack` 通过 `__webpack_require__` 引入模块
+- `__webpack_require__` 就是我们使用的 `require`,被 `webpack` 封装了一层
+
+dist/bundle.js
+
+```js
+(function(modules) {
+ function __webpack_require__(moduleId) {
+ if (installedModules[moduleId]) {
+ return installedModules[moduleId].exports;
+ }
+ var module = (installedModules[moduleId] = {
+ i: moduleId,
+ l: false,
+ exports: {}
+ });
+
+ modules[moduleId].call(
+ module.exports,
+ module,
+ module.exports,
+ __webpack_require__
+ );
+
+ module.l = true;
+
+ return module.exports;
+ }
+ return __webpack_require__((__webpack_require__.s = 0));
+})({
+ './src/index.js': function(module, exports, __webpack_require__) {
+ eval(`
+ const css = __webpack_require__("./src/style/index.css")
+ const a = 100;
+ console.log(a, css)
+ `);
+ },
+
+ './src/style/index.css': function(module, exports, __webpack_require__) {
+ eval(`
+ exports = module.exports = __webpack_require__("./node_modules/css-loader/dist/runtime/api.js")(false);
+ exports.push([module.i, "body {
+ width: 100%;
+ height: 100vh;
+ background-color: orange;
+ }", ""]);
+ `);
+ },
+
+ 0: function(module, exports, __webpack_require__) {
+ module.exports = __webpack_require__('./src/index.js');
+ }
+});
+```
+
+### 动态 import 加载原理
+
+如果我们把 index.js 的 require 改成 import 会发生什么?
+
+我们知道 `import` 跟 `require` 的区别是,`import` 是动态加载只有在用到的时候才会去加载,而 `require` 只要声明了就会加载,`webpack` 遇到了 `require` 就会把它当成一个模块加载到 `bundle` 的依赖里
+
+那么问题来了,如果我们使用了 import 去引用一个模块,它是如何加载的呢?
+
+#### require 改成 import()
+
+src/index.js
+
+```js
+// const css = require('css-loader!./index.css');
+const css = import('css-loader!./index.css');
+const a = 100;
+console.log(a, css);
+```
+
+#### 动态加载打包结果
+
+除了正常的 `bundle` 之外,我们还可以看见一个 `0.boundle.js`
+
+`0.boundle.js` 就是我们的动态加载的 `index.css` 模块
+
+```js
+|-- bundle.js
+|-- 0.boundle.js
+```
+
+#### 动态模块
+
+0.boundle.js
+
+这个文件就是把我们 `import` 的模块放进了一个单独的 `js` 文件中
+
+```js
+(window['webpackJsonp'] = window['webpackJsonp'] || []).push([
+ [0],
+ {
+ './node_modules/css-loader/dist/runtime/api.js': function(
+ module,
+ exports,
+ __webpack_require__
+ ) {
+ 'use strict';
+ eval(`
+ ...
+ `);
+ },
+
+ './src/style/index.css': function(module, exports, __webpack_require__) {
+ eval(`
+ exports = module.exports = __webpack_require__("./node_modules/css-loader/dist/runtime/api.js")(false));
+ exports.push([module.i, \`body {
+ width: 100%;
+ height: 100vh;
+ background-color: orange;
+ },"\`]
+ `);
+ }
+ }
+]);
+```
+
+#### 动态模块加载逻辑
+
+我们再看下 dist/bundle.js
+
+方便理解,我把大部分代码和注释都删掉了
+
+原理很简单,就是利用的 jsonp 的实现原理加载模块,只是在这里并不是从 server 拿数据而是从其他模块中
+
+1. 调用模块时会在 `window` 上注册一个 `webpackJsonp` 数组,window['webpackJsonp'] = window['webpackJsonp'] || []
+2. 当我们 `import`时,`webpack` 会调用 `__webpack_require__.e(0)` 方法,也就是 `requireEnsure`
+3. `webpack` 会动态创建一个 `script` 标签去加载这个模块,加载成功后会将该模块注入到 `webpackJsonp` 中
+4. `webpackJsonp.push` 会调用 `webpackJsonpCallback` 拿到模块
+5. 模块加载完(then)再使用 `__webpack_require__` 获取模块
+
+```js
+(function(modules) {
+ function webpackJsonpCallback(data) {
+ var chunkIds = data[0];
+ var moreModules = data[1];
+ var moduleId,
+ chunkId,
+ i = 0,
+ resolves = [];
+ for (; i < chunkIds.length; i++) {
+ chunkId = chunkIds[i];
+ if (
+ Object.prototype.hasOwnProperty.call(installedChunks, chunkId) &&
+ installedChunks[chunkId]
+ ) {
+ resolves.push(installedChunks[chunkId][0]);
+ }
+ // 模块安装完
+ installedChunks[chunkId] = 0;
+ }
+ for (moduleId in moreModules) {
+ if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
+ modules[moduleId] = moreModules[moduleId];
+ }
+ }
+ if (parentJsonpFunction) parentJsonpFunction(data);
+ while (resolves.length) {
+ // 执行所有 promise 的 resolve 函数
+ resolves.shift()();
+ }
+ }
+
+ function jsonpScriptSrc(chunkId) {
+ return __webpack_require__.p + '' + ({}[chunkId] || chunkId) + '.bundle.js';
+ }
+
+ function __webpack_require__(moduleId) {
+ // ...
+ }
+
+ __webpack_require__.e = function requireEnsure(chunkId) {
+ var promises = [];
+ // ...
+ var script = document.createElement('script');
+ var onScriptComplete;
+ script.charset = 'utf-8';
+ script.timeout = 120;
+ script.src = jsonpScriptSrc(chunkId);
+
+ onScriptComplete = function(event) {
+ // 处理异常,消除副作用
+ // ...
+ };
+ var timeout = setTimeout(function() {
+ onScriptComplete({ type: 'timeout', target: script });
+ }, 120000);
+ script.onerror = script.onload = onScriptComplete;
+ document.head.appendChild(script);
+ // ...
+ // 动态加载模块
+ return Promise.all(promises);
+ };
+
+ var jsonpArray = (window['webpackJsonp'] = window['webpackJsonp'] || []);
+ // 重写数组 push 方法
+ jsonpArray.push = webpackJsonpCallback;
+ jsonpArray = jsonpArray.slice();
+ for (var i = 0; i < jsonpArray.length; i++)
+ webpackJsonpCallback(jsonpArray[i]);
+
+ return __webpack_require__((__webpack_require__.s = 0));
+})({
+ './src/index.js': function(module, exports, __webpack_require__) {
+ eval(`
+ const css = __webpack_require__.e(0).then(__webpack_require__.t.bind(null, "./src/style/index.css", 7))
+ const a = 100;
+ console.log(a, css)
+ `);
+ },
+ 0: function(module, exports, __webpack_require__) {
+ eval(`module.exports = __webpack_require__("./src/index.js");`);
+ }
+});
+```
+
+### 使用 webpack-chain 重写上面配置
+
+我们用 webpack-chain 来写 webpack 的配置,原因是 webpack-chain 的方式更加灵活
+
+官方解释
+
+> `webpack-chain` 尝试通过提供可链式或顺流式的 `API` 创建和修改 `webpack` 配置。`API` 的 `Key` 部分可以由用户指定的名称引用,这有助于跨项目修改配置方式的标准化。
+
+```js
+const path = require('path');
+const rimraf = require('rimraf');
+const Config = require('webpack-chain');
+const config = new Config();
+const resolve = src => {
+ return path.join(process.cwd(), src);
+};
+
+// 删除 dist 目录
+rimraf.sync('dist');
+
+config
+ // 入口
+ .entry('src/index')
+ .add(resolve('src/index.js'))
+ .end()
+ // 模式
+ // .mode(process.env.NODE_ENV) 等价下面
+ .set('mode', process.env.NODE_ENV)
+ // 出口
+ .output.path(resolve('dist'))
+ .filename('[name].bundle.js');
+
+config.module
+ .rule('css')
+ .test(/\.css$/)
+ .use('css')
+ .loader('css-loader');
+
+module.exports = config.toConfig();
+```
+
+### 课时 1 小结
+
+至此课时 1 已经结束了,我们主要做了以下事情
+
+1. webpack 基础配置
+2. 将 css 通过 css-loader 打包进 js 中
+3. 解析 bundle 如何加载模块的
+4. webpack 如何实现的动态加载模块
+
+学习一个工具我们不仅要看懂它的配置,还要对它的原理一起了解,只有学到框架的精髓,我们才能应对如今大前端如此迅猛的发展。
+
+---
diff --git "a/docs/\350\257\276\346\227\2662.md" "b/docs/\350\257\276\346\227\2662.md"
new file mode 100644
index 00000000..d553eb91
--- /dev/null
+++ "b/docs/\350\257\276\346\227\2662.md"
@@ -0,0 +1,284 @@
+## 课题 2:搭建可插拔的开发环境跟生产环境
+
+本章提要:
+
+- 需要哪些包?
+- 构建开发环境(devServer)
+- 构建生产环境
+- 自动生成 html
+- 提取 css
+
+### 需要哪些包?
+
+package.json
+
+```json
+{
+ "scripts": {
+ "dev": "cross-env NODE_ENV=development node build/dev.js",
+ "build": "cross-env NODE_ENV=production node build/build.js"
+ },
+ "dependencies": {
+ "@commitlint/config-conventional": "^8.2.0",
+ "cross-env": "^6.0.3",
+ "css-loader": "^3.2.0",
+ "cssnano": "^4.1.10",
+ "ora": "^4.0.3",
+ "rimraf": "^3.0.0",
+ "webpack": "^4.41.2"
+ },
+ "devDependencies": {
+ "extract-text-webpack-plugin": "^3.0.2",
+ "html-webpack-plugin": "^3.2.0",
+ "mini-css-extract-plugin": "^0.8.0",
+ "vue-cli-plugin-commitlint": "^1.0.4",
+ "webpack-chain": "^6.0.0",
+ "webpack-cli": "^3.3.10",
+ "webpack-dev-server": "^3.9.0"
+ }
+}
+```
+
+### 目录
+
+```js
+|-- build
+ |-- base.js // 公共部分
+ |-- build.js
+ |-- dev.js
+|-- config
+ |-- base.js // 基础配置
+ |-- css.js // css 配置
+ |-- HtmlWebpackPlugin.js // html 配置
+ |-- MiniCssExtractPlugin.js // 提取css
+|-- public // 公共资源
+ |-- index.html // html 模版
+|-- src // 开发目录
+ |-- style
+ |---- index.css
+ |-- main.js // 主入口
+```
+
+### 实现可插拔配置
+
+build/base.js
+
+```js
+const { findSync } = require('../lib');
+const Config = require('webpack-chain');
+const config = new Config();
+const files = findSync('config');
+const path = require('path');
+const resolve = p => {
+ return path.join(process.cwd(), p);
+};
+
+module.exports = () => {
+ const map = new Map();
+
+ files.map(_ => {
+ const name = _.split('/')
+ .pop()
+ .replace('.js', '');
+ return map.set(name, require(_)(config, resolve));
+ });
+
+ map.forEach((v, key) => {
+ v();
+ });
+
+ return config;
+};
+```
+
+### 生产环境搭建
+
+build/build.js
+
+```js
+const rimraf = require('rimraf');
+const ora = require('ora');
+const chalk = require('chalk');
+const path = require('path');
+// 删除 dist 目录
+rimraf.sync(path.join(process.cwd(), 'dist'));
+
+const config = require('./base')();
+const webpack = require('webpack');
+const spinner = ora('开始构建项目...');
+spinner.start();
+
+webpack(config.toConfig(), function(err, stats) {
+ spinner.stop();
+ if (err) throw err;
+ process.stdout.write(
+ stats.toString({
+ colors: true,
+ modules: false,
+ children: false,
+ chunks: false,
+ chunkModules: false
+ }) + '\n\n'
+ );
+
+ if (stats.hasErrors()) {
+ console.log(chalk.red('构建失败\n'));
+ process.exit(1);
+ }
+
+ console.log(chalk.cyan('build完成\n'));
+});
+```
+
+### 开发环境搭建
+
+build/dev.js
+
+```js
+const config = require('./base')();
+const webpack = require('webpack');
+const chalk = require('chalk');
+const WebpackDevServer = require('webpack-dev-server');
+const port = 8080;
+const publicPath = '/common/';
+
+config.devServer
+ .quiet(true)
+ .hot(true)
+ .https(false)
+ .disableHostCheck(true)
+ .publicPath(publicPath)
+ .clientLogLevel('none');
+
+const compiler = webpack(config.toConfig());
+// 拿到 devServer 参数
+const chainDevServer = compiler.options.devServer;
+const server = new WebpackDevServer(
+ compiler,
+ Object.assign(chainDevServer, {})
+);
+
+['SIGINT', 'SIGTERM'].forEach(signal => {
+ process.on(signal, () => {
+ server.close(() => {
+ process.exit(0);
+ });
+ });
+});
+// 监听端口
+server.listen(port);
+
+new Promise(() => {
+ compiler.hooks.done.tap('dev', stats => {
+ const empty = ' ';
+ const common = `App running at:
+ - Local: http://127.0.0.1:${port}${publicPath}\n`;
+ console.log(chalk.cyan('\n' + empty + common));
+ });
+});
+```
+
+### 提取 css
+
+config/css.js
+
+```js
+module.exports = (config, resolve) => {
+ return (lang, test) => {
+ const baseRule = config.module.rule(lang).test(test);
+ const normalRule = baseRule.oneOf('normal');
+ applyLoaders(normalRule);
+ function applyLoaders(rule) {
+ rule
+ .use('extract-css-loader')
+ .loader(require('mini-css-extract-plugin').loader)
+ .options({
+ publicPath: './'
+ });
+ rule
+ .use('css-loader')
+ .loader('css-loader')
+ .options({});
+ }
+ };
+};
+```
+
+### 自动生成 html
+
+config/HtmlWebpackPlugin.js
+
+```js
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+
+module.exports = (config, resolve) => {
+ return () => {
+ config.plugin('html').use(HtmlWebpackPlugin, [
+ {
+ template: 'public/index.html'
+ }
+ ]);
+ };
+};
+```
+
+### css 提取插件
+
+config/MiniCssExtractPlugin.js
+
+```js
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+
+module.exports = (config, resolve) => {
+ return () => {
+ config
+ .oneOf('normal')
+ .plugin('mini-css-extract')
+ .use(MiniCssExtractPlugin);
+ };
+};
+```
+
+### 测试文件
+
+#### 测试 html 模板
+
+public/index.html
+
+```html
+
+
+
+
+ learn_webpack
+
+
+```
+
+#### 测试 css 模板
+
+src/style/index.css
+
+```css
+.test {
+ width: 200px;
+ height: 200px;
+ color: red;
+ background-color: orange;
+}
+```
+
+#### 程序入口
+
+src/main.js
+
+```js
+require('./style/index.css');
+
+const h2 = document.createElement('h2');
+h2.className = 'test';
+h2.innerText = 'test';
+document.body.append(h2);
+```
+
+-------------------------------------------------------
diff --git "a/docs/\350\257\276\346\227\2663.md" "b/docs/\350\257\276\346\227\2663.md"
new file mode 100644
index 00000000..50b7eb35
--- /dev/null
+++ "b/docs/\350\257\276\346\227\2663.md"
@@ -0,0 +1,319 @@
+## 课题 3:基础配置(loder,ts、babel、css、less、sass、postcss)等
+
+本章提要:
+
+- 配置 babel
+- 使用 babel 配置 ts
+- ts 静态类型检查
+- 友好错误提示插件
+- 配置样式,style,css、less、sass、postcss 等
+- 编译前后 css 对比
+- postcss 配置
+- 配置 autoprefixer
+- 开启 source map
+
+### 目录
+
+增加以下文件
+
+```js
+// 配置目录
+|-- config
+ |-- babelLoader.js // babel-loader 配置
+ |-- ForkTsChecker.js // ts 静态检查
+ |-- FriendlyErrorsWebpackPlugin.js // 友好错误提示
+ |-- style
+// 开发目录
+|-- src
+ |-- style
+ |-- app.css
+ |-- index.less // 测试 less
+ |-- index.scss // 测试 sass
+ |-- index.postcss // 测试 postcss
+ |-- ts
+ |-- index.ts // 测试 ts
+|-- babel.js
+|-- postcss.config.js // postcss 配置
+|-- tsconfig.json // ts 配置
+// 打包后的目录
+|--dist
+ |-- app.bundle.js
+ |-- app.css
+ |-- index.html
+```
+
+### 配置 babel
+
+config/babelLoader.js
+
+```js
+module.exports = (config, resolve) => {
+ const baseRule = config.module.rule('js').test(/.js|.tsx?$/);
+ const babelPath = resolve('babel.js');
+ const babelConf = require(babelPath);
+ const version = require(resolve('node_modules/@babel/core/package.json'))
+ .version;
+ return () => {
+ baseRule
+ .use('babel')
+ .loader(require.resolve('babel-loader'))
+ .options(babelConf({ version }));
+ };
+};
+```
+
+### 使用 babel 配置 ts
+
+这里我们使用 `babel` 插件 `@babel/preset-typescript` 将 `ts` 转成 `js,并使用` `ForkTsCheckerWebpackPlugin`、`ForkTsCheckerNotifierWebpackPlugin` 插件进行错误提示。
+
+babel.js
+
+```js
+module.exports = function(api) {
+ return {
+ presets: [
+ [
+ '@babel/preset-env',
+ {
+ targets: {
+ chrome: 59,
+ edge: 13,
+ firefox: 50,
+ safari: 8
+ }
+ }
+ ],
+ [
+ '@babel/preset-typescript',
+ {
+ allExtensions: true
+ }
+ ]
+ ],
+ plugins: [
+ '@babel/plugin-transform-typescript',
+ 'transform-class-properties',
+ '@babel/proposal-object-rest-spread'
+ ]
+ };
+};
+```
+
+### ts 静态类型检查
+
+```js
+const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
+const ForkTsCheckerNotifierWebpackPlugin = require('fork-ts-checker-notifier-webpack-plugin');
+
+module.exports = (config, resolve) => {
+ return () => {
+ config.plugin('ts-fork').use(ForkTsCheckerWebpackPlugin, [
+ {
+ // 将async设为false,可以阻止Webpack的emit以等待类型检查器/linter,并向Webpack的编译添加错误。
+ async: false
+ }
+ ]);
+ // 将TypeScript类型检查错误以弹框提示
+ // 如果fork-ts-checker-webpack-plugin的async为false时可以不用
+ // 否则建议使用,以方便发现错误
+ config.plugin('ts-notifier').use(ForkTsCheckerNotifierWebpackPlugin, [
+ {
+ title: 'TypeScript',
+ excludeWarnings: true,
+ skipSuccessful: true
+ }
+ ]);
+ };
+};
+```
+
+### 友好错误提示
+
+config/FriendlyErrorsWebpackPlugin.js
+
+```js
+const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
+
+module.exports = (config, resolve) => {
+ return () => {
+ config.plugin('error').use(FriendlyErrorsWebpackPlugin);
+ };
+};
+```
+
+### 配置 style,css、less、sass、postcss 等
+
+```js
+module.exports = (config, resolve) => {
+ const createCSSRule = (lang, test, loader, options = {}) => {
+ const baseRule = config.module.rule(lang).test(test);
+ const normalRule = baseRule.oneOf('normal');
+ normalRule
+ .use('extract-css-loader')
+ .loader(require('mini-css-extract-plugin').loader)
+ .options({
+ hmr: process.env.NODE_ENV === 'development',
+ publicPath: '/'
+ });
+ normalRule
+ .use('css-loader')
+ .loader(require.resolve('css-loader'))
+ .options({});
+ normalRule.use('postcss-loader').loader(require.resolve('postcss-loader'));
+ if (loader) {
+ const rs = require.resolve(loader);
+ normalRule
+ .use(loader)
+ .loader(rs)
+ .options(options);
+ }
+ };
+
+ return () => {
+ createCSSRule('css', /\.css$/, 'css-loader', {});
+ createCSSRule('less', /\.less$/, 'less-loader', {});
+ createCSSRule('scss', /\.scss$/, 'sass-loader', {});
+ createCSSRule('postcss', /\.p(ost)?css$/);
+ };
+};
+```
+
+### postcss 配置
+
+```js
+module.exports = {
+ plugins: {
+ 'postcss-px-to-viewport': {
+ unitToConvert: 'px',
+ viewportWidth: 750,
+ unitPrecision: 5,
+ propList: ['*'],
+ viewportUnit: 'vw',
+ fontViewportUnit: 'vw',
+ selectorBlackList: [],
+ minPixelValue: 1,
+ mediaQuery: false,
+ replace: true,
+ exclude: [],
+ landscape: false,
+ landscapeUnit: 'vw',
+ landscapeWidth: 568
+ }
+ }
+};
+```
+
+### 编译后 css 对比
+
+src/style/index.less
+
+```less
+/* index.less */
+.test {
+ width: 300px;
+}
+```
+
+dist/app.css
+
+```css
+/* index.css */
+.test {
+ width: 26.66667vw;
+ height: 26.66667vw;
+ color: red;
+ background-color: orange;
+}
+/* app.css */
+.test {
+ font-size: 8vw;
+}
+/* index.less */
+.test {
+ width: 40vw;
+}
+
+/* index.scss */
+.test {
+ height: 40vw;
+}
+/* index.postcss */
+.test {
+ background: green;
+ height: 26.66667vw;
+}
+```
+
+### 配置 autoprefixer
+
+自动添加 css 前缀
+
+postcss.config.js
+
+```js
+module.exports = {
+ plugins: {
+ autoprefixer: {
+ overrideBrowserslist: [
+ '> 1%',
+ 'last 3 versions',
+ 'iOS >= 8',
+ 'Android >= 4',
+ 'Chrome >= 40'
+ ]
+ }
+ }
+};
+```
+
+#### 转换前
+
+```css
+/* index.css */
+.test {
+ width: 200px;
+ height: 200px;
+ color: red;
+ display: flex;
+ background-color: orange;
+}
+```
+
+#### 转换后
+
+```css
+/* index.css */
+.test {
+ width: 26.66667vw;
+ height: 26.66667vw;
+ color: red;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ background-color: orange;
+}
+```
+
+### 开启 source map
+
+```js
+config.devtool('cheap-source-map');
+```
+
+```js
+|--dist
+ |-- app.bundle.js
+ |-- app.bundle.js.map
+ |-- app.css
+ |-- app.css.map
+ |-- index.html
+```
+
+在源文件下会有一行注释,证明开启了 sourcemap
+
+```js
+/*# sourceMappingURL=app.css.map*/
+```
+
+---
diff --git "a/docs/\350\257\276\346\227\2664.md" "b/docs/\350\257\276\346\227\2664.md"
new file mode 100644
index 00000000..82b6b1c7
--- /dev/null
+++ "b/docs/\350\257\276\346\227\2664.md"
@@ -0,0 +1,178 @@
+## 课时 4:webpack 性能优化1
+
+本章讲解
+
+1. 分离 Manifest
+2. Code Splitting(代码分割)
+3. Bundle Splitting(打包分割)
+4. Tree Shaking(删除死代码)
+5. 开启 gzip
+
+### 分离 Manifest
+
+```js
+module.exports = (config, resolve) => {
+ return () => {
+ config
+ .optimization
+ .runtimeChunk({
+ name: "manifest"
+ })
+ }
+}
+```
+
+### Code Splitting
+
+1. 使用动态 import 或者 require.ensure 语法,在第一节已经讲解
+2. 使用 `babel-plugin-import` 插件按需引入一些组件库
+
+### Bundle Splitting
+
+将公共的包提取到 `chunk-vendors` 里面,比如你require('vue'),webpack 会将 vue 打包进 chunk-vendors.bundle.js
+
+```js
+module.exports = (config, resolve) => {
+ return () => {
+ config
+ .optimization.splitChunks({
+ chunks: 'async',
+ minSize: 30000,
+ minChunks: 1,
+ maxAsyncRequests: 3,
+ maxInitialRequests: 3,
+ cacheGroups: {
+ vendors: {
+ name: `chunk-vendors`,
+ test: /[\\/]node_modules[\\/]/,
+ priority: -10,
+ chunks: 'initial'
+ },
+ common: {
+ name: `chunk-common`,
+ minChunks: 2,
+ priority: -20,
+ chunks: 'initial',
+ reuseExistingChunk: true
+ }
+ }
+ })
+ config.optimization.usedExports(true)
+ }
+}
+```
+
+### Tree Shaking
+
+config/optimization.js
+
+```js
+config.optimization.usedExports(true);
+```
+
+src/treeShaking.js
+
+```js
+export function square(x) {
+ return x * x;
+}
+
+export function cube(x) {
+ return x * x * x;
+}
+```
+
+在 main.js 中只引用了 cube
+
+```js
+import { cube } from './treeShaking';
+
+console.log(cube(2));
+```
+
+#### 未使用 Tree Shaking
+
+```js
+{
+ "./src/treeShaking.js": function(
+ module,
+ __webpack_exports__,
+ __webpack_require__
+ ) {
+ "use strict";
+ __webpack_require__.r(__webpack_exports__);
+ __webpack_require__.d(__webpack_exports__, "square", function() {
+ return square;
+ });
+ __webpack_require__.d(__webpack_exports__, "cube", function() {
+ return cube;
+ });
+ function square(x) {
+ return x * x;
+ }
+ function cube(x) {
+ return x * x * x;
+ }
+ }
+}
+```
+
+#### 使用了 Tree Shaking
+
+这里只导出了 cube 函数,并没有将 square 导出去
+
+当然你可以看见 square 函数还是在 bundle 里面,但是在压缩的时候就会被干掉了,因为它并没有被引用
+
+```js
+{
+ "./src/treeShaking.js": function(
+ module,
+ __webpack_exports__,
+ __webpack_require__
+ ) {
+ "use strict";
+ __webpack_require__.d(__webpack_exports__, "a", function() {
+ return cube;
+ });
+ function square(x) {
+ return x * x;
+ }
+ function cube(x) {
+ return x * x * x;
+ }
+ }
+}
+```
+
+只有当函数给定输入后,产生相应的输出,且不修改任何外部的东西,才可以安全做shaking的操作
+
+如何使用tree-shaking?
+
+1. 确保代码是es6格式,即 export,import
+2. package.json中,设置 sideEffects
+3. 确保 tree-shaking 的函数没有副作用
+4. babelrc中设置presets [["@babel/preset-env", { "modules": false }]] 禁止转换模块,交由webpack进行模块化处理
+5. 结合uglifyjs-webpack-plugin
+
+其实在 `webpack4` 我们根本不需要做这些操作了,因为 `webpack` 在生产环境已经帮我们默认添加好了,开箱即用!
+
+### 开启 gzip
+
+CompressionWebpackPlugin.js
+
+```js
+const CompressionWebpackPlugin = require('compression-webpack-plugin');
+
+module.exports = (config, resolve) => {
+ return () => {
+ config.plugin('CompressionWebpackPlugin').use(CompressionWebpackPlugin, [
+ {
+ algorithm: 'gzip',
+ test: /\.js(\?.*)?$/i,
+ threshold: 10240,
+ minRatio: 0.8
+ }
+ ]);
+ };
+};
+```
diff --git "a/docs/\350\257\276\346\227\2665.md" "b/docs/\350\257\276\346\227\2665.md"
new file mode 100644
index 00000000..e69de29b