Fekit针对CommonJs规范进行的JS模块包装

利用fekit前端自动化构建工具进行前端工程化开发工程中,我们知道fekit使用CommonJS规范进行JS模块化的实现。其中scripts文件夹(JS文件目录)下的每个文件(大多为js文件)都是一个模块,然后我们利用module对象、exports对象和require()方法进行模块的定义和模块引用。

在fekit中利用CommonJS实现的JS文件中包含module对象、exports对象和require()方法,但是浏览器宿主环境中的JS并没有相对的module对象、exports对象和require()方法,那么浏览器是怎样运行我们的JS文件的呢?还是fekit做了哪些工作使我们的JS文件(大多数)既可以模块化,又可以使其在浏览器中运行的呢?

fekit中的模块

下面是一个利用fekit自动构建工具模块化的一个基于CommonJS规范的index.js文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var $ = require('../libs/jquery.js')
exports.ajax = function(){
$.ajax({
url: '/web/getJSON',
method: 'GET',
data: {},
success: function(res){
var str = "";
$.each(res.data,function(index,value){
str+="<li>"+value+"</li>";
});
$('.ul').html(str);
}
})
}
exports.count = 1;

首先我们利用require()方法引用了一个jquery.js模块,然后在index.js中利用module对象中的exports对象定义了一个包含ajax()方法和count变量(测试而已)的模块(不用着急看不懂,往下继续看),其中在ajax方法中我们使用了引入的jquery.js。

当我们书写完这个index.js模块后,利用fekit构建工具运行该项目,然后在chorme浏览器的source面板中,我们可以看到index.js变成了如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
;(function(__context){
var module = {
id : "118cd57f279558dffcbae8096c7cf113" ,
filename : "index.js" ,
exports : {}
};
if( !__context.____MODULES ) { __context.____MODULES = {}; }
var r = (function( exports , module , global ){
var $ =__context.____MODULES['8777f761b8463a858236c246bedbce92'];
exports.ajax = function(){
$.ajax({
url: '/web/getJSON',
method: 'GET',
data: {},
success: function(res){
var str = "";
$.each(res.data,function(index,value){
str+="<li>"+value+"</li>";
});
$('.ul').html(str);
}
})
}
exports.count = 1;
})( module.exports , module , __context );
__context.____MODULES[ "118cd57f279558dffcbae8096c7cf113" ] = module.exports;
})(this);

对比我们自己在项目中的index.js,我们发现,浏览器中展示的index.js对我们的index.js进行了包装,外层包了两层IIFE,这就是fekit帮我们做得事。

fekit做了什么?

我们知道了fekit对我们的JS文件外层包了两层IIFE,那么这两层IIFE干了什么呢?

同时我们好像也明白了为什么模块之前的作用域实现了隔离,避免了相互污染。就是这两层IIFE的引起的。

第一层IIFE

通过浏览器中的index.js,我们可以看出此层包装的目的:

  1. 传入宿主对象。通过传入this实参给__context形参来实现宿主对象的传递,我们还可以看出宿主对象中有一个____MODULES数组,其储存着通过CommonJS规范定义的每一个模块的映射。这就是第一层IIFE为什么要传入宿主对象的原因,就是为了通过这个——MODULES数组中储存的映射去引用其他模块;同时将当前模块的映射保存进这个数组,以便其他模块引用。
  2. 创建当前模块的module对象。该对象中包括当前模块的id、filename、exports属性。

第二层IIFE

此层包装的目的:

  1. 将上一层IIFE中创建的module对象和宿主对象传入。传入module对象是为了包装当前模块,传入宿主对象是为了将当前模块的映射保存进——MODULES数组。
  2. 引用其他模块。我们可以看出第二层IIFE的第一句就是去宿主对象的——MODULES数组中查找一个ID,这个ID就是jquery.js模块的moudule对象中的id属性。
  3. 将模块中的方法或变量赋值给exports对象。将该模块需要暴露出去给其他模块使用的方法或变量赋值给上一层传入的module对象的exports对象。
  4. 将当前模块储存进宿主对象的“____MODULES”数组。将该模块的id储存到宿主对象的“____MODULES”数组后,其他模块就可以引用当前模块了。

注:在CommonJS中,每个模块都有一个module对象代表当前模块对象,其exports对象中储存了暴露给其他模块的变量和方法。

结束语

这就是fekit对CommonJS规范的实现,使开发者可以方便的对JS进行模块化开发,而不用担心浏览器不支持CommonJS规范编写的JS模块化的文件。

前端自动化构建工具对于JS模块化的包装的方式也各有不同,但是目的是一样的,比如Gulp利用webpack的imports-loader插件进行的包装如下,有兴趣的小伙伴可以研究一下。

gulp(webpack)包装后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
module.exports = __webpack_require__(1);
/***/ },
/* 1 */
/***/ function(module, exports) {
/*** IMPORTS FROM imports-loader ***/
var define = false;
var common = {
render: function ($el, str) {
if($el.get(0).tagName.toLowerCase() == 'body') {
$el.prepend(str);
} else {
$el.html(str);
}
}
};
module.exports = common;
/***/ }
/******/ ]);

gulp(webpack)包装前

1
2
3
4
5
6
7
8
9
10
11
var common = {
render: function ($el, str) {
if($el.get(0).tagName.toLowerCase() == 'body') {
$el.prepend(str);
} else {
$el.html(str);
}
}
};
module.exports = common;

测试功能而已,你非要赏点我就没办法了...