Webpack代码分割详解:原理、配置与实践
一、为什么需要代码分割
在现代Web开发中,随着前端应用变得越来越复杂,JavaScript包的体积也在不断增长。大体积的JS包会导致应用加载缓慢,影响用户体验。即使我们使用了懒加载等技术,但如果初始包的体积仍然很大,应用的初始加载速度仍然会受到影响。
那么,我们如何解决这个问题呢?一种解决方案是使用代码分割(Code Splitting)技术。
二、什么是代码分割
代码分割是指将代码分割成多个小包,然后按需加载或并行加载这些小包。这样可以减小初始包的体积,加速应用的初始加载。
代码分割的好处不仅仅是减小包的体积,它还可以帮助我们:
- 按需加载代码,避免加载用户不需要的代码。
- 并行加载代码,提高加载速度。
- 更好地利用浏览器缓存。
- 更灵活地组织代码,提高代码的可维护性。
三、Webpack中的代码分割
Webpack作为目前最流行的打包工具之一,内置了强大的代码分割功能。它允许我们定义分割点,或者使用动态导入语句,Webpack会自动将代码分割成小包。Webpack还提供了丰富的配置选项,让我们能够灵活地控制代码分割的行为。
3.1 如何实现代码分割
在Webpack中,有两种主要的代码分割方式:
- 入口点分割:通过配置多个入口点,Webpack会为每个入口点生成一个独立的包。
- 动态导入:通过使用动态导入语句(如
import()
),Webpack会将动态导入的模块分割到一个独立的包中。
除此之外,Webpack还提供了更高级的代码分割功能,如使用SplitChunksPlugin进行公共代码提取。
3.2 SplitChunksPlugin
SplitChunksPlugin是Webpack内置的一个插件,用于提取公共代码到独立的包中。它会根据我们的配置,自动识别公共的模块,并将这些模块提取到单独的包中。这样可以减小主包的体积,并且可以更好地利用浏览器缓存。
SplitChunksPlugin提供了丰富的配置选项,让我们能够灵活地控制代码分割的行为,如最小包大小、最大包数量、缓存组等。
四、Webpack源码分析
为了更深入地理解代码分割的原理,让我们来分析一下SplitChunksPlugin的关键源码。代码位于github.com/webpack/web…
4.1 splitChunks
方法
splitChunks
方法是代码分割的主入口,它会遍历所有的chunks,为每个chunk执行分割逻辑。
splitChunks(compilation, chunks) {
// 步骤1: 获取所有的模块
const allModules = compilation.modules;
// 步骤2: 根据缓存组配置,为每个模块确定所属的缓存组
const cacheGroupSources = getCacheGroups(allModules);
// 步骤3: 创建模块组,每个模块组包含了相同缓存组的模块
const moduleGroups = this._getModuleGroups(
allModules,
cacheGroupSources
);
// 步骤4: 遍历每个chunk,为每个chunk生成模块组
for (const chunk of chunks) {
// ...
}
}
4.2 _splitChunk
方法
_splitChunk
方法会遍历分割点,检查每个分割点是否满足分割条件。如果满足,就会创建一个新的chunk,并将分割点的模块移动到新的chunk中。
_splitChunk(chunk, splitPoints, compilation) {
// 遍历所有的分割点
for (const [key, splitPoint] of splitPoints) {
const { modules, cacheGroup, chunks } = splitPoint;
// 检查分割点是否满足分割条件
if (this._checkSplitConditions(chunk, splitPoint)) {
// 创建新的chunk
const newChunk = compilation.addChunk(
cacheGroup.getName(chunk)
);
// 将模块移动到新的chunk中
for (const module of modules) {
compilation.chunkGraph.connectChunkAndModule(
newChunk,
module
);
}
// 建立新chunk与原chunk的关系
compilation.chunkGraph.connectChunkAndChunk(
chunk,
newChunk
);
}
}
}
总的来说,SplitChunksPlugin的工作流程可以总结为:
- 遍历所有模块,根据缓存组配置生成模块组。
- 遍历所有chunk,为每个chunk生成模块组。
- 基于chunk的模块组,生成分割点。
- 遍历分割点,检查是否满足分割条件,如果满足就执行分割,生成新的chunk。
理解了SplitChunksPlugin的工作原理,我们就能更好地配置和使用代码分割。
Citations: [1] ppl-ai-file-upload.s3.amazonaws.com/web/direct-…
五、代码分割实践:一个完整的例子
接下来,让我们通过一个完整的例子,来实践Webpack的代码分割。
步骤一:初始化项目
首先,创建一个新的目录,并在其中初始化npm项目:
mkdir webpack-code-splitting-demo
cd webpack-code-splitting-demo
npm init -y
安装必要的依赖:
npm install webpack webpack-cli lodash moment --save-dev
步骤二:创建项目文件
创建以下项目结构:
webpack-code-splitting-demo
├── package.json
├── webpack.config.js
└── src
├── index.js
└── utils.js
src/index.js
作为入口文件,src/utils.js
作为需要异步加载的模块。
src/index.js
:
import _ from 'lodash';
import moment from 'moment';
function component() {
const element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
element.classList.add('hello');
const dateElement = document.createElement('p');
dateElement.innerHTML = moment().format('MMMM Do YYYY, h:mm:ss a');
element.appendChild(dateElement);
const button = document.createElement('button');
button.innerHTML = 'Click me and check the console!';
element.appendChild(button);
button.onclick = function() {
import('./utils').then(module => {
const result = module.add(1, 2);
console.log('Result:', result);
});
};
return element;
}
document.body.appendChild(component());
src/utils.js
:
export function add(a, b) {
return a + b;
}
步骤三:配置Webpack(不使用代码分割)
webpack.config.js
:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
mode: 'development',
};
在package.json
中添加一个构建脚本:
"scripts": {
"build": "webpack"
}
运行npm run build
,查看打包结果。此时,所有代码都打包到了main.js
中。这时候的打包结果如下图,main.js有1.29mb, src_utils_js.main.js是1.18kb。
步骤四:配置Webpack(使用代码分割)
修改webpack.config.js
,启用代码分割:
module.exports = {
entry: './src/index.js',
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
},
mode: 'development',
optimization: {
splitChunks: {
chunks: 'all',
minSize: 20000,
maxSize: 0,
minChunks: 1,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};
让我们详细解释几个比较重要的配置项:
chunks
:表示哪些代码需要优化。这里设置为all
,表示同步和异步代码都要优化。minSize
:表示拆分前的最小模块大小。这里设置为20000字节(约20KB),避免生成过多的小文件。cacheGroups
:定义了拆分的缓存组。vendors
:用于拆分node_modules
目录下的模块。default
:默认的缓存组,用于拆分被多次引用的模块。
运行npm run build
,可以看到代码被拆分成了以下几个文件:
main.js
:我们的应用代码。现在只有34kbvendors-xxx.js
:包含lodash
和moment
这两个第三方库。src_utils_js.xxx.js
:包含utils.js
模块的代码。
步骤五:分析打包结果
对比代码分割前后的打包结果,我们可以发现:
-
代码分割前,所有代码都打包到了一个大的
main.js
文件中,包括第三方库和异步加载的模块。这导致初始加载时间较长。 -
代码分割后,代码被拆分成了多个小文件:
- 主文件
main.js
体积显著减小,只包含我们的应用代码。 - 第三方库被拆分到了
vendors-xxx.js
,可以被浏览器缓存,加速后续加载。
- 主文件
通过合理配置代码分割,我们可以:
- 将第三方库与应用代码分离,减小主文件体积,加速初始加载。
- 将异步模块单独打包,实现按需加载,减小初始加载时间。
- 将公共的代码提取到单独的文件,可以被浏览器缓存,加速后续加载。
六、总结
代码分割是优化Web应用性能的重要手段之一。它可以帮助我们减小初始包的体积,加速应用的加载,提升用户体验。
Webpack内置的代码分割功能,特别是SplitChunksPlugin插件,让我们能够轻松实现代码分割。通过分析SplitChunksPlugin的源码,我们深入理解了其工作原理。在编译阶段,该插件会遍历所有的chunks和modules,识别出满足分割条件的模块,将其添加到新的chunk中,并进行一些优化。
在实践中,我们通过一个完整的例子,学习了如何配置splitChunks
来实现代码分割。一些重要的配置项如chunks
,minSize
,cacheGroups
等,可以帮助我们控制代码分割的粒度和效果。
通过对比代码分割前后的打包结果,我们直观地看到了代码分割的效果:主文件体积减小,第三方库被分离,异步模块按需加载。这些都有助于提升我们Web应用的性能。
当然,代码分割并非银弹。我们需要根据实际情况,权衡代码分割的粒度和效果,避免过度分割导致的负面影响,如请求数量增加,缓存利用率降低等。
希望通过这篇文章,你能够全面了解Webpack代码分割的原理和实践,并在实际项目中灵活运用。让我们一起努力,构建更快、更好的Web应用!
项目源码: github.com/jerryjiao/w…
转载自:https://juejin.cn/post/7352079402057220108