前言
code-splitting是webpack最引人瞩目的特性之一,此特性将代码分离到不同的bundle文件中。详细介绍官网code-split,这次实现则在笔者上次文件打包之上做开发。
功能分析
官网上有三种方式实现
- 入口起点:使用 entry 选项手动分离代码。
- 防止重复:使用 CommonsChunkPlugin 去重和分离 chunk。
- 动态导入:通过模块的内联函数调用来分离代码。
1本质则是多个入口的chunk,2则在以common.js为入口文件,将多入口的chunk切分为按切割文件,通过jsonp加载。在这里笔者则介绍最为复杂的3的实现,
对于webpack 的切割文件的引入本质就是jsonp,动态引入一个约定好格式的js,并运行。
__webpack_require__.e = function requireEnsure(chunkId) {
....
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.src = __webpack_require__.p + "" + chunkId + ".bundle.js";
head.appendChild(script);
....
}
切割文件去除注释如下:
webpackJsonp([1],[function(){},function(){}])
而在入口文件的webpackJsonpCallback函数内,则是将切割的文件包含的modules依次放入存储在modules内
function webpackJsonpCallback(chunkIds, moreModules){
....
for(moduleId in moreModules) {
if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
}
....
所以实现以上功能需求如下:
- parse模块:定位切割点并组装异步加载文件所需要的依赖。
- chunks模块:各个chunk包含module模块的集合,通过文件树里面模块的依赖关系生成。
- writeChunks模块:根据chunks,通过文件流写入文件。
例子
require('d');
function a() {
require.ensure(['./a'], function () {
require('c');
});
}
require.ensure(['./b'], function () {
require('./m');
});
require('./e');
实现
parse模块
实现思路:
- 通过递归,以及文件树的特征定位到require.ensure
- 将arguments第一个参数的数组,第二个参数的函数内递归搜索require,存入数组asyncs内,并递归下去
数据结构如下:
{
filename: '/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/main.js',
id: 0,
requires: [{
name: 'd',
range: [8, 11],
id: 1
}],
rangeRequires: [[0, 7]],
asyncs: [{
requires: [{
name: './a',
id: 2
}, {
name: 'c',
range: [88, 91],
id: 3
}],
asyncs: [],
rangeRequires: [80, 87],
ensureRequires: [34, 58]
},
{
requires: [{
name: './b',
id: 4
}, {
name: './m',
range: [156, 161],
id: 5
}],
asyncs: [],
rangeRequires: [148, 155],
ensureRequires: [106, 130]
}],
}
chunks模块
由于各个依赖文件的源码都包含在modlues内,所以chunks包含的是具体各个切割文件所包需module的moduleId。
实现思路:
- 通过入口mainPath 找到modules的入口mainModule
- 将mainModule 的requires遍历,将值归入本chunk的modules内,将asyncs遍历,依次新建chunk,并关联父chunk,以上两个依次递归遍历。
- 最终生成完了之后,将各个非根节点的chunk遍历,将依赖的modules遍历对比父节点的chunk,如有重复标记'in-parent'
数据结构如下
{ '0':
{ id: 0,
modules: { '0': 'include', '1': 'include', '2': 'include' } },
'1':
{ id: 1,
modules:
{ '1': 'in-parent',
'3': 'include',
'4': 'include',
'5': 'include',
'6': 'include' },
parentId: 0 },
'2':
{ id: 2,
modules: { '5': 'include', '6': 'include' },
parentId: 0
}
}
writeChunks模块
实现思路:
- 判断是否有多个chunk,来区分引入的模版。如果chunks的个数超过1,入口chunk则加载包含webpackJsonp,__webpack_require__.e等支持jsonp函数的模版,未超过则加载简单的仅包含__webpack_require__的模版
- 区分入口chunk,入口/非入口chunk加载不同的头部。
- webpackJsonp的入参有两种,一种数组,一种以moduleId为key的对象。为数组时候则需要将以[,,modlue]等方式保证顺序
如:
代码实现
本人的简易版webpack实现simple-webpack
(完)