本篇讲的是 Webpack 对于优化打包构建速度,也就是对于开发体验和效率的优化。
有如下几处可以优化:
优化 babel-loader
IgnorePlugin 避免引入无用模块
noParse 避免重复打包
happyPack //多进程打包工具
ParalleUglifyPlugin //多进程打包压缩
自动刷新
热更新
DLLPlugin
1.优化 babel-loader
在babel-loader后加cacheDirectory,开启缓存,
没有改动的es6代码会启用缓存,不会重新编译
[webpack.dev.js]:
module: {
rules: [{
test: /\.js$/,
loader: ['babel-loader?cacheDirectory'],
include: srcPath,
exclude: /node_modules/
},
]
},
2.IgnorePlugin 避免引入无用模块
使用场景:
日期处理类库moment中,包含很多种语言,默认引入所有语言的JS代码,代码体积过大;只需要使用中文,那就需要配置忽略对整个类库的引入,手动按需引入中文。
当全部引入配置
[index.js]配置
import moment from 'moment' //全部引入
moment.locale('zh-cn') //设置语言为中文
当按需引入配置
① 忽略 moment 引用的所有语言包,忽略 moment 下的 /locale 目录
[webpack.prod.js]配置:
plugins: [
// 忽略 moment 下的 /locale 目录
new webpack.IgnorePlugin(/\.\/locale/, /moment/),
]
② 手动动态引入中文语言
[index.js]:
import moment from 'moment' // moment的引入被忽略
import 'moment/locale/zh-cn' //手动引入中文语言包
综上,IgnorePlugin 避免引入无用模块,减少了打包体积,提升了打包速度。
3.noParse 避免重复打包
像vue.min.js已经模块化处理过了,需要忽略对此类型文件的打包。
webpack.prod.js:
rules: [{
test: /\.vue$/,
loader: ['vue-loader'],
include: srcPath
}, ]
IgnorePlugin vs noParse
- IgnorePlugin 直接不引入,代码中没有,减少了产出代码体积;
- noParse 引入了,但是未打包;
4.happyPack 多进程打包
js是单线程,开启多线程打包,提高构建速度(特别是多核CPU)。
webpack.prod.js:
① 安装 HappyPack,并引入:
const HappyPack = require('happypack')
② plugins中引入HappyPack规则
new HappyPack({
// 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
id: 'babel',
// 如何处理 .js 文件,用法和 Loader 配置中一样
loaders: ['babel-loader?cacheDirectory']
}),
③ rules 中 配置js 转换规则为happypack:
rules: [{
test: /\.js$/,
// 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
use: ['happypack/loader?id=babel'],
include: srcPath,
// exclude: /node_modules/
}, ]
5.ParalleUglifyPlugin 多进程压缩JS
JS单线程, 开启多进程压缩更快,webpack 内置 Uglify工具压缩JS
webpack.prod.js:
① 安装webpack-parallel-uglify-plugin 并引入配置文件中
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
② plugins中引入ParallelUglifyPlugin规则
new ParallelUglifyPlugin({
// 传递给 UglifyJS 的参数
// (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)
uglifyJS: {
output: {
beautify: false, // 最紧凑的输出
comments: false, // 删除所有的注释
},
compress: {
// 删除所有的 `console` 语句,可以兼容ie浏览器
drop_console: true,
// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: true,
}
}
})
happyPack 放在生产环境下,也可以放在本地环境下;
但是 ParallelUglifyPlugin 的压缩就只能放在生产环境下,开发环境下没有必要去做压缩
项目较大, 打包较慢, 开启多进程能提高速度
项目较小, 打包很快, 开启多进程会降低速度( 进程开销)
6.自动刷新
自动刷新用于开发环境
webpack.dev.js:
module.export = {
watch: true, // 开启监听,默认为 false
watchOptions: {
ignored: /node_modules/, // 忽略哪些
// 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高
// 默认为 300ms
aggregateTimeout: 300,
// 判断文件是否发生变化是通过不停的去询问系统指定文件有没有变化实现的
// 默认每隔1000毫秒询问一次
poll: 1000
}
}
若开发环境开启devserver,自动刷新便默认不开启
7.热更新
webpack.dev.js:
① 引用webpack的插件HotModuleReplacementPlugin
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');
② 配置入口
entry: {
// index: path.join(srcPath, 'index.js'),
index: [
'webpack-dev-server/client?http://localhost:8080/',
'webpack/hot/dev-server',
path.join(srcPath, 'index.js')
],
other: path.join(srcPath, 'other.js')
},
③ 在 plugins中 new HotModuleReplacementPlugin()
④ 配置 devServer中 的 hot: true
devServer: {
port: 8080,
progress: true, // 显示打包的进度条
contentBase: distPath, // 根目录
open: true, // 自动打开浏览器
compress: true, // 启动 gzip 压缩
hot: true,
// 设置代理
proxy: {
// 将本地 /api/xxx 代理到 localhost:3000/api/xxx
'/api': 'http://localhost:3000',
// 将本地 /api2/xxx 代理到 localhost:3000/xxx
'/api2': {
target: 'http://localhost:3000',
pathRewrite: {
'/api2': ''
}
}
}
},
⑤ 开发环境中注册哪些模块能触发热更新
index.js:
// 增加,开启热更新之后的代码逻辑
if (module.hot) {
module.hot.accept(['./math'], () => {
const sumRes = sum(10, 30)
console.log('sumRes in hot', sumRes)
})
}
自动刷新 VS 热更新
自动刷新:整个网页全部刷新,速度较慢;状态会丢失
热更新:新代码生效,网页不刷新,状态不丢失
8.DLLPlugin 动态链接库插件
背景:
前端框架如 vue React ,体积大,构建慢
较稳定,不常升级版本
同一个版本之构建一次就可以,不用每次都重新构建
DLLPlugin 将常用框架打包成dll文件,然后再引用
webpack 已内置DLLPlugin支持:
- DLLPlugin 打包出 dll 文件
- DllReferencePlugin -使用 dll 文件
Stage1. 打包生成 dll 文件
① 单独配置 webpack.dll.js,用于生成dll文件(DllPlugin插件)
const path = require('path')
const DllPlugin = require('webpack/lib/DllPlugin')
const { srcPath, distPath } = require('./paths')
module.exports = {
mode: 'development',
// JS 执行入口文件
entry: {
// 把 React 相关模块的放到一个单独的动态链接库
react: ['react', 'react-dom']
},
output: {
// 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,
// 也就是 entry 中配置的 react 和 polyfill
filename: '[name].dll.js',
// 输出的文件都放到 dist 目录下
path: distPath,
// 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
// 之所以在前面加上 _dll_ 是为了防止全局变量冲突
library: '_dll_[name]',
},
plugins: [
// 接入 DllPlugin
new DllPlugin({
// 动态链接库的全局变量名称,需要和 output.library 中保持一致
// 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
// 例如 react.manifest.json 中就有 "name": "_dll_react"
name: '_dll_[name]',
// 描述动态链接库的 manifest.json 文件输出时的文件名称
path: path.join(distPath, '[name].manifest.json'),
}),
],
}
② 在package.json里配置dll,并运行:npm run dll
package.json:
script :{
"dll":"webpack --config build/webpack.dll.js"
}
产出文件:
react.dll.js文件、//react打包后的dll文件
react.manifast.json.js文件//索引 找到对应关系
Stage2. 使用 dll 文件
webpack.dev.js配置引入:
① 引入插件 DllReferencePlugin
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
② 忽略 /node_modules/ 代码的loader
module: {
rules: [
{
test: /\.js$/,
loader: ['babel-loader'],
include: srcPath,
exclude: /node_modules/ // 第二,不要再转换 node_modules 的代码
},
]
},
③ plugin配置 DllReferencePlugin 插件,告诉 Webpack 使用了哪些动态链接库
plugins: [
// 第三,告诉 Webpack 使用了哪些动态链接库
new DllReferencePlugin({
// 描述 react 动态链接库的文件内容
manifest: require(path.join(distPath, 'react.manifest.json')),
}),
],
④ index.html 里引用 react.dll.js:
<script src="./react.dll.js"></script>
webpack优化构建速度总结:
可用于生产环境:
- 优化 babel-loader 缓存(主要用于开发)
- IgnorePlugin // 避免引入无用模块
- noParse // 避免重复打包
- happyPack // 多进程打包
- ParalleUglifyPlugin(主要用于生产环境) // 多进程压缩JS
不能用于生产环境:
- 自动刷新
- 热更新
- DLLPlugin(开发)