第五章:webpack构建速度和体积优化策略
https://github.com/cpselvis/geektime-webpack-course
1. 初级分析,使用 webpack 内置的 stats
stats: 构建统计信息
package.json
scripts: {
// 将构建的信息输出到当前的 stats.json 中
"build:stats":"webpack --env production --json > stats.json",
}
2. 速度分析:使用 speed-measure-webpack-plugin
可以看到每个 loader 和 插件的执行耗时。
安装依赖: npm i speed-measure-webpack-plugin -D
webpack.config.js
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin')
const smp = new SpeedMeasureWebpackPlugin();
module.exports = smp.wrap({
entry: ...,
output: ...,
plugins: [
...
]
})
通过将 webpack 配置,使用 smp 包裹一遍,最终可以得到每个插件,每个 loader 的时间。
3. 体积分析: 使用 webpack-bundle-analyzer 分析体积
安装依赖: npm i webpack-bundle-analyzer -D
设置 webpack.config.js
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
plugins: [
new BundleAnalyzerPlugin(),
]
如某个包很大,想想是否有可能这个包用 CDN 进行加载,而不打入 bundle 中
是否可以利用 js 懒加载,动态 import js
4. 使用高版本的 webpack 和 nodejs(优化构建时间)
webpack 4.0 对比 webpack 3.0 ,大概构建时间能降低 60% - 90%。
使用 webpack 4 能带来优化的原因: 1. V8 带来的优化(for of 替代 forEach, Map 和 Set 代替 Object,includes 代替 indexOf) 2. 默认使用更快的 md4 hash 算法 3. webpack AST 可以直接从 loader 传递给 AST, 减少解析时间 4. 使用字符串方法,替代正则表达式
5. 多进程/多实例构建
可选方案: 1. thread-loader (官方方案) 2. parallel-webpack 3. HappyPack (webpack3 的方案,目前已不再维护,推荐使用 thread-loader)
HappyPack 原理,每次 webpack 解析一个模块,HappyPack 会将它和它的以来分配给 worker 线程中。thread-loader 其实也是如此。
happypack 配置
安装依赖: npm install --save-dev happypack
设置 webpack.config.js
const HappyPack = require('happypack');
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
// 'babel-loader',
'happypack/loader', // 注释掉原有的loader,如 babel-loader, 添加 happypack/loader
],
},
]
},
plugins: [
new HappyPack({
// 将注释掉的原有 loader 添加回来
loaders: ['babel-loader'],
}),
]
}
thread-loader 配置
安装依赖: npm install --save-dev thread-loader
设置 webpack.config.js
module.exports = {
{
test: /\.js$/,
use: [
{
loader: 'thread-loader',
options: {
worker: 3, // 启动三个 worker 进程
},
},
'babel-loader',
],
},
}
不知道为啥,我的测试结果没有很大的提升~~
6. 多进程并行压缩代码
使用 parallel-uglify-plugin 插件
使用 uglifyjs-webpack-plugin 开启 parallel 参数(不支持压缩 es6 语法)
terser-webpack-plugin 开启 parallel 参数 (推荐使用)
7. 进一步分包: 预编译资源模块
分包:即设置 extrnals
思路: 将 react, react-dom 基础包通过 cdn 引入,不打入 bundle 中
方法:使用 html-webpack-extrnals-plugin
缺点:会引入很多的 script 标签,如 react 一个,react-dom 一个,react-redux 又是一个。
更好的方式 是使用 DLLPlugin,DllReferencePlugin, 将 react, react-dom, redux, react-redux 基础包和业务基础包打包成一个文件。
新建 webpack.dll.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: {
// 将 react 和 react-dom 进行打包,生成 output.filename 文件
library: [
'react',
'react-dom',
],
},
output: {
// 将生成的文件,存放于 build/library 目录中
// 全局暴露的变量是 library
filename: '[name]_[chunkhash:8].dll.js', // 生成 library_[xxx].dll.js
path: path.join(__dirname, 'build/library'),
library: '[name]',
},
plugins: [
// =============================================
// dll plugin 中的name 必须和 output.library 一致
// =============================================
new webpack.DllPlugin({
name: '[name]',
path: path.resolve(__dirname, './build/library/[name].json'),
}),
],
}
设置 webpack.config.js
plugins: [
// manifest 的值与 DLLPlugin 的 path 输出的json路径需要一致
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, './build/library/library.json'),
}),
]
最后,需要将 [name]_[chunkhash:8].dll.js 引入到 html 模板中。
library.json 长这样:
{
"name": "library",
"content": {
"./node_modules/react/index.js": {
"id": 0,
"buildMeta": {
"providedExports": true
}
},
"./node_modules/react-dom/index.js": {
"id": 0,
"buildMeta": {
"providedExports": true
}
},
}
}
大致就是,DllPlugin 在打包 dll.js(内部暴露一个 library 的变量,内部包含了 react 和 react-dom 打包后的内容) 的时候,会生成以上的 .json 映射文件。
在 DllReferencePlugin 中引用该映射文件,使得在遇到 react 和 react-dom 的时候,不会打入 bundle 中。
而最终,还是要在页面中引入 .dll.js 文件。
8. 充分利用缓存提升二次构建速度
思路:
1. babel-loader 开启缓存: {cacheDirectory: ture}
参考文档 2. terser-webpack-plugin 开启压缩缓存: {cache: true}
参考文档 3. 使用 cache-loader 或者 hard-source-wekpack-plugin 参考文档
以上开启缓存之后,会在 node_modules 下生成 .cache 目录,用于存放缓存结果。
9. 缩小构建目标
比如设置 babel-loader 不解析(exclude
) node_modules
优化 resolve.modules 配置,减少模块搜索层级
优化 resolve.mainFields 配置
优化 resolve.extensions 配置
合理使用 alias
module.exports = {
resolve: {
alias: {
// 让 import react 的时候,直接查找到该目录
react: path.resolve(__dirname, './node_modules/react/dist/react.min.js')
...
},
// 只在当前的 node_modules 查找,不继续往父级查找
modules: [path.resolve(__dirname, 'node_modules')],
// 设置只查找什么后缀的文件
extensions: ['.js'],
// 默认 webpack 会查找 package.json 中的 main 字段
// 之后会查找 index.js 文件等
// 设置 mainFields 之后,只找 package.json 中的main 字段
mainFields: ["main"]
}
}
10. 使用 Tree Sharking 擦除无用的 CSS
无用的 CSS 代码如何删除掉?
Purufy CSS: 遍历代码,识别已经用到的 CSS class
uncss: HTML 需要通过 jsdom 来加载,所有的样式通过 PostCSS 解析,通过 document.querySelector 来识别在 html 文件里面不存在的选择器。 即 document.querySelector 能获取到元素,则说明该样式有被用到。否则是无用的。
做法: purgecss-webpack-plugin 与 mini-css-extract-plugin 一起使用
安装依赖: npm i purgecss-webpack-plugin -D
设置 webpack.config.js
const PurgecssPlugin = require('purgecss-webpack-plugin')
const PATHS = {
src: path.join(__dirname, 'src'),
}
new PurgecssPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
}),
11. 使用 webpack 进行图片压缩
基于 node 库的 imagemin (推荐) 或者 tinypng 的 API。
安装依赖: npm install image-webpack-loader --save-dev
设置 webpack.config.js
{
test: /\.(png|jpg|jpeg|gif)$/,
use: [{
loader: 'file-loader', // 不能使用 url-loader, 因为 url-loader 是采用 base64 的方式
options: {
name: '[name].[hash:8].[ext]', // 设置图片 hash
},
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65,
},
// optipng.enabled: false will disable optipng
optipng: {
enabled: false,
},
pngquant: {
quality: [0.65, 0.90],
speed: 4,
},
gifsicle: {
interlaced: false,
},
// the webp option will enable WEBP
webp: {
quality: 75,
},
},
},
],
},
12. 使用动态 Polyfill 服务
例如 Promise 语法,Map, Set 语法。问题在于但部分手机浏览器能元素支持 Promise,有一些浏览器不能。
如果统一返回 Promise,对于能支持 Promise 的机器来说,就是多余的。
方案
优点
缺点
是否采用
babel-polyfill
React 16 官方推荐
1. 包体积 200k, 难以单独抽离 Map 和 Set 2. 项目里 react 是单独引用的 cdn,如果要使用 polyfill, 需要单独构建一份在 react 之前加载
否
babel-plugin-transform-runtime
能够只引用某些用到的类或者方法,相对体积较小
不能 polyfill 原型上的方法,不适用于业务项目上的复杂环境
否
polyfill-service (后端服务,根据请求者的UA 返回相应的 polyfill)
只给用户返回需要的 polyfill, 社区维护
部分国内奇葩浏览器UA可能无法识别,(此时可以降级返回所有的 polyfill)
是
polyfill-service 地址: https://polyfill.io/v3/polyfill.js
体积优化总结: 1. scope hoisting 2. tree-sharking 3. 公共资源分离 4. 图片压缩 5. 动态 polyfill
Last updated
Was this helpful?