博客 / 詳情

返回

browserify 運行原理

摘抄於 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在運行中的整個執行過程。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.