代码分离是 webpack 中最引人注目的特性之一。
代码分离能把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影提高项目的性能。
代码分离的三种方法:
配置多个入口是最简单、最直观的分离代码的方式。不过,这种方式手动配置较多,并有一些陷阱,我们将会解决这些问题。
src/index.js 文件,内容如下:
import _ from 'lodash';
function component() {
var 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!'], ' ')
);
确保 package.json 文件中,存在下面的 npm script 内容:
"dev": "webpack --mode development --config webpack.config.js"
修改 webpack.config.js 文件,以便配置多个入口(entry):
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: './src/index.js',
vendor: './src/another-module.js'
},
plugins: [
new HTMLWebpackPlugin({
title: 'Code Splitting--代码分离'
}),
new CleanWebpackPlugin(['dist'])
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
打开终端,切换到项目目录,运行命令:
npm run dev
这将生成如下构建结果:
Hash: 7ce7198831b386f081df
Version: webpack 4.0.0
Time: 606ms
Built at: 2018-3-3 14:43:02
Asset Size Chunks Chunk Names
index.bundle.js 550 KiB index [emitted] [big] index
vendor.bundle.js 550 KiB vendor [emitted] [big] vendor
index.html 268 bytes [emitted]
Entrypoint index [big] = index.bundle.js
Entrypoint vendor [big] = vendor.bundle.js
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 509 bytes {vendor} {index} [built]
[./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 519 bytes {vendor} {index} [built]
[./src/another-module.js] 94 bytes {vendor} [built]
[./src/index.js] 226 bytes {index} [built]
虽然,现在确实生成了两个bundle(index.bundle.js 和 vendor.bundle.js),但却存在两个问题:
第一点对我们的示例来说是个大问题,因为之前我们在 src/index.js 中也引入过 lodash,这样生成的两个 bundle 中就会有重复。
下面,通过使用 CommonsChunkPlugin 来移除重复的模块。
CommonsChunkPlugin 插件可以将公共的依赖模块提取到已有的 chunk 中,或者提取到一个新生成的 chunk。
让我们使用这个插件,将上面示例中重复的 lodash 模块去除。
修改 webpack.config.js 配置文件:
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: './src/index.js',
vendor: './src/another-module.js'
},
plugins: [
new HTMLWebpackPlugin({
title: 'Code Splitting--代码分离'
}),
new CleanWebpackPlugin(['dist']),
new webpack.optimize.CommonsChunkPlugin({
name: 'common' // 指定公共 bundle 的名称。
})
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
运行命令:
npm run dev
这时,就报错了。报错信息为:
Error: webpack.optimize.CommonsChunkPlugin has been removed, please use config.optimization.splitChunks instead.
这是因为,在 webpack 4.0.0 版本,CommonsChunkPlugin 被移除了,改用 config.optimization.splitChunks 替换。
那么,重新修改 webpack.config.js 文件,内容如下:
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: './src/index.js',
vendor: './src/another-module.js'
},
plugins: [
new HTMLWebpackPlugin({
title: 'Code Splitting--代码分离'
}),
new CleanWebpackPlugin(['dist'])
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
optimization: {
splitChunks: {
// chunks: "initial", // "initial" | "all"(默认就是all) | "async"
// minSize: 0, // 最小尺寸,默认0
// minChunks: 1, // 最小 chunk ,默认1
// maxAsyncRequests: 1, // 最大异步请求数, 默认1
// maxInitialRequests : 1, // 最大初始化请求数,默认1
cacheGroups:{ // 这里开始设置缓存的 chunks
// priority: 0, // 缓存组优先级
vendor: { // key 为entry中定义的 入口名称
chunks: "initial", // "initial" | "all" | "async"(默认就是异步)
test: /react|lodash/, // 正则规则验证,如果符合就提取 chunk
name: "vendor", // 要缓存的 分隔出来的 chunk 名称
// minSize: 0,
// minChunks: 1,
enforce: true,
// maxAsyncRequests: 1, // 最大异步请求数, 默认1
// maxInitialRequests : 1, // 最大初始化请求数,默认1
// reuseExistingChunk: true // 可设置是否重用该chunk(查看源码没有发现默认值)
}
}
}
},
};
再来运行命令:
npm run dev
结果如下:
Hash: 8bb93388ae63800ea88c
Version: webpack 4.0.0
Time: 590ms
Built at: 2018-3-3 14:59:15
Asset Size Chunks Chunk Names
index.bundle.js 7.67 KiB index [emitted] index
vendor.bundle.js 547 KiB vendor [emitted] [big] vendor
index.html 268 bytes [emitted]
Entrypoint index [big] = vendor.bundle.js index.bundle.js
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 509 bytes {vendor} {index} [built]
[./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 519 bytes {vendor} {index} [built]
[./src/another-module.js] 94 bytes {vendor} [built]
[./src/index.js] 226 bytes {index} [built]
可以发现,index.bundle.js 文件的大小明显比之前小了许多。
当涉及到动态代码的拆分时,可以使用 import() 语法来实现。
先修改 webpack.config.js 配置文件:
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: './src/index.js'
},
plugins: [
new HTMLWebpackPlugin({
title: 'Code Splitting--代码分离'
}),
new CleanWebpackPlugin(['dist'])
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
chunkFilename: '[name].bundle.js' // chunkFilename 指定的是非入口 chunk 的名称
}
};
注:这里使用了 chunkFilename,它决定非入口 chunk 的名称。
现在,我们不再使用静态导入 lodash,而是通过使用动态导入来分离一个 chunk。
修改 src/index.js 文件:
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');
}
getComponent().then(component => {
document.body.appendChild(component);
})
注: 在注释中使用了 webpackChunkName。可以使非入口的 bundle 被命名为 lodash.bundle.js ,而不是 [id].bundle.js 。
执行命令:
npm run dev
结果如下:
Hash: 07e5a1638ac146171b8f
Version: webpack 4.0.0
Time: 555ms
Built at: 2018-3-3 20:10:20
Asset Size Chunks Chunk Names
index.bundle.js 7.57 KiB index [emitted] index
vendors~lodash.bundle.js 547 KiB vendors~lodash [emitted] [big] vendors~lodash
index.html 205 bytes [emitted]
Entrypoint index = index.bundle.js
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 509 bytes {vendors~lodash} [built]
[./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 519 bytes {vendors~lodash} [built]
[./src/index.js] 391 bytes {index} [built]
可以看出,lodash 被拆分到了一个单独的 bundle 。
由于 import() 会返回一个 promise,因此它可以和 async 函数一起使用。但是,需要使用像 Babel 这样的预处理器和 Syntax Dynamic Import Babel Plugin。
下面通过 async 函数简化代码,修改 src/index.js 文件:
async function getComponent() {
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);
})