背景
原想在网上搜一下webpack各版本分别升级了升级了什么,发生了什么变化,发现似乎都是都是1对1的比较没有一次性列出从3到4,从4到5的变化。于是便自己写一遍记录一下。
内容只做一个大概的引导,其中也有尚未解决的问题,有知道的大佬,还请不吝赐教。
webpack3
特性
- Scope Hoisting (作用域提升):将所有模块打包后放置到一个闭包函数中,从而减少了打包后文件大小,减少了浏览器运行耗时。
- Magic Comments (魔法注释):分割代码块,并实现懒加载
webpack3 => webpack4
特性
- 编译速度提升,but why and do what?
- 0配置开箱即用
- 提供了wasm的支持,(WebAssembly)了解我
- 全新插件系统
- 提供了针对插件和钩子的新API,如下:
- 所有的 hook 由 hooks 对象统一管理,它将所有的hook作为可扩展的类属性
- 添加插件时,你需要提供一个名字
- 开发插件时,你可以选择插件的类型(sync/callback/promise之一)
- 通过 this.hooks = { myHook: new SyncHook(…) } 来注册hook
- 提供了针对插件和钩子的新API,如下:
配置
- 提供mode属性:'production' | 'development' | 'none' ; development 有最好的开发体验,production 专注项目编译部署,比如开启 Scope hoisting 和 Tree-shaking 功能。
- optimization
- 由 optimization.splitChunks 和 optimization.runtimeChunk 替代CommonsChunkPlugin:前者拆分代码,后者提取runtime代码。原来的CommonsChunkPlugin产出模块时,会包含重复的代码,并且无法优化异步模块,minchunks的配置也较复杂,splitChunks解决了这个问题;另外,将 optimization.runtimeChunk 设置为true(或{name: “manifest”}),便能将入口模块中的runtime部分提取出来。
- 由 optimization.noEmitOnErrors 替代NoEmitOnErrorsPlugin,生产环境默认开启。
- 由 optimization.namedModules 替代NamedModulesPlugin,生产环境默认开启。
- 由 optimization.concatenateModules 替代ModuleConcatenationPlugin,生产环境默认开启。
- 由 optimization.minimize 替代optimize.UglifyJsPlugin ,生产环境默认开启。
optimization: {
minimize: env === 'production' ? true : false, // 开发环境不压缩
splitChunks: {
chunks: "async", // 共有三个值可选:initial(初始模块)、async(按需加载模块)和all(全部模块)
minSize: 30000, // 模块超过30k自动被抽离成公共模块
minChunks: 1, // 模块被引用>=1次,便分割
maxAsyncRequests: 5, // 异步加载chunk的并发请求数量<=5
maxInitialRequests: 3, // 一个入口并发加载的chunk数量<=3
name: true, // 默认由模块名+hash命名,名称相同时多个模块将合并为1个,可以设置为function
automaticNameDelimiter: '~', // 命名分隔符
cacheGroups: { // 缓存组,会继承和覆盖splitChunks的配置
default: { // 模块缓存规则,设置为false,默认缓存组将禁用
minChunks: 2, // 模块被引用>=2次,拆分至vendors公共模块
priority: -20, // 优先级
reuseExistingChunk: true, // 默认使用已有的模块
},
vendors: {
test: /[\/]node_modules[\/]/, // 表示默认拆分node_modules中的模块
priority: -10
}
}
}
}
避坑
- mini-css-extract-plugin 替代 extract-text-webpack-plugin 做css打包
- 优点
- 异步加载
- 不重复编译,性能更好
- 更容易使用
- 缺点:不支持css热更新,需在开发环境引入 css-hot-loader
- 优点
- 使用 optimize-css-assets-webpack-plugin做生产环境css的优化压缩
性能优化
- 缩小编译范围,减少不必要的编译工作。即 modules、mainFields、noParse、includes、exclude、alias用起来。
const resolve = dir => path.join(__dirname, '..', dir);
// ...
resolve: {
modules: [ // 指定以下目录寻找第三方模块,避免webpack往父级目录递归搜索
resolve('src'),
resolve('node_modules'),
resolve(config.common.layoutPath)
],
mainFields: ['main'], // 只采用main字段作为入口文件描述字段,减少搜索步骤
alias: {
vue$: "vue/dist/vue.common",
"@": resolve("src") // 缓存src目录为@符号,避免重复寻址
}
},
module: {
noParse: /jquery|lodash/, // 忽略未采用模块化的文件,因此jquery或lodash将不会被下面的loaders解析
// noParse: function(content) {
// return /jquery|lodash/.test(content)
// },
rules: [
{
test: /.js$/,
include: [ // 表示只解析以下目录,减少loader处理范围
resolve("src"),
resolve(config.common.layoutPath)
],
exclude: file => /test/.test(file), // 排除test目录文件
loader: "happypack/loader?id=happy-babel" // 后面会介绍
},
]
}
- 使用happypack提升loader的解析速度。使用子进程实现并发编译。
- 使用 webpack-parallel-uglify-plugin 插件提升AST还原成js速度。原理调用node=>fork子进程,把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程,从而实现并发编译,进而大幅提升js压缩速度。
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
// ...
optimization: {
minimizer: [
new ParallelUglifyPlugin({ // 多进程压缩
cacheDir: '.cache/',
uglifyJS: {
output: {
comments: false,
beautify: false
},
compress: {
warnings: false,
drop_console: true,
collapse_vars: true,
reduce_vars: true
}
}
}),
]
}
- 使用 DLLPlugin 和 DLLReferencePlugin 插件提前打包一些基本框架代码,不需每次编译都要加载一遍。比如:babel-polyfill、vue、vue-router、vuex、axios、element-ui、fastclick 等。
// webpack.dll.config.js
const webpack = require("webpack");
const path = require('path');
const CleanWebpackPlugin = require("clean-webpack-plugin");
const dllPath = path.resolve(__dirname, "../src/assets/dll"); // dll文件存放的目录
module.exports = {
entry: {
// 把 vue 相关模块的放到一个单独的动态链接库
vue: ["babel-polyfill", "fastclick", "vue", "vue-router", "vuex", "axios", "element-ui"]
},
output: {
filename: "[name]-[hash].dll.js", // 生成vue.dll.js
path: dllPath,
library: "_dll_[name]"
},
plugins: [
new CleanWebpackPlugin(["*.js"], { // 清除之前的dll文件
root: dllPath,
}),
new webpack.DllPlugin({
name: "_dll_[name]",
// manifest.json 描述动态链接库包含了哪些内容
path: path.join(__dirname, "./", "[name].dll.manifest.json")
}),
],
};
// webpack.config.js
externals: {
'vue': 'Vue',
'vue-router': 'VueRouter',
'vuex': 'vuex',
'elemenct-ui': 'ELEMENT',
'axios': 'axios',
'fastclick': 'FastClick'
},
plugins: [
...(config.common.needDll ? [
new webpack.DllReferencePlugin({
manifest: require("./vue.dll.manifest.json")
})
] : [])
]
// package.json
"scripts": {
"dll": "webpack --mode production --config build/webpack.dll.config.js"
}
- add-asset-html-webpack-plugin 插件帮我们自动引入 dll 文件。避免无法找到更新后的dll文件(新dll文件会加上新的hash)
//webpack.config.js
const autoAddDllRes = () => {
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
return new AddAssetHtmlPlugin([{ // 往html中注入dll js
publicPath: config.common.publicPath + "dll/", // 注入到html中的路径
outputPath: "dll", // 最终输出的目录
filepath: resolve("src/assets/dll/*.js"),
includeSourcemap: false,
typeOfAsset: "js" // options js、css; default js
}]);
};
// ...
plugins: [
...(config.common.needDll ? [autoAddDllRes()] : [])
]
webpack4 => webpack5
特性
- 剔除Node.js Polyfills
- 缓存
-
浏览器长期缓存持久化:v4是根据代码的结构生成chunkhash,现在v5根据完全内容生成chunkhash,改了内容的注释或者变量则不会引起chunkhash的变化,让浏览器继续使用缓存
- moduleId改为根据上下文模块路径计算,chunkId根据chunk内容计算,为module,chunk 分配确定的(3或5位)数字ID,这是包大小和长期缓存之间的一种权衡
-
打包持久化缓存
- 第一次构建是一次全量构建,它会利用磁盘模块缓存(以空间换时间),使得后续的构建从中获利。
- 后续构建具体流程是:读取磁盘缓存 -> 校验模块 -> 解封模块内容。
-
// webpack.config.js
module.exports = {
cache: {
// 1. 将缓存类型设置为文件系统
type: 'filesystem', // 默认是memory
// 2. 将缓存文件夹命名为 .temp_cache,
// 默认路径是 node_modules/.cache/webpack
cacheDirectory: path.resolve(__dirname, '.temp_cache')
}
}
-
更好的Tree Shacking
- 不仅仅支持ES module,v5开始支持commonjs规范的模块
- 可标记更多没有使用的导出项
-
模板联邦——模块共享
- 说明:直接将一个应用的包应用于另一个应用,同时具备整体应用一起打包的公共依赖抽取能力。
- 精读
webpack打包结果分析
开发环境和生产环境都引入 webpack-bundle-analyzer 插件