此特性能够把代码分离到不同的 bundle 中,然后便能按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle、控制资源加载优先级,如果使用合理,会极大减小加载时间。代码分离是 webpack 中最引人注目的特性之一。
常用的代码分离方法有三种:
- 入口起点:使用
entry配置手动地分离代码。 - 防止重复:使用 入口依赖 或者
SplitChunksPlugin去重和分离 chunk。 - 动态导入:通过模块的内联函数调用分离代码。
入口起点
这是迄今为止最简单直观的实现代码分离的方式。不过,这种方式手动配置较多,并有一些隐患。不过,我们将会介绍如何解决这些隐患。先来看看如何从 main bundle 中分离另一个模块: webpack.config.js
const path = require('path');
module.exports = {
- entry: './src/index.js',
+ mode: 'development',
+ entry: {
+ index: './src/index.js',
+ another: './src/another-module.js',
+ },
output: {
- filename: 'main.js',
+ filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
正如前面所提及,这种方式存在一些隐患:
- 如果入口 chunk 之间包含一些重复的模块,那么这些重复模块会被引入到各个 bundle 中。
- 这种方法不够灵活,并且不能动态地拆分应用程序逻辑中的核心代码。
以上两点中,第一点所对应的问题已经在我们上面的实例中体现出来了。除了 ./src/another-module.js,我们也曾在 ./src/index.js 中引入过 lodash,这就导致了重复引用。下一章节会介绍如何移除重复的模块。
防止重复
入口依赖
在配置文件中配置 dependOn 选项,以在多个 chunk 之间共享模块:
webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',
entry: {
- index: './src/index.js',
- another: './src/another-module.js',
+ index: {
+ import: './src/index.js',
+ dependOn: 'shared',
+ },
+ another: {
+ import: './src/another-module.js',
+ dependOn: 'shared',
+ },
+ shared: 'lodash',
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
如果想要在一个 HTML 页面上使用多个入口起点,还需设置 optimization.runtimeChunk: 'single',否则会遇到 此处 所述的麻烦。
SplitChunksPlugin
SplitChunksPlugin 插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。让我们使用这个插件去除之前示例中重复的 lodash 模块:
webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',
entry: {
index: './src/index.js',
another: './src/another-module.js',
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
+ optimization: {
+ splitChunks: {
+ chunks: 'all',
+ },
+ },
};
以下是由社区提供,对代码分离很有帮助的 plugin 和 loader:
mini-css-extract-plugin:用于将 CSS 从主应用程序中分离。
动态导入
webpack 提供了两个类似的技术实现动态代码分离。第一种,也是推荐选择的方式,是使用符合 ECMAScript 提案 的 import() 语法 实现动态导入。第二种则是 webpack 的遗留功能,使用 webpack 特定的 require.ensure。让我们先尝试使用第一种。
现在,我们不再静态导入 lodash,而是通过动态导入来分离出一个 chunk:
src/index.js
-import _ from 'lodash';
-
-function component() {
+function getComponent() {
- const element = document.createElement('div');
- // lodash 现在使用 import 引入
- element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+ return import('lodash')
+ .then(({ default: _ }) => {
+ const element = document.createElement('div');
+
+ element.innerHTML = _.join(['Hello', 'webpack'], ' ');
- return element;
+ return element;
+ })
+ .catch((error) => 'An error occurred while loading the component');
}
-document.body.appendChild(component());
+getComponent().then((component) => {
+ document.body.appendChild(component);
+});
预获取/预加载模块
- 预获取(prefetch) :将来某些导航下可能需要的资源
- 预加载(preload) :当前导航下可能需要资源
与预获取指令相比,预加载指令有许多不同之处:
- 预加载 chunk 会在父 chunk 加载时以并行方式开始加载;而预获取 chunk 会在父 chunk 加载结束后开始加载。
- 预加载 chunk 具有中等优先级,并会立即下载;而预获取 chunk 则在浏览器闲置时下载。
- 预加载 chunk 会在父 chunk 中立即请求,用于当下时刻;而预获取 chunk 则用于未来的某个时刻。
- 浏览器支持程度不同。
分析 bundle
一旦开始分离代码,一件很有帮助的事情是,分析输出结果来检查模块在何处结束。官方分析工具 是一个不错的开始。还有一些其他社区支持的可选项:
- webpack-chart:webpack stats 可交互饼图。
- webpack-visualizer:分析并可视化 bundle,检查哪些模块占用空间,哪些可能是重复使用的。
- webpack-bundle-analyzer:一个 plugin 和 CLI 工具,它将 bundle 内容展示为一个便捷的、交互式、可缩放的树状图形式。
- webpack bundle optimize helper:这个工具会分析 bundle,并提供可操作的改进措施,以减少 bundle 的大小。
- bundle-stats:生成一个 bundle 报告(bundle 大小、资源、模块),并比较不同构建之间的结果。