谈谈 webpack 原理

  1. 初始化参数
  2. 开始编译:初始化 compiler 对象
  3. 确定入口
  4. 编译模板:调用 loader
  5. 完成模板编译:得到 loader 翻译后的所有模块
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 chunk,再把每个 chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

loader 作用

  1. 加载文件
  2. 转换文件

plugins 作用

  1. 打包优化
  2. 资源管理
  3. 注入环境变量
  4. Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果

webpack 的生命周期

  1. entry-options:option初始化
  2. compile:开始编译
  3. make:分析入口文件创建模板对象(compile中触发make事件并调用addEntry找到入口js文件,进行下一步的模块绑定)
  4. build-module:构建模块
  5. after-compile:完成所有模块构建结束编译过程
  6. emit:compiler开始输出生成的assets、插件;有最后的机会修改输出内容
  7. after-emit:输出完成

webpack 常用的 plugin 和 loader

常用的 loader

- `file-loader`:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件
- `url-loader`:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
- `source-map-loader`:加载额外的 Source Map 文件,以方便断点调试
- `image-loader`:加载并且压缩图片文件
- `babel-loader`:把 ES6 转换成 ES5
- `css-loader`:加载 CSS,支持模块化、压缩、文件导入等特性
- `style-loader`:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。
- `eslint-loader`:通过 ESLint 检查 JavaScript 代码

常用的 plugin
- `webpack-merge`:用于合并 webpack 的公共配置和环境配置(合并 webpack.config.js 和 webpack.development.js 或者 webpack.production.js)
- `yargs-parser`: 用于将我们的 npm scripts 中的命令行参数转换成键值对的形式如 `--mode development` 会被解析成键值对的形式 `mode: "development"`,便于在配置文件中获取参数
- `clean-webpack-plugin`: 用于清除本地文件,在进行生产环境打包的时候,如果不清除 dist 文件夹,那么每次打包都会生成不同的 js 文件或者 css 文件堆积在文件夹中,因为每次打包都会生成不同的 hash 值导致每次打包生成的文件名与上次打包不一样不会覆盖上次打包留下来的文件
- `progress-bar-webpack-plugin`: 打包编译的时候以进度条的形式反馈打包进度
- `webpack-build-notifier`: 当你打包之后切换到别的页面的时候,完成时会在本地系统弹出一个提示框告知你打包结果(成功或失败或警告)
- `html-webpack-plugin`: 自动生成 html,并默认将打包生成的 js、css 引入到 html 文件中
- `mini-css-extract-plugin`: webpack 打包样式文件中的默认会把样式文件代码打包到 bundle.js 中,mini-css-extract-plugin 这个插件可以将样式文件从 bundle.js 抽离出来一个文件,并且支持 chunk css
- `add-asset-html-webpack-plugin`: 从命名可以看出,它的作用是可以将静态资源 css 或者 js 引入到 html-webpack-plugin 生成的 html 文件中
- `uglifyjs-webpack-plugin`: 代码丑化,用于 js 压缩(可以调用系统的线程进行多线程压缩,优化 webpack 的压缩速度)
- `optimize-css-assets-webpack-plugin`: css 压缩,主要使用 cssnano 压缩器(webpack4 的执行环境内置了 cssnano,所以不用安装)
- `friendly-errors-webpack-plugin`: 能够更好在终端看到 webapck 运行的警告和错误
- `happypack`: 多线程编译,加快编译速度(加快 loader 的编译速度),注意,thread-loader 不可以和 mini-css-extract-plugin 结合使用
- `splitChunks`: CommonChunkPlugin 的后世,用于对 bundle.js 进行 chunk 切割(webpack 的内置插件)
- `DllPlugin`: 将模块预先编译,它会在第一次编译的时候将配置好的需要预先编译的模块编译在缓存中,第二次编译的时候,解析到这些模块就直接使用缓存,而不是去编译这些模块(webpack 的内置插件)
- `DllReferencePlugin`: 将预先编译好的模块关联到当前编译中,当 webpack 解析到这些模块时,会直接使用预先编译好的模块(webpack 的内置插件)
- `HotModuleReplacementPlugin`: 实现局部热加载(刷新),区别与在 webpack-dev-server 的全局刷新(webpack 的内置插件)

WebpackGulp、Grunt 的不同

1.Gulp、Grunt
- 轻量化的任务
- 将打包各个阶段称为 task,需要开发者自己去调用打包中各个阶段生成文件之后的 task 任务(串行执行)
2. Webpack
- 打包大型应用
- Webpack打包过程中会发布各个事件,开发者只要在这些事件阶段中通过Webpack提供的API` 修改 编译产物。不需要开发者掌握整个打包流程中各个阶段如何工作(管理好自己需要管理的那部分即可)

如何看待 WebpackRollup

  1. Webpack 是大型应用的打包,输出大量文件以及它们之间如何引用
  2. rollupJavaScript 类库的打包(注重最终输出一个 js 文件)

如果要将 .vue 文件中的 css 全部提取到一个 .css 文件中,为什么使用 plugin,而不是 loader

答:plugin 可以在 Webpack 的 生命周期中执行,可以获取到编译完成后的所有文件,在合适的时机通过 Webpack 提供的 API 改变输出结果。loader 用来加载和编译转换文件,不适合做文件内容抽取和合并

webpack 性能优化

开发环境

1. 优化代码调试(选择合适 `source-map`)
- source-map: 外部
- inline-source-map: 内联;`内联和外部的区别:外部生成了文件,内联没有,内联构建速度会更快`
- hidden-source-map: 外部
- evel-source-map: 内联;`eval-source-map 和 inline-source-map 的区别:inline-source-map只会生成一个内联 sourceMap,eval-source-map 每个文件都会生成一个 sourceMap`
- nosource-source-map: 外部
- cheap-source-map: 外部
- cheap-module-source-map: 外部
2. HMR
`devServer: { hot: true }`
作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块)极大提升构建速度
- 样式文件:可以使用 HMR 功能:因为 style-loader 内部实现了
- js 文件:默认不能使用 HMR 功能 --> 需要修改 js 代码,添加支持 HMR 功能的代码
注意:HMR 功能对 js 的处理,只能处理非入口 js 文件的其它文件
- html 文件:默认不能使用 HMR 功能,同时会导致问题:html 文件不能热更新了(不用做 HMR)
解决:修改入口文件,将 html 引入
3. Dll

- `DllPlugin`:将模块预先编译,只需编译一次(不易变的文件,一般为各类库,如:vue.js, axios。需要在 externals 中声明 build 被 DllPlugin 预编译的库)
- `DllReferencePlugin`:将预先编译好的模块关联到当前编译中 (将通过 DllPlugin 编译好的文件引入到 index.html 中)
4. 开启缓存
- babel 缓存
- `cacheDirectory:true`
|- 让第二次打包构建速度更快
- 文件资源缓存
- _`hash`_:每次 webpack 构建时会生成一个唯一的 hash 值
问题:因为 js 和 css 同时使用一个 hash 值
|- 如果重新打包,会导致所有缓存失效。(可能我却只改动一个文件)
- _`chunkhash`_:根据 chunk 生成的 hash 值。如果打包来源于同一个 chunk,那么 hash 值就一样
问题:js 和 css 的 hash 值还是一样的
|- 因为 css 是在 js 中被引入的,所以同属于一个 chunk
- _`contenthash`_:根据文件的内容生成 hash 值。不同文件 hash 值一定不一样
|- 让代码上线运行缓存更好使用
5. 开启多线程
- `HappyPack`:多线程编译,加快编译速度(加快 loader 的编译速度),不建议与 thread-loader 一起使用
- `thread-loader`:把这个 loader 放置在其他 loader 之前, 放置在这个 loader 之后的 loader 就会在一个单独的 worker 池(worker pool)中运行

1. 使用 oneOf 匹配 loader
2. tree shaking
- 去除无用代码
- 前提:1、必须使用 es6 模块化;2、开启 production 环境
- 作用:减少代码体积
- 在 `package.json` 中配置

"sideEffects": false 所有代码都没有副作用(都可以进行tree shaking)
问题:可能会把css / @babel/polyfill(副作用)文件干掉
"sideEffects": ["*.css"]

6. 多入口打包

生产环境优化
  
1. 比开发环境多一个文件压缩
- `uglifyjs-webpack-plugin`:用于 js 压缩
- `mini-css-extract-plugin`:可以将样式文件从 bundle.js 抽离出来一个文件
- `optimize-css-assets-webpack-plugin`:css 压缩,主要使用 cssnano 压缩器
1. 其余内容同上(开发环境)