browserify運行原理分析

目前對於前端工程師而言,若是隻針對瀏覽器編寫代碼,那麼很簡單,只須要在頁面的script腳本中引入所用js就能夠了。html

可是某些狀況下,咱們可能須要在服務端也跑一套相似的邏輯代碼,考慮以下這些情景(以node做爲後端爲例):前端

1.spa的應用,須要同時支持服務端直出頁面以及客戶端pjax拉取數據渲染,客戶端和服務器公用一套渲染模板並執行大部分相似的邏輯。node

2.一個經過websocket對戰的遊戲,客戶端和服務端可能須要進行相似的邏輯計算,兩套代碼分別用於對用戶客戶端的展現以及服務端實際數值的計算。git

 

這些狀況下,極可能但願咱們客戶端代碼的邏輯可以同時無縫運行在服務端。github

 

解決方法1:UMDweb

一種解決方法是使用UMD的方式,前端使用requirejs,同時兼容nodejs的狀況,例如:express

(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

相關文章
相關標籤/搜索