# 代码分割
安装一个 lodash 插件
yarn add lodash
在 index.js 使用 lodash
import _ from "lodash"
console.log(_.join([1, 2, 3], "***"));
2
执行打包,发现我们的 app.js 有 1.39 MiB
很明显,我们什么都没有写,仅仅只有几行代码,下次如果更新,我们的 app.js 会再次打包也是这么大,这肯定不如我们所愿.
# 拆分 js
新建 lodash.js 并移除 index.js lodash 的引入
import _ from "lodash";
window._ = _;
2
修改 webpack 配置 entry
entry: {
lodash: './src/lodash.js',
app: './src/index.js',
}
2
3
4
再次执行打包,发现 dist 目录下生成 app.js 和 lodash.js, 这是我们的 app.js 只有 31.8kb, lodash.js 有 1.39m
lodash 作为一个库文件肯定是不会经常变更的。对于我们的业务代码 app.js 来说,如果下次更新了,用户只需要拉去最新的 app.js 代码即可, lodash 则根据浏览器的强缓存 而不需要再次请求服务器,大大的提升了性能
# 使用 splitChunks 自动拆分
上面的代码分割是我们自己手动进行的,其实 webpack splitChunks 会自动进行,下面我们来进行配置下.
删除 lodash.js,index.js 还是正常引入 lodash,修改下 webpack 配置
entry:'./src/index.js',
...
optimization: {
splitChunks: {
chunks: 'all'
}
},
2
3
4
5
6
7
再次执行打包,发现 webpack 打包出来了一个 vendor-main.js 自动帮我们进行了代码分割
# SplitChunksPlugin
默认配置如下:
splitChunks: {
chunks: "async",
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- async 只会对异步才进行代码分割,对同步无效,需要设置为 all
- cacheGroups 缓存组 抽取公共模块那个地方的
- priority 优先级权重
cacheGroups: {
vendor: {
name: "vendor",
test: /[\\/]node_modules[\\/]/,
chunks: "all",
priority: 10 // 优先级
},
common: {
name: "common",
test: /[\\/]src[\\/]/,
minSize: 1024,
chunks: "all",
priority: 5
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
如上配置了两个缓存组 vendor 和 common
- vendor: 抽取来自 node_modules 文件夹下的第三方代码,优先级权重为 10
- common: 抽取来自 src 文件夹下的代码,优先级权重为 5
- vendor 比 common 的优先级权重高,所以先提取 vendor,再提取 common
# chunks 为什么默认是 async
我们现在需要做这样一个需求,点击 body 创建一个 div
import _ from "lodash";
function getComponent() {
var element = document.createElement("div");
element.innerHTML = _.join([1, 2, 3], "***");
return element;
}
document.addEventListener("click", () => {
const component = getComponent();
document.body.appendChild(component);
});
2
3
4
5
6
7
8
9
10
讨论这个问题之前,我们首先应该检查一下 页面代码的 Coverage, 在 Chorem 中 找到 Coverage 进行页面录制,发现页面代码使用率

很明显 getComponent 里面的代码在首次页面渲染得时候根本用不上,因此代码使用率不高,我们懒加载再来测试一下
document.addEventListener("click", () => {
import(/* webpackChunkName: "click" */ "./click.js").then(
({ default: func }) => {
const component = func();
document.body.appendChild(component);
},
);
});
2
3
4
5
6
7
8
click.js
import _ from "lodash";
function getComponent() {
var element = document.createElement("div");
element.innerHTML = _.join([1, 2, 3], "***");
return element;
}
export default getComponent;
2
3
4
5
6
7
8
执行打包 发现报错,提示不识别 /_ webpackChunkName: "click" _/ 这样得写法,安装插件
npm install --save-dev @babel/plugin-syntax-dynamic-import
配置 .babelrc
"plugins": ["@babel/plugin-syntax-dynamic-import"]

打开 Network 我们发现 首次只加载了 main.js 执行点击后分别加载了 vendors.js 和 click.js
webpack 希望我们是使用异步懒加载来进行编程,因此 chunks 默认 也就是 async. 对于 webpack 来说 同步提取意义也不是很大
# 利用 prefetch 进行优化
上面我们虽然对代码进行了分割懒加载,我们在点击得时候才会去加载对应得 js 文件,那么这个时候就会给人一种反应 很慢得感觉。 那能不能在进入首页后,在浏览器的空闲时间提前下好我们页面需要的 js 文件呢
webpack 给我们提供了 prefetch ,会在浏览器空闲时,下载相应文件,我们再次修改下代码
import(/* webpackPrefetch: true */ './click.js')
# 对 CSS 进行代码分割
安装插件
npm install --save-dev mini-css-extract-plugin
配置 webpack.prod.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
}
plugins: [new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
})],
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
在 index.js 中引入 index.css 和 index1.css 执行 prod 打包。发现 会生成一个 main.css
# Production 压缩
webpack5 可能会内置 CSS 压缩器,webpack4 需要自己使用压缩器,可以使用 optimize-css-assets-webpack-plugin 插件
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
optimization: {
minimizer: [
new OptimizeCSSAssetsPlugin({}),
],
},
2
3
4
5
6
7
再次打包 发现 css 文件被压缩合并了
# 把 CSS 提取到一个文件
可以利用 splitChunks.cacheGroups 将 css 提取到一个 CSS 中
optimization: {
...
splitChunks: {
cacheGroups: {
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true,
},
},
},
}
2
3
4
5
6
7
8
9
10
11
12
13
# 根据 entry 提取 CSS
可以根据 webpack 的 entry name 来提取 CSS,这对动态引入路由,并且动态加载对于的 css 这种情况十分有用.
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
function recursiveIssuer(m) {
if (m.issuer) {
return recursiveIssuer(m.issuer);
} else if (m.name) {
return m.name;
} else {
return false;
}
}
module.exports = {
entry: {
foo: path.resolve(__dirname, "src/foo"),
bar: path.resolve(__dirname, "src/bar"),
},
optimization: {
splitChunks: {
cacheGroups: {
fooStyles: {
name: "foo",
test: (m, c, entry = "foo") =>
m.constructor.name === "CssModule" && recursiveIssuer(m) === entry,
chunks: "all",
enforce: true,
},
barStyles: {
name: "bar",
test: (m, c, entry = "bar") =>
m.constructor.name === "CssModule" && recursiveIssuer(m) === entry,
chunks: "all",
enforce: true,
},
},
},
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
],
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# shimming
shimming 垫片的使用
//现在有两个JS文件:
//一个index.js,内容如下
import _ from 'lodash';
import $ from 'jquery';
import {ui} from './jquery-ui';
ui();
/**其他业务代码*/
//一个我们模仿的第三方模块jqurey-ui.js文件:
import $ from 'jquery';
export function ui() {
$('body').css('background', 'red');
}
两个文件的关系:jqurey-ui.js是一个第三方模块,jqurey-ui.js模块依赖于jQuery,
但是这个模块中并没有导入jQuery;我们在index.js中导入了这个模块,
并且在index.js中我们还引入了lodash模块和jquery模块;
此时,我们去执行导入的UI函数可以运行吗?
答案是不可以执行,会报错,这是因为前段模块化存在变量隔离的原因,
在index.js中导入的jQuery并不能在jquery-ui.js中生效;
也就是本模块导入的变量、类、函数,只在本模块生效,不会对导入模块生效;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
这个时候我们可以用到 webpack.ProvidePlugin
const webpack = require("webpack");
plugins: [
new webpack.ProvidePlugin({
_: "lodash",
}),
];
2
3
4
5
6
# 使用 imports-loader 实现垫片
现在我们有这样一个场景,想让所有的 this 都指向 windows 这个时候可以使用 imports-loader
npm install imports-loader --save-dev
webpack 配置
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{ loader: "babel-loader" },
{
loader: "imports-loader? ",
options: "this=>window"
}
],
}
2
3
4
5
6
7
8
9
10
11