摘抄於 Cson的博客
正好看browserify。。。。。看了幾遍不錯的博文。找到以爲很不錯的。html
使用browerify,使代碼能同時運行於服務端和瀏覽器端。前端
Browserify 可讓你使用相似於 node 的 require() 的方式來組織瀏覽器端的 Javascript 代碼,經過預編譯讓前端 Javascript 能夠直接使用 Node NPM 安裝的一些庫,支持CommonJS。node
階段1:預編譯階段git
1.從入口模塊開始,分析代碼中require函數的調用
2.生成AST
3.根據AST找到每一個模塊require的模塊名
4.獲得每一個模塊的依賴關係,生成一個依賴字典
5.包裝每一個模塊(傳入依賴字典以及本身實現的export和require函數),生成用於執行的jsgithub階段2:執行階段express
從入口模塊開始執行,遞歸執行所require的模塊,獲得依賴對象。數組
因爲瀏覽器端並無原生的require函數,因此全部require函數都是須要咱們本身實現的。所以第一步咱們須要知道一個模塊的代碼中,哪些地方用了require函數,依賴了什麼模塊。瀏覽器
browerify實現的原理是爲代碼文件生成AST,而後根據AST找到require函數依賴的模塊。函數
文件代碼ui
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\"" } ] }
生成了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生成代碼
從上面的步驟,咱們已經能夠獲取到每一個模塊的依賴關係,所以能夠生成一個以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]。
擁有了上面的依賴字典以後,咱們至關於知道了代碼中的依賴關係。爲了讓代碼能執行,最後一步就是實現瀏覽器中並不支持的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在運行中的整個執行過程。