优化打包构建速度
缩小打包范围
resolve.modules
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: config.alias,
modules: ['node_modules']
},
生产环境
缩小打包作用域(范围)
1. 优化babel-loader(module/rules)。 开启缓存, 设置文件范围(include、exclude)
test: /\.js$/,
loader: ['babel-loader?cacheDirectory'], // 开启混存
include: path.resolve(__dirname, 'src'), // 明确范围
// exclude: path.resolve(__dirname, 'node_modules') //两者选一即可
dev启动提升30%: 11286ms -> 7965ms
prod时间提升35%: 14.41s -> 9.34s
2. IgnorePlugin 避免引入无用模块
以moment 为例,正常打包index.js大小约为5K, 设置忽略local文件后, 打包后的index.js 约为1K。 项目需要的
地方手动引入。webpack.prod.js/plugins
new webpack.IgnorePlugin(/\.\/locale/, /moment/),// 忽略 moment 下的 /locale 目录
⚠️ 中文需要单独引入
import moment from 'moment'
import 'moment/locale/zh-cn'
moment.locale('zh-cn')
dev:
prod: moment(async-vendors.js)体积大大减小 571k -> 151k
3. noParse避免重复打包。
忽略对xx.js 文件的递归解析处理。例如jquery内部独立, 没有第三方依赖。
module: {
noParse: /jquery|lodash/,
noParse: [/react\.min\.js/],
noParse: (content) => /jquery|lodash/.test(content)
}
IgnorePlugin 与 noParse 的区别:IgnorePlugin 直接不引入, 代码中没有; noParse 引入, 但不打包
多进程打包。 JS 单线程, 开启多进程打包。提高构建速度(特别是多核CPU)
- thread-loader
- webpack-parallel-uglify-plugin
-
thread-loader(周下载130万)
webpack.prod.js (建议, 仅在生产环境中使用)
module: {
rules: [
{
test: /\.js$/,
loader: ['babel-loader?cacheDirectory', 'thread-loader'],
}
]
}
实践
loader: 'babel-loader',
loaders: ['thread-loader', 'babel-loader'],
before: Time: 36650ms
after: Time: 15276ms
提升: 58%webpack-parallel-uglify-plugin
webpack 内置Uglify 工具压缩JS。JS 单线程, 开启多进程压缩更快。和happyPack 同理。
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
new ParallelUglifyPlugin({
uglifyJS: { //使用 UglifyJS 压缩
output: {
beautify: false, // 最紧凑的输出
comments: false, // 删除所有的注释
},
compress: {
drop_console: true, // 删除所有的 `console` 语句,可以兼容ie浏览器
collapse_vars: true, // 内嵌定义了但是只用到一次的变量
reduce_vars: true, // 提取出出现多次但是没有定义成变量去引用的静态值
}
}
开启多进程需要按照需求使用:项目较大, 打包较慢, 开启多进程能提高速度。项目较小, 打包很快, 开启多进程会降低速度(进程开销)
happypack(已废弃)
happyPack(周下载11万)
module: {
rules: [{
test: /\.js$/
use: ['happypack/loader?id=babel'], // 把对 .js文件的处理转交给 id 为 babel 的 HappyPack 实例
include: srcPath,
}]
},
plugins: [
new HappyPack({ // happyPack 开启多进程打包
id: 'babel', // 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
loaders: ['babel-loader?cacheDirectory'] // 如何处理 .js 文件,用法和 Loader 配置中一样
}),
]
vue-cli4自带
vue-cli (内置接口)
module.exports = {
parallel: require('os').cpus().length > 1,
}
缓存
babel-loader缓存
terser-webpack-plugin实现缓存
cache-loader/hard-source-webpack-plugin
优化开发体验(开发环境)
6. 自动刷新(开发环境)
watch: true, // 开启监听,webpack-dev-server 会自动开启刷新浏览器
watchOptions: {
ignored: /node_modules/,
aggregateTimeout: 300, // 防止文件更新太快导致重新编译频率太高,
poll: 1000 // 判断文件是否发生变化是通过不停的去询问系统指定文件有没有变化实现的
}
7. 热更新
| 自动刷新(整个页面全部刷新) | 热更新 | | —- | —- | |
- 速度较慢
- 状态会丢失
| 新代码生效, 网页不刷新, 状态不丢失 |
配置+ 开启监听范围
plugins: [ new HotModuleReplacementPlugin() ],
devServer: { hot: true, }
if (module.hot) { // 配置, 哪些模块进行热更新
8. 动态链接库插件 : DllPugin + DllReferencePlugin
拆分 bundles
背景:前端框架(Vue、React),体积大,构建慢。 较稳定, 不常升级版本。 同一个版本只构建一次即可, 不用每次重新构建
webpack已内置DllPlugin支持;DllPlugin - 打包出dll文件; DllReferencePlugin - 使用dll 文件。 "dll": "webpack --config build/webpack.dll.js"
const DllPlugin = require('webpack/lib/DllPlugin')
module.exports = {
mode: 'development',
entry: {
react: ['react', 'react-dom'] // 把 React 相关模块的放到一个单独的动态链接库
},
output: {
filename: '[name].dll.js', // 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,
path: distPath, // 输出的文件都放到 dist 目录下
library: '_dll_[name]', // 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
},
plugins: [
new DllPlugin({
name: '_dll_[name]', // 动态链接库的全局变量名称,需要和 output.library 中保持一致
context: __dirname, // 必填,_dll_polyfill is not defined
path: path.join(distPath, '[name].manifest.json'), // 描述动态链接库的 manifest.json 文件输出时的文件名称
}),
],
}
webpack.dev.js
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
new DllReferencePlugin({
manifest: require(path.join(distPath, 'react.manifest.json')), // 描述 react 动态链接库的文件内容
}),
与 splitChunks 的区别?splitChunks
作用是将第三方的组件拆分出来,打包成一个或几个包,用于长期缓存。这个行为可以在webpack中设置并自动完成。DllPlugin
也能将第三方组件拆分出来,打包成一个或几个包,用于长期缓存且能加速打包过程。
那么它们的差异在于:
DllPlugin
需要设置打包的配置文件,并先于项目打包将第三方组件打包;DllPlugin
需要手动插入到对应的页面(可以使用add-asset-html-webpack-plugin
在打包项目的时候自动插入 );Dllplugin
内含有的组件在webpack
打包项目的时候,不经过打包过程。所以能加快打包速度。(这个其实可以使用webpack
中的external
来排除打包某些组件,然后通过链接将对应的组件链入页面,达到相同效果);- 如果库可以按需加载,
Dllplugin
将不能按需加载,它方式是全量的引入的,而splitChunks
可以按需加载地打包。
其实看起来,splitChunks
就是 DllPlugin
的自动版本。
优化产出
使用production。 优势:
- 自动开启代码压缩(webpack v4+)
- Vue、React等会自动删掉调试代码(ex, 开发环境的warning)
- 启动 Tree-Shaking(mode为’production’即可)
什么是Tree-Shaking?(注意: 必须使用ES6 Module 才能使 Tree-shaking 生效)比如引入但未使用的不打包。
ES6 Module, 静态引用, 编译时引用; Commonjs, 动态引入, 执行引入
什么是Scope Hosting?new webpack.optimize.ModuleConcatenationPlugin();
前提是ES6模块化语法的文件
优势:代码体积更小; 创建函数作用域更少; 代码易读性更好
懒加载
const Foo = resolve => require(['./Foo.vue'], resolve)
//或者
const Foo = () => import('./Foo');
动态polyfill
根据不同浏览器不同版本载入不同补丁。
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "entry"
}
]
]
}
vue-cli
// webpack.dll.conf.js
module.exports = {
entry: { // 把 vue 相关模块的放到一个单独的动态链接库
vue: ['babel-polyfill', 'vue', 'vue-router', 'vuex', 'axios', 'element-ui']
},
}
vue-cli4
// babel.config.js
module.exports = {
presets: [
['@vue/app', {
polyfills: [
'es.promise',
'es.symbol'
]
}]
]
}
参考:
https://tsejx.github.io/webpack-guidebook/best-practice/optimization/dynamic-polyfill
测试工具
webpack-bundle-analyzer
if (config.build.bundleAnalyzerReport) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
webpack-jarvis
if (process.env.watch) {
const Jarvis = require('webpack-jarvis')
console.log(config.build.watch);
webpackConfig.plugins.push(new Jarvis({
watchOnly: false,
port: 1337 // optional: set a port
}))
}
speed-measure-webpack-plugin
参考:
https://github.com/lgwebdream/FE-Interview/issues/25
https://www.yuque.com/allenzhoujiawei/kb/zsp9gp?language=zh-cn