前言
笔者最近重新复习了一遍 Webpack 核心功能,果然是温故而知新!这篇文章结合高级进阶之 Webpack 篇阅读效果更佳!
常见优化手段
构建速度优化
思路:缩小搜索范围,减少不必要的模块打包,加快构建(缓存,多线程)
缩少搜索范围:
- 指定第三方目录 resolve.modules,resolve.alias 缓存目录,extensions 减少后缀的搜索
- loader 配置指定编译范围 exclude/include
减少不必要的模块打包:
- 通过cdn引入(vue 全家桶)等,然后用externals 提取常用库,不会再打包到 bundle 文件,并可以通过import 引入;
- 通过 DllPlugin、DllReferencePlugin 插件来预编译模块,减少不必要的打包 注意:在 Webpack5 中已经不⽤ DllPlugin、DllReferencePlugin 了,⽽是⽤ HardSourceWebpackPlugin 替代
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
const plugins = [
new HardSourceWebpackPlugin(
)
]
加快构建:
- 使用cache-loader进行缓存;
- 使用 happypack 插件开启多个线程打包资源文件(happypack不再维护),可以用 thread-loader 替代
性能优化
代码压缩 :
使用 ParallelUglifyPlugin 插件:开启多线程对 js 文件压缩和缓存,删除多余的注释和 console.log
注意: 在生产环境下即配置中 mode 设置为 production,webpack 默认开启了 TerserWebpackPlugin 可以实现该功能,如果需要剔除调试代码可以自行配置,具体参考下面的 TerserWebpackPlugin 配置
- 多进程并行压缩
- webpack-paralle-uglify-plugin
- uglifyjs-webpack-plugin 开启 parallel 参数 (不支持ES6)
- terser-webpack-plugin 开启 parallel 参数
- 通过 mini-css-extract-plugin 提取 Chunk 中的 CSS 代码到单独文件,通过 css-loader 的 minimize 选项开启 cssnano 压缩 CSS。
- 图片压缩 配置 image-webpack-loader
代码分割缓存:
使用 optimization.splitChunks.cacheGroup 进行公共代码分割抽离和缓存
如何做 Tree Shaking
Webpack4.0 以上版本在 mode 为 production 时,会自动开启 Tree Shaking 能力。默认 production mode 的配置如下:
const config = {
mode: 'production',
optimization: {
usedExports: true,
minimizer: [
new TerserPlugin({...})
]
}
};
什么是副作用
"side effect(副作用)" 的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。
解决副作用:通过 package.json 的 "sideEffects" 属性,来实现这种方式。
{
"name": "your-project",
"sideEffects": ["./src/some-side-effectful-file.js"]
}
如果你的代码确实有一些副作用,可以改为提供一个数组:
{
"name": "your-project",
"sideEffects": ["./src/some-side-effectful-file.js"]
}
loader 与 plugin 的区别
- loader: loader是一个转换器是在 import 或"加载"模块时预处理文件,将A语言转成B语言,如 TypeScript转换为 JavaScript,less 转成 css,单纯的文件转换成浏览器可以识别的文件。
- plugin:插件是一个扩展器,可以扩展 webpack 的功能,在 webpack 运行的生命周期会广播许多钩子,plugin 会监听这些事件,在合适的时机通过 webapck API (run、compile、emit)等改变输出结果。
热更新
watch
配置新增 watch: true 或者在脚步加--wacth 即可实现代码变化之后自动打包
Live Reload
devServer: {
contentBase: './dist', //为./dist目录中的静态页面文件提供本地服务渲染
open: true //启动服务后自动打开浏览器网页
}
以上代码通过websocket 链接,使打开的网页和本地服务间建立持久化的通信。当源代码变更时,我们就可以通过 Socket 通知到网页端,网页端在接到通知后会自动触发页面刷新。
热更新实现
devServer: {
...
hot: true
},
加 hot 可以让css更新是局部刷新,但js(文本)内容改变还是会浏览器刷新
加 HotModuleReplacementPlugin 插件才可以全部的局部刷新功能
webpack 热更新原理
以下内容来自字节前端面试题
- 当修改了一个或多个文件;
- 文件系统接收更改并通知 webpack;
- webpack 重新编译构建一个或多个模块,并通知 HMR 服务器进行更新;
- HMR Server 使用 webSocket 通知 HMR runtime 需要更新,HMR 运行时
通过 HTTP 请求更新 jsonp; - HMR 运行时替换更新中的模块,如果确定这些模块无法更新,则触发整
个页面刷新。
注意:启动 HMR 后,css 抽离会不⽣效,还有不⽀持 contenthash,chunkhash
webapck 打包原理
重要配置代码展示
webpack-paralle-uglify-plugin
// 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
new ParallelUglifyPlugin({
// 传递给 UglifyJS 的参数
uglifyJS: {
output: {
// 最紧凑的输出
beautify: false,
// 删除所有的注释
comments: false,
},
compress: {
// 在UglifyJs删除没有用到的代码时不输出警告
warnings: false,
// 删除所有的 `console` 语句,可以兼容ie浏览器
drop_console: true,
// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: true,
}
},
}),
uglifyjs-webpack-plugin
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
optimization: {
minimizer: [
new UglifyJsPlugin({
test: /\.js(\?.*)?$/i, //测试匹配文件,
// include: /\/includes/, //包含哪些文件
// exclude: /\/node_modules/, //不包含哪些文件
//允许过滤哪些块应该被uglified(默认情况下,所有块都是uglified)。
//返回true以uglify块,否则返回false。
chunkFilter: chunk => {
// `vendor` 模块不压缩
if (chunk.name === "vendor") {
return false;
}
return true;
},
cache: false, //是否启用文件缓存,默认缓存在node_modules/.cache/uglifyjs-webpack-plugin.目录
parallel: true //使用多进程并行运行来提高构建速度
})
]
},
}
// 等价于
{
plugins: [
// 压缩JS文件
new UglifyJSPlugin({
test: /\.js(\?.*)?$/i, //测试匹配文件,
// include: /\/includes/, //包含哪些文件
// exclude: /\/node_modules/, //不包含哪些文件
//允许过滤哪些块应该被uglified(默认情况下,所有块都是uglified)。
//返回true以uglify块,否则返回false。
chunkFilter: chunk => {
// `vendor` 模块不压缩
if (chunk.name === "vendor") {
return false;
}
return true;
},
cache: false, //是否启用文件缓存,默认缓存在node_modules/.cache/uglifyjs-webpack-plugin.目录
parallel: true //使用多进程并行运行来提高构建速度,不支持 es6
}),
],
}
terser-webpack-plugin
// webpack.config.js
// 导入terser-webpack-plugin-->减少js体积(其中删除js的console.log和注释)
const TerserWebpackPlugin = require('terser-webpack-plugin');
// 实例化TerserWebpackPlugin对象
const terserPlugin = new TerserWebpackPlugin({
parallel: 4,
extractComments: true,
terserOptions: {
compress: {
warnings: false,
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log'] //移除console
}
}
});
module.exports = {
optimization: {
minimizer: [
// 只有打包环境为production时才能生效
terserPlugin
],
},
}
mini-css-extract-plugin
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: "css/[name].css",
// chunkFilename: "[name].css",
disable: isDebug
}),],
module: {
rules: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
};
optimization.splitChunks
splitChunks:{
cacheGroups: {
common:{
chunks: 'initial',
name:'Common', // 打包后的文件名
minSize: 0,
minChunks: 2 // 重复2次才能打包到此模块
},
vendor: {
priority: 1, // 优先级配置,优先匹配优先级更高的规则,不设置的规则优先级默认为0
test: /node_modules/, // 匹配对应文件
chunks: 'initial',
name:'Vendor',
minSize: 0,
minChunks: 1
}
}
}
热更新实现
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: './index.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, '/')
},
devServer: {
hot: true
},
plugins:[
new webpack.HotModuleReplacementPlugin()
]
}
如果不使用 HotModuleReplacementPlugin 可以直接 package.json 配置
"dev": "webpack-dev-server --hot --open"