-
Notifications
You must be signed in to change notification settings - Fork 292
Webpack构建优化—提取公共代码
大型网站通常会由多个页面组成,每个页面都是一个独立的单页应用。 但由于所有页面都采用同样的技术栈,以及使用同一套样式代码,这导致这些页面之间有很多相同的代码。
如果每个页面的代码都把这些公共的部分包含进去,会造成以下问题:
- 相同的资源被重复的加载,浪费用户的流量和服务器的成本;
- 每个页面需要加载的资源太大,导致网页首屏加载缓慢,影响用户体验。
如果把多个页面公共的代码抽离成单独的文件,就能优化以上问题。 原因是在用户第一次访问后,这些页面公共代码的文件已经被浏览器缓存起来,在用户切换到其它页面时,存放公共代码的文件就不会再重新加载,而是直接从缓存中获取。这样做后有如下好处:
- 减少网络传输流量,降低服务器成本;
- 虽然用户第一次打开网站的速度得不到优化,但之后访问其它页面的速度将大大提升。
可以采用以下原则去为你的网站提取公共代码:
- 根据你网站所使用的技术栈,找出网站所有页面都需要用到的基础库,以采用React技术栈的网站为例,所有页面都会依赖
react
、react-dom
等库,把它们提取到一个单独的文件。 一般把这个文件叫做base.js
,因为它包含所有网页的基础运行环境; - 在剔除了各个页面中被
base.js
包含的部分代码外,再找出所有页面都依赖的公共部分的代码提取出来放到common.js
中去。 - 再为每个网页都生成一个单独的文件,这个文件中不再包含
base.js
和common.js
中包含的部分,而只包含各个页面单独需要的部分代码。
文件之间的结构图如下:
既然能找出所有页面都依赖的公共代码,并提取出来放到common.js
中去,为什么还需要再把网站所有页面都需要用到的基础库提取到base.js
去呢? 原因是为了长期的缓存base.js
这个文件。
发布到线上的文件都会对静态文件的文件名都附加根据文件内容计算出Hash值,也就是最终base.js
的文件名会变成base_3b1682ac.js
,以长期缓存文件。网站通常会不断的更新发布,每次发布都会导致common.js
和各个网页的JavaScript文件都会因为文件内容发生变化而导致其Hash值被更新,也就是缓存被更新。
把所有页面都需要用到的基础库提取到base.js
的好处在于只要不升级基础库的版本,base.js
的文件内容就不会变化,Hash值不会被更新,缓存就不会被更新。 每次发布浏览器都会使用被缓存的base.js
文件,而不用去重新下载base.js
文件。 由于base.js
通常会很大,这对提升网页加速速度能起到很大的效果。
Webpack内置了专门用于提取多个Chunk
中公共部分的插件CommonsChunkPlugin
,CommonsChunkPlugin
大致使用方法如下:
new webpack.optimize.CommonsChunkPlugin({
// 从哪些 Chunk 中提取
chunks: ['a', 'b'],
// 提取出的公共部分形成一个新的 Chunk,这个新 Chunk 的名称
name: 'common'
})
以上配置就能从网页A和网页B中抽离出公共部分,放到common
中。
每个CommonsChunkPlugin
实例都会生成一个新的Chunk
,这个新Chunk
中包含了被提取出的代码,在使用过程中必须指定name
属性,以告诉插件新生成的Chunk
的名称。 其中chunks
属性指明从哪些已有的Chunk
中提取,如果不填该属性,则默认会从所有已知的Chunk
中提取。
Chunk
是一系列文件的集合,一个Chunk
中会包含这个Chunk
的入口文件和入口文件依赖的文件。
通过以上配置输出的common Chunk
中会包含所有页面都依赖的基础运行库react
、react-dom
,为了把基础运行库从common
中抽离到base
中去,还需要做一些处理。
首先需要先配置一个Chunk
,这个Chunk
中只依赖所有页面都依赖的基础库以及所有页面都使用的样式,为此需要在项目中写一个文件base.js
来描述base Chunk
所依赖的模块,文件内容如下:
// 所有页面都依赖的基础库
import 'react';
import 'react-dom';
// 所有页面都使用的样式
import './base.css';
接着再修改Webpack配置,在entry
中加入base
,相关修改如下:
module.exports = {
entry: {
base: './base.js'
},
};
以上就完成了对新Chunk base
的配置。
为了从common
中提取出base
也包含的部分,还需要配置一个CommonsChunkPlugin
,相关代码如下:
new webpack.optimize.CommonsChunkPlugin({
// 从 common 和 base 两个现成的 Chunk 中提取公共的部分
chunks: ['common', 'base'],
// 把公共的部分放到 base 中
name: 'base'
})
由于common
和base
公共的部分就是base
目前已经包含的部分,所以这样配置后common
将会变小,而base
将保持不变。
以上都配置好后重新执行构建,会得到四个文件,它们分别是:
-
base.js
:所有网页都依赖的基础库组成的代码; -
common.js
:网页A、B都需要的,但又不在base.js
文件中出现过的代码; -
a.js
:网页A单独需要的代码; -
b.js
:网页B单独需要的代码。
为了让网页正常运行,以网页A为例,你需要在其HTML中按照以下顺序引入以下文件才能让网页正常运行:
<script src="base.js"></script>
<script src="common.js"></script>
<script src="a.js"></script>
以上就完成了提取公共代码需要的所有步骤。
针对 CSS 资源,以上理论和方法同样有效,也就是说可以对CSS文件做同样的优化。
以上方法可能会出现common.js
中没有代码的情况,原因是去掉基础运行库外很难再找到所有页面都会用上的模块。 在出现这种情况时,可以采取以下做法之一:
-
CommonsChunkPlugin
提供一个选项minChunks
,表示文件要被提取出来时需要在指定的Chunks
中最小出现最小次数。 假如minChunks=2
、chunks=['a','b','c','d']
,任何一个文件只要在['a','b','c','d']
中任意两个以上的Chunk
中都出现过,这个文件就会被提取出来。 你可以根据自己的需求去调整minChunks
的值,minChunks
越小越多的文件会被提取到common.js
中去,但这也会导致部分页面加载的不相关的资源越多;minChunks
越大越少的文件会被提取到common.js
中去,但这会导致common.js
变小、效果变弱。 - 根据各个页面之间的相关性选取其中的部分页面用
CommonsChunkPlugin
去提取这部分被选出的页面的公共部分,而不是提取所有页面的公共部分,而且这样的操作可以叠加多次。 这样做的效果会很好,但缺点是配置复杂,你需要根据页面之间的关系去思考如何配置,该方法不通用。