代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle、控制资源加载优先级,如果使用合理,会极大减小加载时间。
入口起点
这是迄今为止最简单直观的分离代码的方式。但是不够灵活,使用场景有限。
通过配置多个入口文件,打包之后也会对应多个输出 bundle
// 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"),
clean: true,
},
};
// src/index.js
import _ from "lodash";
function component() {
const element = document.createElement("div");
element.innerHTML = _.join(["Hello", "webpack"], " ");
return element;
}
document.body.appendChild(component());
// src/another-module.js
import _ from "lodash";
console.log(_.join(["Another", "module", "loaded!"], " "));
打包结果:
可以看到,输出的两个 bundle 中都引用 lodash。这就是重复引用。
防止重复
如何避免重复引用,通过下面两种方法可以做到:
1. 入口依赖
在配置文件中配置 dependOn 选项,以在多个 chunk 之间共享模块:
const path = require("path");
module.exports = {
mode: "development",
entry: {
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"),
clean: true,
},
};
打包结果:
可以看到,lodash 这个公共模块被打包到了一个单独的 bundle 文件。
2. SplitChunksPlugin
SplitChunksPlugin 插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。这是一个内置插件。
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"),
clean: true,
},
optimization: {
splitChunks: {
// webpack 使用 chunk 的来源和名称生成名称(例如 vendors~main.js)。该选项可以指定生成名称的分隔符。默认值:'~'
automaticNameDelimiter: "~",
// 对哪些chunk 进行优化。可选值:all,async 和 initial
chunks: "all",
// 按需加载时最大并行请求数,默认 30
maxAsyncRequests: 30,
// 最少被多少个 chunk 引用才会拆分,默认 1
minChunks: 1,
// 生成 chunk 的最小体积(以 bytes 为单位),默认是 20000
minSize: 20000,
},
},
};
打包结果:
动态导入
webpack 提供了两个类似的技术实现动态拆分代码。
- 第一种(推荐),使用符合 ECMAScript 提案 的
import() 语法 - 第二种,是 webpack 的遗留功能,使用 webpack 特定的
require.ensure。
调用
import()会在内部使用 promise。因此如果在旧版本浏览器中(例如,IE 11)使用 import(),需要使用一个polyfill库(例如es6-promise或promise-polyfill)来 shim Promise。
// webpack.config.js
const path = require("path");
module.exports = {
mode: "development",
entry: {
index: "./src/index.js",
},
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, "dist"),
clean: true,
},
};
// src/index.js
function getComponent() {
return import("lodash")
.then(({ default: _ }) => {
const element = document.createElement("div");
element.innerHTML = _.join(["Hello", "webpack"], " ");
return element;
})
.catch((error) => "An error occurred while loading the component");
}
getComponent().then((component) => {
document.body.appendChild(component);
});
打包结果:
预获取/预加载模块
Webpack v4.6.0+ 增加了对预获取(prefetch)和预加载(preload)的支持。
在声明 import 时,使用下面这些内置指令,可以让 webpack 输出“resource hint”,来告知浏览器:
- prefetch(预获取):将来某些导航下可能需要的资源
- preload(预加载):当前导航下可能需要资源
import(/* webpackPrefetch: true */ "./path/to/LoginModal.js");
这会生成 <link rel="prefetch" href="login-modal-chunk.js"> 并追加到页面头部,指示浏览器在闲置时间预取 login-modal-chunk.js 文件。
只要父 chunk 完成加载,webpack 就会添加预获取提示。
与预获取指令相比,预加载指令有许多不同之处:
- 预加载 chunk 会在父 chunk 加载时,以并行方式开始加载。预加载 chunk 会在父 chunk 加载结束后开始加载。
- 预加载 chunk 具有中等优先级,并立即下载。预加载 chunk 在浏览器闲置时下载。
- 预加载 chunk 会在父 chunk 中立即请求,用于当下时刻。预加载 chunk 会用于未来的某个时刻。
- 浏览器支持程度不同。
import(/* webpackPreload: true */ "ChartingLibrary");
通过 <link rel="preload"> 请求 charting-library-chunk