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