webpack构建速度和构建体积优化策略
初级分析:使用webpack内置的stats
stats:构建的统计信息
package.json中使用stats 输出一个stats.json文件
"scripts": {
"build:stats": "webapck --env production --json > stats.json"
}
使用speed-measure-webpack-plugin
可以看到每个loader和plugin执行耗时
使用webpack-bundle-analyzer分析体积
构建完成后会在8888端口展示大小
可以分析依赖的第三方模块文件大小,业务里面组件代码大小
速度优化
V8 带来的优化(for of替代foreach,Map和Set替代Object,includes替代indexOf,默认使用更快的md4 hash算法,webpack AST可以直接从loader传递给AST,减少解析时间,使用字符串方法替代正则表达式)
-
使用高版本的webpack和Node.js
-
多进程多实例构建:资源并行解析可选方案
- thread-loader 可选方案:parallel-webpack/HappyPack
-
多进程/多实例:并行压缩
- 使用parallel-uglify-plugin插件
- uglifyjs-webpack-plugin开启parallel参数(不支持es6的语法压缩)
- terser-webpack-plugin开启parallel参数(支持es6压缩,推荐使用)
-
分包:设置Externals
思路:将react,react-dom基础包通过cdn引入,不打入bundle中
- 方法:使用html-webpack-externals-plugin
-
进一步分包
思路:将react,react-dom,redux,react-redux基础包和业务基础包打成一个文件
- 进一步分包:进阶使用预编译资源模块
- 使用DLLPlugin进行分包,DllReferencePlugin对manifest.json(描述文件)引用
- 使用DLLPlugin进行分包 webpack.dll.json 文件
const path = require("path"); const webpack = require("webpack"); module.exports = { context: process.cwd(), resolve: { extensions: ['.js', '.jsx', '.json', '.less', '.css'], modules: [__dirname, 'node_modules'] }, entry: { library: [ 'react', 'react-dom', 'redux', 'react-redux' ] }, output: { filename: '[name]_[chunkhash].dll.js', path: path.resolve(__dirname, './build/library'), library:'[name]' }, plugins: [ new webpack.DllPlugin({ name: '[name]', path: './build/library/[name].json' }) ] }
-
使用缓存
目的:提升二次构建速度
缓存思路:提升模块转换io速度
- babel-loader开启缓存
new HappyPack({ loaders: ['babel-loader?cacheDirectory=true'] }) // 开启babel缓存
- terser-webpack-plugin 开启压缩缓存
optimization: { minimizer: { new TerserPlugin({ parallel: true, cache: true }) } }
- 使用cache-loader 或者 hard-source-webpack-plugin
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); modules.exports = { ... plugins: [ new HardSourceWebpackPlugin() ] }
-
缩小构建目标
目的:尽可能的少构建模块,比如babel-loader 不解析 node_modules
module.exports = { rules: { test: /\.js$/, loader: 'happypack/loader', exclude: 'node_modules' } }
-
减少文件搜索范围
- 优化resolve.modules 配置(较少模块搜索层级)
- 优化resolve.mainFields配置
- 优化resolve.extensions配置
- 合理使用alias
module.exports = { resolve: { alias: { react: path.resolve(__dirname, './node_modules/react/dist/react.min.js') }, modules: [path.resolve(__dirname, 'node_modules')], extensions:['.js'], // 默认找.js .json文件 mainFields:['main'] // 只查找 package.json的 main 文件 } }
-
使用Tree Shaking擦除无用的JavaScript和CSS
概念:1个模块可能有多个方法,只要其中的某个方法使用到了,则整个文件都会被打到bundle里面去,tree shaking就是只把用到的方法打入bundle,没用到的方法会在uglify阶段被擦除掉。
使用:webpack默认支持,在.babelrc里面设置modules:false即可,production mode的情况下默认开启
- PurifyCSS:遍历代码,识别已经用到的CSS class
- uncss:HTML需要通过jsdom加载,所有的样式通过PostCSS解析,通过document.querySelector来识别在html文件里面不存在的选择器
使用 purgecss-webpack-plugin和mini-css-extract-plugin配合使用
-
使用webpack进行图片压缩
要求Node库的imagemin或者tinypng API,使用:配置image-webpack-loader
imagemin的压缩原理,pngquant:是一款png压缩器,通过将图像转换为具有alpha通道(通常比24/32位PNG文件小60-80%)的更高效的8位PNG格式,可显著减少文件大小
pngcrush:其主要目的是通过尝试不同的压缩级别和PNG过滤方法来降低PNG IDAT数据流的大小
tinypng:将24位png文件转换为更小有索引的8位图片,同时所有非必要的metadata也会被剥离掉
{ test: /.(png|jpg|gif|jpeg)$/, use: [ { loader: 'file-loader', options: { name: '[name]_[hash:8].[ext]' } }, { loader: 'image-webpack-loader', options: { mozjpeg: { progressive: true, quality: 65 }, // optipng.enabled: false will disable optipng optipng: { enabled: false, }, pngquant: { quality: '65-90', speed: 4 }, gifsicle: { interlaced: false, }, // the webp option will enable WEBP webp: { quality: 75 } } }
-
构建体积优化: 动态Polyfill babel-polyfill占比29.6%很大
方案 优点 缺点 是否采用 babel-polyfill 简单方便 1.包体积较大,难以单独抽离Map,Set
2.每一个单独的项目都需要手动引用否 babel-plugin-transform-runtime 能只polyfill用到的类或者方法,相对体积较小 不能polyfill原型上的方法,不适用于业务项目的复杂开发环境 否 自己写Map,Set的polyfill 定制化高,体积小 1.浪费时间,重复造轮子,容易在日后年久失修留坑
2.即使体积小,依然所有用户都要加载否 polyfill-service 只给用户返回需要的polyfill,社区维护 部分国内的浏览器UA可能无法识别(但是可以降级返回全部polyfill) 是 Polyfill Service原理:识别User Agent,下发不同的Polyfill
polyfill.io 官方提供的服务
<script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>