摘抄于 Cson的博客
正好看browserify。。。。。看了几遍不错的博文。找到觉得很不错的。

什么是browserify

  • 使用browerify,使代码能同时运行于服务端和浏览器端。

  • Browserify 可以让你使用类似于 node 的 require() 的方式来组织浏览器端的 Javascript 代码,通过预编译让前端 Javascript 可以直接使用 Node NPM 安装的一些库,支持CommonJS

运行过程

阶段1:预编译阶段

1.从入口模块开始,分析代码中require函数的调用
2.生成AST
3.根据AST找到每个模块require的模块名
4.得到每个模块的依赖关系,生成一个依赖字典
5.包装每个模块(传入依赖字典以及自己实现的export和require函数),生成用于执行的js

阶段2:执行阶段

从入口模块开始执行,递归执行所require的模块,得到依赖对象。

具体步骤分析

1.从入口模块开始,分析代码中require函数的调用

由于浏览器端并没有原生的require函数,所以所有require函数都是需要我们自己实现的。因此第一步我们需要知道一个模块的代码中,哪些地方用了require函数,依赖了什么模块。

browerify实现的原理是为代码文件生成AST,然后根据AST找到require函数依赖的模块。

2.生成AST

文件代码

var t = require("b");
t.write();

生成的js描述的AST为:

{
"type": "Program",
"body": [
    {
        "type": "VariableDeclaration",
        "declarations": [
            {
                "type": "VariableDeclarator",
                "id": {
                    "type": "Identifier",
                    "name": "t"
                },
                "init": {
                    "type": "CallExpression",
                    "callee": {
                        "type": "Identifier",
                        "name": "require"
                    },
                    "arguments": [
                        {
                            "type": "Literal",
                            "value": "b",
                            "raw": "\"b\""
                        }
                    ]
                }
            }
        ],
        "kind": "var"
    },
    {
        "type": "ExpressionStatement",
        "expression": {
            "type": "CallExpression",
            "callee": {
                "type": "MemberExpression",
                "computed": false,
                "object": {
                    "type": "Identifier",
                    "name": "t"
                },
                "property": {
                    "type": "Identifier",
                    "name": "write"
                }
            },
            "arguments": []
        }
    }
]
}

我调用的reqiure(),正是上面代码的这一部分:

"init": {
         "type": "CallExpression",
         "callee": {
                      "type": "Identifier",
                      "name": "require"
                    },
         "arguments": [
                        {
                          "type": "Literal",
                          "value": "b",
                          "raw": "\"b\""
                        }
                      ]
         }

3.根据AST找到每个模块require的模块名

生成了AST之后,我们下一部就需要根据AST找到require依赖的模块名了。再次看看上面生成的AST对象,要找到require的模块名,实质上就是要:

找到type为callExpression,callee的name为require所对应的第一个argument的value。

关于生成js描述的AST以及解析AST对象,可以参考:

https://github.com/ariya/esprima 代码生成AST

https://github.com/substack/n... 从AST中提取reqiure

https://github.com/Constellat... AST生成代码

4.得到每个模块的依赖关系,生成一个依赖字典

从上面的步骤,我们已经可以获取到每个模块的依赖关系,因此可以生成一个以id为键的模块依赖字典,browerify生成的字典示例如下(根据之前的范例代码生成):

(function e(t,n,r){
    /*
    function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;
    if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");
    throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];
        return s(n?n:e)},l,l.exports,e,t,n,r)}
        return n[o].exports}var i=typeof require=="function"&&require;
        for(var o=0;o<r.length;o++)s(r[o]);return s */

})(
{
    1:[function(require,module,exports){

        var b = require('./b');

        function a() {
            b();
            console.log("a.js");
        }
        a();
        },
        {"./b":2}
    ],
    2:[function(require,module,exports){
        function b() {
            console.log("b.js");
        }
        module.exports = b;
        },
        {}
    ]
}, //第一个参数是一个对象,每一个数字key都代表模块的id,
   //每一个key值对应的是长度为2的数组。
   //key值对应数组的第一项为某个模块,第二项为该模块依赖的模块。
{},//第二个模块几乎总是为空,如果存在值则为map
[1]//第三个参数是一个数组 指定入口模块id
)

该函数传入了3个参数,第一个参数是一个对象;第二个参数是一个对象,为空;第三个参数是一个数组:[1]。

5.包装每个模块(传入依赖字典以及自己实现的export和require函数),生成用于执行的js

拥有了上面的依赖字典之后,我们相当于知道了代码中的依赖关系。为了让代码能执行,最后一步就是实现浏览器中并不支持的export和require。因此我们需要对原有的模块代码进行包装,就像上面的代码那样,外层会传入自己实现的export和require函数。

然而,应该怎样实现export和require呢?

export很简单,我们只要创建一个对象作为该模块的export就可以。

对于require,其实我们已经拥有了依赖字典,所以要做的也很简单了,只需要根据传入的模块名,根据依赖字典找到所依赖的模块函数,然后执行,一直重复下去(递归执行这个过程)。

(function e(t,n,r){
    function s(o,u){
        if(!n[o]){
            if(!t[o]){
                var a=typeof require=="function"&&require;
                if(!u&&a)
                    return a(o,!0);
                if(i)
                    return i(o,!0);

                var f=new Error("Cannot find module '"+o+"'");
                throw f.code="MODULE_NOT_FOUND",f
            }
            var l=n[o]={exports:{}};
            t[o][0].call(l.exports,function(e){
                var n=t[o][1][e];
                return s(n?n:e)
            },l,l.exports,e,t,n,r)
        }
        return n[o].exports
    }
    var i=typeof require=="function"&&require;
    for(var o=0;o<r.length;o++)
        s(r[o]);
    return s
})

我们主要关注的

 var l=n[o]={exports:{}};
     t[o][0].call(l.exports,function(e){
         var n=t[o][1][e];
         return s(n?n:e)
       },l,l.exports,e,t,n,r)

部分,其中t是传入的依赖字典(之前提到的那块代码),n是一个空对象,用于保存所有新创建的模块(export对象),对比之前的依赖字典来看就比较清晰了:

首先我们创建module对象(包含一个空对象export),并分别把module和export传入模块函数作为浏览器自己实现的module和export,然后,我们自己实现一个require函数,该函数获取模块名,并递归寻找依赖的模块执行,最后获取到所有被依赖到的模块对象,这个也是browerify生成的js在运行中的整个执行过程。


shots
56 声望2 粉丝

前端开发前进者


下一篇 »
js基础篇

引用和评论

0 条评论