继上一篇以后,咱们今天来看看如何实现 webpack 的代码切割(code-splitting)功能,最后实现的代码版本请参考这里。至于什么是 code-splitting ,为何要使用它,请直接参考官方文档。前端
通常说来,code-splitting 有两种含义:node
换句话说,咱们的目标是:将原先集中到一个 output.js 中的代码,切割成若干个 js 文件,而后分别进行加载。 也就是说:原先只加载 output.js ,如今把代码分割到3个文件中,先加载 output.js ,而后 output.js 又会自动加载 1.output.js 和 2.output.js 。jquery
既然要将一份代码切割成若干份代码,总得有个切割点的标志吧,从哪儿开始切呢?
答案:webpack 使用require.ensure
做为切割点。webpack
然而,我用 nodeJS 也挺长时间了,怎么不知道还有require.ensure
这种用法?而事实上 nodeJS 也是不支持的,这个问题我在CommonJS 的标准中找到了答案:虽然 CommonJS 通俗地讲是一个同步模块加载规范,可是其中是包含异步加载相关内容的。只不过这条内容只停留在 PROPOSAL (建议)阶段,并未最终进入标准,因此 nodeJS 没有实现它也就不奇怪了。只不过 webpack 刚好利用了这个做为代码的切割点。git
ok,如今咱们已经明白了为何要选择require.ensure
做为切割点了。接下来的问题是:如何根据切割点对代码进行切割? 下面举个例子。github
// example.js
var a = require("a");
var b = require("b");
a();
require.ensure(["c"], function(require) {
require("b")();
var d = require("d");
var c = require('c');
c();
d();
});
require.ensure(['e'], function (require) {
require('f')();
});复制代码
假设这个 example.js 就是项目的主入口文件,模块 a ~ f 是简简单单的模块(既没有进一步的依赖,也不包含require.ensure
)。那么,这里一共有2个切割点,这份代码将被切割为3部分。也就说,到时候会产生3个文件:output.js ,1.output.js ,2.output.jsweb
程序如何识别require.ensure
呢?答案天然是继续使用强大的 esprima 。关键代码以下:算法
// parse.js
if (expression.callee && expression.callee.type === 'MemberExpression'
&& expression.callee.object.type === 'Identifier' && expression.callee.object.name === 'require'
&& expression.callee.property.type === 'Identifier' && expression.callee.property.name === 'ensure'
&& expression.arguments && expression.arguments.length >= 1) {
// 处理require.ensure的依赖参数部分
let param = parseStringArray(expression.arguments[0])
let newModule = {
requires: [],
namesRange: expression.arguments[0].range
};
param.forEach(module => {
newModule.requires.push({
name: module
});
});
module.asyncs = module.asyncs || [];
module.asyncs.push(newModule);
module = newModule;
// 处理require.ensure的函数体部分
if(expression.arguments.length > 1) {
walkExpression(module, expression.arguments[1]);
}
}复制代码
观察上面的代码能够看出,识别出require.ensure
以后,会将其存储到 asyncs 数组中,且继续遍历其中所包含的其余依赖。举个例子,example.js 模块最终解析出来的数据结构以下图所示:
express
我在刚刚使用 webpack 的时候,是分不清这两个概念的。如今我能够说:“在上面的例子中,有3个 chunk,分别对应 output.js、1.output.js 、2.output.js;有7个 module,分别是 example 和 a ~ f。json
因此,module 和 chunk 之间的关系是:1个 chunk 能够包含若干个 module。
观察上面的例子,得出如下结论:
好了,下面进入重头戏。
在对各个模块进行解析以后,咱们能大概获得如下这样结构的 depTree。
下面咱们要作的就是:如何从8个 module 中构建出3个 chunk 出来。 这里的代码较长,我就不贴出来了,想看的到这里的 buildDep.js 。
其中要重点注意是:前文说到,为了不代码的冗余,须要将模块 c 从 chunk1 中移除,具体发挥做用的就是函数removeParentsModules
,本质上无非就是改变一下标志位。最终生成的chunks的结构以下:
经历重重难关,咱们终于来到了最后一步:如何根据构建出来的 chunks 拼接出若干个 output.js 呢?
此处的拼接与上一篇最后提到的拼接大同小异,主要不一样点有如下2个:
其实关于 webpack 的代码切割还有不少值得研究的地方。好比本文咱们实现的例子仅仅是将1个文件切割成3个,并未就其加载时机进行控制。好比说,如何支持在单页面应用切换 router 的时候再加载特定的 x.output.js?
注:更多系列文章请移步个人博客
-------- EOF -----------
本文对你有帮助?欢迎扫码加入前端学习小组微信群: