本文首发于知乎专栏:
http://zhuanlan.zhihu.com/starkwang
CommonJS 是一个盛行的前端模块化范例,也是如今 NodeJS 以及其模块托管堆栈 npm 运用的范例,但如今暂无浏览器支撑 CommonJS 。要想让浏览器用上这些模块,必需转换花样。
这个系列的文章,我们会一步步完成一个基于 CommonJS 的打包东西,类似于一个简朴版的 Browserify 或许 Webpack 。
一、道理
与 NodeJS 环境差别,浏览器中不支撑 CommonJS 的重要原因是缺少了以下几个环境变量:
module
exports
require
global
换句话说,打包器的道理就是模拟这四个变量的行动。
比方我们有一个index.js
文件,依靠了module1
和module2
两个模块,而且module1
依靠module2
:
//index.js
var module1 = require("./module1");
var module2 = require("./module2");
module1.foo();
module2.foo();
function hello(){
console.log("Hello!");
}
module.exports = hello;
//module1.js
var module2 = require(module2);
console.log("initialize module1");
console.log("this is module2.foo() in module1:");
module2.foo();
console.log("\n")
module.exports = {
foo: function(){
console.log("module1 foo !!!");
}
};
//module2.js
console.log("initialize module2");
module.exports = {
foo: function(){
console.log("module2 foo !!!");
}
};
把它放入一个匿名函数内,经由过程这个匿名函数注入 require
、modules
、export
、global
变量(我们暂时不完成global)
function(module, exports, require, global){
var module1 = require("./module1");
var module2 = require("./module2");
module1.foo();
module2.foo();
function hello(){
console.log("Hello!");
}
module.exports = hello;
}
如今我们用一个 modules
对象来存入这些匿名函数:
//modules
{
"entry": function(module, exports, require, global){
//index.js
var module1 = require("./module1");
var module2 = require("./module2");
module1.foo();
module2.foo();
function hello(){
console.log("Hello!");
}
module.exports = hello;
},
"./module1": function(module, exports, require, global){
var module2 = require("./module2");
console.log("initialize module1");
console.log("this is module2.foo() in module1:");
module2.foo();
console.log("\n")
module.exports = {
foo: function(){
console.log("module1 foo !!!");
}
};
},
"./module2": function(module, exports, require, global){
console.log("initialize module2");
module.exports = {
foo: function(){
console.log("module2 foo !!!");
}
};
}
}
下面我们完成一个简朴的 require
函数:
//这个对象用于贮存已导入的模块
var installedModules = {};
function require(moduleName) {
//假如模块已导入,那末直接返回它的exports
if(installedModules[moduleName]){
return installedModules[moduleName].exports;
}
//模块初始化
var module = installedModules[moduleName] = {
exports: {},
name: moduleName,
loaded: false
};
//实行模块内部的代码,这里的 modules 变量即为我们在上面写好的 modules 对象
modules[moduleName].call(module.exports, module, module.exports,require);
//模块导入完成
module.loaded = true;
//将模块的exports返回
return module.exports;
}
末了只要把我们上面写好的 modules
对象以马上实行函数的情势传入这个 require
函数就能够了,以下是完全的代码:
(function(modules){
//这个对象用于贮存已导入的模块
var installedModules = {};
function require(moduleName) {
//假如模块已导入,那末直接返回它的exports
if(installedModules[moduleName]){
return installedModules[moduleName].exports;
}
//模块初始化
var module = installedModules[moduleName] = {
exports: {},
name: moduleName,
loaded: false
};
//实行模块内部的代码,这里的 modules 变量即为我们在上面写好的 modules 对象
modules[moduleName].call(module.exports, module, module.exports,require);
//模块导入完成
module.loaded = true;
//将模块的exports返回
return module.exports;
}
//进口函数
return require("entry");
})({
"entry": function(module, exports, require, global){
//index.js
var module1 = require("./module1");
var module2 = require("./module2");
module1.foo();
module2.foo();
function hello(){
console.log("Hello!");
}
module.exports = hello;
},
"./module1": function(module, exports, require, global){
var module2 = require("./module2");
console.log("initialize module1");
console.log("this is module2.foo() in module1:");
module2.foo();
console.log("\n")
module.exports = {
foo: function(){
console.log("module1 foo !!!");
}
};
},
"./module2": function(module, exports, require, global){
console.log("initialize module2");
module.exports = {
foo: function(){
console.log("module2 foo !!!");
}
};
}
});
事实上,我们短短的这几十行代码模拟了 Webpack 的部份完成。但我们依旧在运用诸如 "./module1"
如许的字符串作为模块的唯一识别码,这是一个显著的缺点,存在多层级文件时,这个称号很轻易争执。
在 Browserify 或 Webpack 如许的临盆级东西里,平常运用数字作为函数的唯一识别码,比方它可能会把(以 Webpack 为例):
var module1 = require("./module1");
编译成:
var module1 = __webpack_require__(1);
二、小结
我们在这里完成了一个最简朴的 CommonJS 规范的实行器,接下来的文章中我们会做以下事变:
1、完成 global 变量
2、用 moduleID 替换 moduleName
3、写一个命令行小东西
4、支撑 node_modules 和多层级文件