深究 webpack 代码分离优化(一)
出发版本 webpack 4.26.1
阅读之前
需要你事先了解一点关于 webpack 的基本配置方法和理论知识。
准备知识
- 根据 webpack 官网介绍,第四版 webpack 已经删除掉了原 webpack.optimize.CommonsChunkPlugin,需要使用在 config 里面配置的 optimization 属性下的属性 splitChunks 替换之。所以如果你是用的是 webpack 第三版或更老的版本,请略过本文,直接搜索CommonsChunkPlugin。
- 使用 html-webpack-plugin 辅助生成 html 模板。
你可以选择参考我的文章
第一节、代码分离
我们先写打包前的源码,并梳理逻辑关系。
我们的文件分布如下
+ dist + node_modules - src app1.js app2.js app3.js app4.js index.js index2.js index3.js index.html index2.html index3.html package.json webpack.config.js
src 文件夹中 index.js 用于配合 index.html 入口; index2.js 用于配合 index2.html 入口; index3.js 用于配合 index3.html 入口。
逻辑关系如下:
//index.js
import './app1';
import './app2';
//index2.js
import './app1';
import './app2';
import './app3';
//index3.js
import './app3';
import './app4';
//app1.js
console.log("app1");
... //八千行
console.log("app1");
alert("app1");
//app2.js
console.log("app2");
... //八千行
console.log("app2");
alert("app2");
//app3.js
console.log("app3");
... //八千行
console.log("app3");
alert("app3");
//app4.js
console.log("app4");
... //八千行
console.log("app4");
alert("app4");
【解释】我们用简单的 console.log()来表示动作发生,以数量堆积来模拟一个计算量大的包,并最终用 alert 来标记执行结束。
1.默认效果
我们先不使用代码分离,设置3个入口文件并设置打包查看效果
配置 webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: {
index: "./src/index.js",
index2: "./src/index2.js",
index3: "./src/index3.js"
},
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, "dist")
},
plugins: [
new HtmlWebpackPlugin({
filename: "index.html",
template: "./index.html",
chunks: ["index"]
}),
new HtmlWebpackPlugin({
filename: "index2.html",
template: "./index2.html",
chunks: ["index2"]
}),
new HtmlWebpackPlugin({
filename: "index3.html",
template: "./index3.html",
chunks: ["index3"]
})
]
};
可以清楚滴看到,三个入口命名为 index/index2/index3 ,这将指示 webpack 至少要打包出3个文件来。并且下面的每一个 HtmlWebpackPlugin 模板都配置了相应的 chunks。 这表示的是将来生成的html文件将会依赖谁,如果不注明的话,这三个html模板都将会引入全部入口文件。
此时运行
npm run build
生成文件如下(dist文件夹内部)
index.bundle.js //296k index.html //1k index2.bundle.js //446k index2.html //1k index3.bundle.js //292k index3.html //1k
观察其打包结果: webpack 解析了每一个入口 js 文件的依赖关系并进行了按需分配,最终给每一个入口文件打包成一个文件(当中包含所有依赖)。对于小型项目这样已经可以了,但是如果是比较庞大的项目就会有进一步的优化空间。index 和 index2 都依赖了 app1 和 app2;index2 又有自己独需的 app3,假如一个请求同时要求 index 和 index2,那么毫无疑问,传输的两个 bundle 文件就会产生重复代码。这个影响是大型项目中明显的性能问题。所有已有必要进行代码分离。
2.使用 splitChunks 去重
使用此组件无需再安装,已经被放置在 webpack 当中(是 cli 里面还是 webpack 里面,这个我也不知道 -_-!)。所以直接配置就好。
配置 webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
//代码分离
module.exports = {
entry: {
index: "./src/index.js",
index2: "./src/index2.js",
index3: "./src/index3.js"
},
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, "dist")
},
optimization: {
splitChunks: {
chunks: "async",
// minSize: 30000, //指定当文件包超过多大时应该被分离出来
minSize: 1, //这里我们期望的实验效果是只要是可以被分离出来的复用代码,都给我出来
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: "~",
name: true,
cacheGroups: {
commons: {
name: "commons",
chunks: "initial",
minChunks: 2
},
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
},
plugins: [
new HtmlWebpackPlugin({
filename: "index.html",
template: "./index.html",
chunks: ["index", "commons"]
}),
new HtmlWebpackPlugin({
filename: "index2.html",
template: "./index2.html",
chunks: ["index2", "commons"]
}),
new HtmlWebpackPlugin({
filename: "index3.html",
template: "./index3.html",
chunks: ["index3", "commons"]
})
]
};
这里我们将3个 html 模板的依赖都加了一个 “commons”。
执行
npm run build
生成文件如下(dist文件夹内部)
commons.bundle.js //446k index.bundle.js //2k index.html //1k index2.bundle.js //2k index2.html //1k index3.bundle.js //143k index3.html //1k
现在你可以看到,凡是可以被抽离出来的复用代码都被打包进了commons.bundle.js ,形成了一个“库”,可供我们调取其中的方法。index3里面有着他自己独需的 app4,app4的内容就被封至index3 的内部了,所以比其他 index 文件大。这样我们的代码在理论上达到了最小化的“瘦身”效果,总体上来看,代码总量是最小的了,已经删去了所有不必要的重复。
但是做到这样还是不行,因为你会发现每当你想访问 index3 时,他要加载自己的 index3.html 和 index3.bundle.js 这不必解释,但是他为了要表达 app3 的动作就还要加载 commons.bundle.js这个库。但是加载这个库就同时加载了不必要的 app1 和 app2 动作,不使用的巨量代码带来的网速迟缓也是一个大型项目中的巨大问题。如何才能只加载当前入口需要的文件呢?我认为应该进一步分割代码。
下一节我将探索利用缓存以及 manifest 来提升应用性能?
作者知识水平有限,如有错误,敬请交流指正。
-------------结束线----------------