一、用打包模板实现最简单的模块的打包
先试想一下,对于下面这样一个遵循CommonJS规范的模块:
- tiny-bundler\index.js
console.log('hello, bundler');
module.exports = 'hello, world';
将它打包后,需要变成什么样子呢?
曾在前文 几种常用的模块化规范总结 中提到过,Node.js对于CommonJS模块的处理做法是在外面套一层壳,把原生JS中没有的require、module、exports等方法注进去。这里,我们也可以学习这种做法。此外,为了获得一个模块的作用域,我们把它封在(function(){})()
中。
1. (function() {
2. var moduleList = [
3. function (require, module, exports) {
4. console.log('hello, bundler');
5. module.exports = 'hello, world';
6. }
7. ]
8.
9. var module = {
10. exports: {}
11. };
12.
13. moduleList[0](null, module, null);
14. })();
通过观察,我们发现,模块的内容只体现在第4-5行,于是,我们想到可以把其余的部分抽成模板。于是,我们建立文件:
- tiny-bundler\src\index.bundle.boilerplate
(function() {
var moduleList = [
function (require, module, exports) {
/* template */
}
]
var module = {
exports: {}
};
moduleList[0](null, module, null);
})();
- tiny-bundler\src\bundle.js
const fs = require('fs');
const path = require('path');
const boilerplate = fs.readFileSync(path.resolve(__dirname, 'index.bundle.boilerplate'), 'utf-8');
const target = fs.readFileSync(path.resolve(__dirname, '../index.js'), 'utf-8');
const content = boilerplate.replace('/* template */', target);
fs.writeFileSync(path.resolve(__dirname, '../dist/index.bundle.js'), content, 'utf-8');
我们在tiny-bundler目录下执行node src/bundle.js
,就可以得到打包后的结果:
- tiny-bundler\dist\index.bundle.js
var moduleList = [
function (require, module, exports) {
console.log('hello, world');
module.exports = 'hello, world';
}
]
var module = {
exports: {}
};
moduleList[0](null, module, null);
})();
二、自己实现require,完成带有require的模块的打包
下面,我们新建一个文件:
- tiny-bundler\moduleA.js
module.exports = new Date().getTime();
并将tiny-bundler\index.js的内容修改为:
const moduleA = require('./moduleA');
console.log(moduleA);
这个时候,打包应该怎么实现呢?我们仍然可以采用倒推的方法,先自己来手写一下index.bundle.js:
1. (function() {
2. var moduleList = [
3. // index.js
4. function (require, module, exports) {
5. const moduleA = require('./moduleA');
6. console.log(moduleA);
7. },
8. // moduleA.js
9. function (require, module, exports) {
10. module.exports = new Date().getTime();
11. }
12. ]
13.
14. var moduleDepIdList = [
15. {
16. // 表示moduleList中的第0个模块中,如果遇到了require('./moduleA'),就需要去moduleList[1]中找这个模块
17. './moduleA': 1,
18. },
19. {
20. }
21. ]
22.
23. // 自己实现一个require
24. function require(id, parentId) {
25. var currentModuleId = parentId !== undefined ? moduleDepIdList[parentId][id] : id;
26. var module = { exports: {} };
27. var moduleFunc = moduleList[currentModuleId];
28. moduleFunc((id) => require(id, currentModuleId), module, module.exports);
29. return module.exports;
30. }
31.
32. require(0);
33. })();
经过观察,我们发现上面代码中3-11、15-20行是面对不同模块的打包时要变化的部分,其余部分是不变的。因此,我们可以把tiny-bundler\src\index.bundle.boilerplate改成:
(function() {
var moduleList = [
/* template-module-list */
]
var moduleDepIdList = [
/* template-module-dep-id-list */
]
function require(id, parentId) {
var currentModuleId = parentId !== undefined ? moduleDepIdList[parentId][id] : id;
var module = { exports: {} };
var moduleFunc = moduleList[currentModuleId];
moduleFunc((id) => require(id, currentModuleId), module, module.exports);
return module.exports;
}
require(0);
})();
三、实现require.ensure,完成chunk异步加载的打包
还是采用倒推的方式,我们先手写出来一个实现的效果:
- tiny-bundler\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./index.bundle.js"></script>
</body>
</html>
- tiny-bundler\index.bundle.js
(function() {
var moduleList = [
function (require, module, exports) {
require
.ensure('1')
.then(res => {
console.log(res);
})
}
]
var moduleDepIdList = [];
var cache = {};
function require(id, parentId) {
var currentModuleId = parentId !== undefined ? moduleDepIdList[parentId][id] : id;
var module = { exports: {} };
var moduleFunc = moduleList[currentModuleId];
moduleFunc((id) => require(id, currentModuleId), module, module.exports);
return module.exports;
}
window.__JSONP = function (chunkId, moduleFunc) {
var resolve = cache[chunkId][0];
var module = {
exports: {}
};
// 执行这个chunk的代码
moduleFunc(require, module, module.exports);
// 然后resolve掉执行模块代码后得到的module.exports
resolve(module.exports);
}
require.ensure = function (chunkId, parentId) {
var currentModuleId = parentId !== undefined ? moduleDepIdList[parentId][chunkId] : chunkId;
// 如果cache[currentModuleId] === undefined,表明是首次加载
if (cache[currentModuleId] === undefined) {
// 通过JSONP异步加载这个JS
var $script = document.createElement('script');
$script.src = '/chunk_' + chunkId + '.js';
document.body.appendChild($script);
// 把几个状态都挂到全局的cache里面
var promise = new Promise(function (resolve) {
cache[currentModuleId] = [resolve];
cache[currentModuleId].status = true; // 这个状态为true则表示'/chunk_' + chunkId + '.js'这个JS还在加载过程中
});
cache[currentModuleId].push(promise);
return promise;
} else if (cache[currentModuleId].status) {
// 正在加载中
return cache[currentModuleId][1];
}
return cache[currentModuleId];
}
moduleList[0](require, null, null);
})();
- tiny-bundler\chunk_1.js
window.__JSONP('1', function(require, module, exports) {
module.exports = 'hello, world';
});
在tiny-bundler目录下执行anywhere -p 80
启动静态服务器,访问 http://localhost 在Chrome Devtools中可看到如下结果:
具体的实现上,可以如前面一样,抽取出来模板,进行replace替换。
顺便提一下热更新。因为moduleList中存储着所有编译后的模块的代码,所以,当我们对某个模块进行了更新的时候,websocket server端通过fsevents.watch会监听到,这个时候,就会触发重新打包,打包后就通过websocket通知客户端,客户端获得通知后只需要将moduleList中对应模块的代码进行替换,这个时候就能在内存中直接将moduleList中对应模块的内容改掉,就能实现热更新的效果。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!