代码分离 | Code Splitting
Code Splitting
本指南扩展了入门和输出管理中提供的示例。请确保您至少熟悉其中提供的示例。
代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
有三种常用的代码分离方法:
- 入口点:使用
entry
配置手动分割代码。
- 防止复制:使用
CommonsChunkPlugin
去重和分离 chunk。
- 动态导入:通过模块的内联函数调用来分离代码。
入口点
这是迄今为止最简单、最直观的分离代码的方式。不过,这种方式手动配置较多,并有一些陷阱,我们将会解决这些问题。先来看看如何从 main bundle 中分离另一个模块:
项目
webpack-demo
|- package.json
|- webpack.config.js
|- /dist
|- /src
|- index.js
+ |- another-module.js
|- /node_modules
another-module.js
import _ from 'lodash';
console.log(
_.join(['Another', 'module', 'loaded!'], ' ')
webpack.config.js
const path = require('path'
const HTMLWebpackPlugin = require('html-webpack-plugin'
module.exports = {
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
plugins: [
new HTMLWebpackPlugin{
title: 'Code Splitting'
})
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
这将产生以下生成结果:
Hash: 309402710a14167f42a8
Version: webpack 2.6.1
Time: 570ms
Asset Size Chunks Chunk Names
index.bundle.js 544 kB 0 [emitted] [big] index
another.bundle.js 544 kB 1 [emitted] [big] another
[0] ./~/lodash/lodash.js 540 kB {0} {1} [built]
[1] (webpack)/buildin/global.js 509 bytes {0} {1} [built]
[2] (webpack)/buildin/module.js 517 bytes {0} {1} [built]
[3] ./src/another-module.js 87 bytes {1} [built]
[4] ./src/index.js 216 bytes {0} [built]
如上所述,这种方法存在一些缺陷:
- 如果入口 chunks 之间包含重复的模块,那些重复模块都会被引入到各个 bundle 中。
- 这种方法不够灵活,并且不能将核心应用程序逻辑进行动态拆分代码。
以上两点中,第一点对我们的示例来说无疑是个问题,因为之前我们在 ./src/index.js
中也引入过 lodash
,这样就在两个 bundle 中造成重复引用。接着,我们通过使用 CommonsChunkPlugin
来移除重复的模块。
防止复制
CommonsChunkPlugin
插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。让我们使用这个插件,将之前的示例中重复的 lodash
模块去除:
webpack.config.js
const path = require('path'
+ const webpack = require('webpack'
const HTMLWebpackPlugin = require('html-webpack-plugin'
module.exports = {
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
plugins: [
new HTMLWebpackPlugin{
title: 'Code Splitting'
- })
+ }),
+ new webpack.optimize.CommonsChunkPlugin{
+ name: 'common' // Specify the common bundle's name.
+ })
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
这里我们使用 CommonsChunkPlugin
之后,现在应该可以看出,index.bundle.js
中已经移除了重复的依赖模块。需要注意的是,CommonsChunkPlugin
插件将 lodash
分离到单独的 chunk,并且将其从 main bundle 中移除,减轻了大小。执行 npm run build
查看效果:
Hash: 70a59f8d46ff12575481
Version: webpack 2.6.1
Time: 510ms
Asset Size Chunks Chunk Names
index.bundle.js 665 bytes 0 [emitted] index
another.bundle.js 537 bytes 1 [emitted] another
common.bundle.js 547 kB 2 [emitted] [big] common
[0] ./~/lodash/lodash.js 540 kB {2} [built]
[1] (webpack)/buildin/global.js 509 bytes {2} [built]
[2] (webpack)/buildin/module.js 517 bytes {2} [built]
[3] ./src/another-module.js 87 bytes {1} [built]
[4] ./src/index.js 216 bytes {0} [built]
以下是由社区提供的,一些对于代码分离很有帮助的插件和 loaders:
ExtractTextPlugin
:用于将CSS从主应用程序中分离出来。
bundle-loader
:用于分离代码和延迟加载生成的 bundle。
promise-loader
:类似于bundle-loader
,但是使用的是 promises。
CommonsChunkPlugin
插件还可以通过使用显式的 vendor chunks 功能,从应用程序代码中分离 vendor 模块。
动态导入
当涉及到动态代码拆分时,webpack 提供了两个类似的技术。对于动态导入,第一种,也是优先选择的方式是,使用符合 ECMAScript 提案 的 import()
语法。第二种,则是使用 webpack 特定的 require.ensure
。让我们先尝试使用第一种……
import()
调用会在内部用到 promises。如果在旧有版本浏览器中使用 import()
,记得使用 一个 polyfill 库(例如 es6-promise 或 promise-polyfill),来 shim Promise
。
在我们开始本节之前,先从配置中移除掉多余的 entry
和 CommonsChunkPlugin
,因为接下来的演示中并不需要它们:
webpack.config.js
const path = require('path'
- const webpack = require('webpack'
const HTMLWebpackPlugin = require('html-webpack-plugin'
module.exports = {
entry: {
+ index: './src/index.js'
- index: './src/index.js',
- another: './src/another-module.js'
},
plugins: [
new HTMLWebpackPlugin{
title: 'Code Splitting'
- }),
+ })
- new webpack.optimize.CommonsChunkPlugin{
- name: 'common' // Specify the common bundle's name.
- })
],
output: {
filename: '[name].bundle.js',
+ chunkFilename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
注意,这里使用了 chunkFilename
,它决定非入口 chunk 的名称。想了解 chunkFilename
更多信息,请查看 output 相关文档。接着,更新我们的项目,移除掉那些现在不会用到的文件:
项目
webpack-demo
|- package.json
|- webpack.config.js
|- /dist
|- /src
|- index.js
- |- another-module.js
|- /node_modules
现在,我们不再使用静态导入 lodash
,而是通过使用动态导入来分离一个 chunk:
src/index.js
- import _ from 'lodash';
-
- function component() {
+ function getComponent() {
- var element = document.createElement('div'
-
- // Lodash, now imported by this script
- element.innerHTML = _.join(['Hello', 'webpack'], ' '
+ return import(/* webpackChunkName: "lodash" */ 'lodash').then(_ => {
+ var element = document.createElement('div'
+
+ element.innerHTML = _.join(['Hello', 'webpack'], ' '
+
+ return element;
+
+ }).catch(error => 'An error occurred while loading the component'
}
- document.body.appendChild(component()
+ getComponent().then(component => {
+ document.body.appendChild(component
+ })
注意,在注释中使用了 webpackChunkName
。这样做会导致我们的 bundle 被命名为 lodash.bundle.js
,而不是 [id].bundle.js
。想了解更多关于 webpackChunkName
和其他可用选项,请查看 import()
相关文档。让我们执行 webpack,查看 lodash
是否会分离到一个单独的 bundle:
Hash: a27e5bf1dd73c675d5c9
Version: webpack 2.6.1
Time: 544ms
Asset Size Chunks Chunk Names
lodash.bundle.js 541 kB 0 [emitted] [big] lodash
index.bundle.js 6.35 kB 1 [emitted] index
[0] ./~/lodash/lodash.js 540 kB {0} [built]
[1] ./src/index.js 377 bytes {1} [built]
[2] (webpack)/buildin/global.js 509 bytes {0} [built]
[3] (webpack)/buildin/module.js 517 bytes {0} [built]
由于 import()
会返回一个 promise,因此它可以和 async
函数一起使用。但是,需要使用像 Babel 这样的预处理器和Syntax Dynamic Import Babel Plugin。下面是如何通过 async
函数简化代码:
src/index.js
- function getComponent() {
+ async function getComponent() {
- return import(/* webpackChunkName: "lodash" */ 'lodash').then(_ => {
- var element = document.createElement('div'
-
- element.innerHTML = _.join(['Hello', 'webpack'], ' '
-
- return element;
-
- }).catch(error => 'An error occurred while loading the component'
+ var element = document.createElement('div'
+ const _ = await import(/* webpackChunkName: "lodash" */ 'lodash'
+
+ element.innerHTML = _.join(['Hello', 'webpack'], ' '
+
+ return element;
}
getComponent().then(component => {
document.body.appendChild(component
}
bundle 分析
如果我们以分离代码作为开始,那么就以检查模块作为结束,分析输出结果是很有用处的。官方分析工具 是一个好的初始选择。下面是一些社区支持(community-supported)的可选工具:
- webpack-chart:用于webpack统计的交互式饼图。
- webpack-visualizer:可视化并分析你的 bundle,检查哪些模块占用空间,哪些可能是重复使用的。
- webpack-bundle-analyzer:一款分析 bundle 内容的插件及 CLI 工具,以便捷的、交互式、可缩放的树状图形式展现给用户。
下一步
关于「如何在真正的应用程序和缓存中 import()
导入」以及学习「如何更加高效地分离代码」的具体示例,请查看懒加载。