Code Split
把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
常用的代码分离方法有三种:
入口起点: 在 entry
中手动分离(见 output.md)
防止重复:使用 Entry dependencies
或 SplitChunksPppplugin
去重分离
入口起点
见 ,这种方法存在一些隐患:
如果 entry 中多个 chunk 包含同一个模块,那这个模块会被重复引入
不够灵活,不能动态地将核心应用逻辑中的代码拆分出来
防止重复
入口依赖
略,详见:
SplitChunksPlugin
将公共的依赖模块提取出来
webpack.config.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
},
},
}
假设 index.js 和 print.js 都引入了 lodash,打包后,第三方库 lodash 已经被单独提取出来
asset index.0c57e77abda314a84ed1.js 17.8 KiB
asset print.16746bd586ccb72489d3.js 17.4 KiB
vendors-node_modules_lodash_lodash_js.196895f8828735c59bdd.js 1.37 MiB
对比不提取时候的体积
asset index.2374a1266dfc8e3e369f.js 1.38 MiB
asset print.fd5a45164084432521fd.js 1.38 MiB
动态导入
使用 import()
webpack.config.js
module.exports = {
entry: './src/index.js'
}
index.js
async function component() {
const element = document.createElement('div')
const { default: _ } = await import('lodash') // 使用 Promise 动态的 import
element.innerHTML = _.join(['Hello', 'Webpack'], ' ')
const btn = document.createElement('button')
return element
}
component().then(component => document.body.appendChild(component))
预获取/预加载模块
Cache
若打包的 bundle 文件名不改变,浏览器就会命中缓存
修改输出的文件名
使用文件内容的 hash 值来命名 bundle,使得在 bundle 不变时,hash 值也不改变
webpack.config.js
output: {
filename: '[name].[contenthash].js'
},
输出的文件:
asset main.f4fb0c69a973e7ecc535.js 1.38 MiB [emitted] [immutable] (name: main)
💡 然而这样的方法并不能完全确保在文件内容不改变时保持 hash 值不变,因为入口 chunk 中可能包含了某些引导模板
使用 SplitChunksPlugin
可以将模块分离到单独的 bundle 中。利用 optimization.runtimeChunk
将 runtime 代码拆分为一个单独的 chunk
webpack.config.js
module.exports = {
entry: './src/index.js',
optimization: {
runtimeChunk: 'single',
},
};
构建后,runtime 代码已经被提取出来
asset runtime.bf1d01aa56a316de9c1b.js 15.5 KiB [emitted] [immutable] (name: runtime)
接着,我们还可以将第三方库(如 react、lodash)的代码提取到单独的 vendor
chunk 中
webpack.config.js
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all'
}
}
再次构建,不仅 runtime 代码被提取出来,第三方库也被提取了出来
asset runtime.bf1d01aa56a316de9c1b.js 15.5 KiB
asset index.217c743aea23034fef65.js 1.98 KiB
asset print.fce4bfb7ed7994ea07c8.js 1.67 KiB
vendors-node_modules_lodash_lodash_js.196895f8828735c59bdd.js 1.37 MiB
模块标识符*
vendor
bundle 会随着自身的 module.id
的变化,而发生变化,但这不符合需求
optimization: {
moduleIds: 'deterministic'
}
Tree Shaking
Tree Shaking 是一种通过消除最终文件中未使用的代码来优化体积的方法。
你可以将应用程序想象成一棵树。绿色表示实际用到的 source code(源码) 和 library(库),是树上活的树叶。灰色表示未引用代码,是秋天树上枯萎的树叶。为了除去死去的树叶,你必须摇动这棵树,使它们落下。
webpack.config.js
optimization: {
usedExports: true
}
math.js
export function square(x) { // 不会被调用的函数
return x * x;
}
export function cube(x) {
return x * x * x;
}
bundle.js
// 没有被引用到的函数会被打上注释标签(development环境下)
/* unused harmony export square */
function square(x) {
return x * x;
}
side-effect-free
side-effect-free
即 没有副作用
,是指 tree shaking 不会错误地把代码给剔除掉。但是真实中的项目不是所有的代码都是这么的纯粹,是 side-effect-free
的。
因此,要在 package.json 中,指定那些不纯粹
即不是side-effect-free
的文件
"sideEffects": ["xxx.js", "*.css"] // css 文件就是 none-side-effect-free 的
当然,如果所有的文件都是 side-effect-free
,那么在 package.json 中直接指定
最后,要将 mode 切换为 production,才会真正的将不需要的代码给剔除
总结
必须依赖 ES6 的 import
export
特性
确保编译器没有将 ES6 转换为 CommonJS
lazy load
button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => {
const print = module.default;
print();
}