首页 > 代码库 > browserify运行原理分析

browserify运行原理分析

目前对于前端工程师而言,如果只针对浏览器编写代码,那么很简单,只需要在页面的script脚本中引入所用js就可以了。

但是某些情况下,我们可能需要在服务端也跑一套类似的逻辑代码,考虑如下这些情景(以node作为后端为例):

1.spa的应用,需要同时支持服务端直出页面以及客户端pjax拉取数据渲染,客户端和服务器公用一套渲染模板并执行大部分类似的逻辑。

2.一个通过websocket对战的游戏,客户端和服务端可能需要进行类似的逻辑计算,两套代码分别用于对用户客户端的展示以及服务端实际数值的计算。

 

这些情况下,很可能希望我们客户端代码的逻辑能够同时无缝运行在服务端。

 

解决方法1:UMD

一种解决方法是使用UMD的方式,前端使用requirejs,同时兼容nodejs的情况,例如:

(function (window, factory) {    if (typeod exports === ‘object‘) {             module.exports = factory();    } else if (typeof define === ‘function‘ && define.amd) {             define(factory);    } else {             window.eventUtil = factory();    }})(this, function () {    //module ...});

 

解决方案2:使用browerify,使代码能同时运行于服务端和浏览器端。

 

什么是browserify?

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

 

例如我们可以这样写js,同时运行在服务端和浏览器中:

mo2.js:

exports.write2 = function(){    //write2}

mo.js:

var t = require("./mo2.js");exports.write = function(){    t.write2();}

test.js:

var mo = require("./mo.js");mo.write();

 

代码可以完全已node的形式编写。

 

原理分析:

总体过程其实可以分为以下几个步骤:

 

阶段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": []            }        }    ]}

 

可以看到我们代码中调用的require函数,对应AST中的对象为上面红字部分。

 

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/node-detective 从AST中提取reqiure

https://github.com/Constellation/escodegen AST生成代码

 

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

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

{    1:[    function(require,module,exports){        var t = require("./mo2.js");        exports.write = function(){            document.write("test1");            t.write2();        }    },    {"./mo2.js":2}    ],    2:[    function(require,module,exports){        exports.write2 = function(){            document.write("=2=");        }    },    {}    ],    3:[    function(require,module,exports){        var mo = require("./mo.js");        mo.write();    },    {"./mo.js":1}    ]}

 

 字典记录了拥有那些模块,以及模块各自依赖的模块。

 

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

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

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

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

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

在browerify生成的js中,会添加以下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})

 

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

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

 

感谢围观,转载请标明出处:http://www.cnblogs.com/Cson/p/4039144.html

 

browserify运行原理分析