日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
只用90行代碼實(shí)現(xiàn)模塊打包器

大家好,我卡頌。

今天來聊聊如何用90行代碼實(shí)現(xiàn)一個(gè)現(xiàn)代JS模塊打包器。

我們的打包器雖然迷你,但是實(shí)現(xiàn)了webpack的核心功能。

而且,我知道你看到大段代碼頭疼,所以這篇文章都是圖??赐旮信d趣的話,這里是完整代碼的倉庫地址[1],只有90行代碼哦。

讓我們愉快的開始吧。

生成依賴圖

如果應(yīng)用是個(gè)毛線團(tuán)的話,那么入口文件就是線頭。打包器要做的第一件事是:

順著線頭開始濾清整條線的走向

假設(shè)入口文件是entry.js:

 
 
 
 
  1. // entry.js 
  2.  
  3. import a from './a.js'; 
  4. import b from './b.js'; 
  5.  
  6. console.log(a, b); 

他依賴了a.js與b.js。 

em... 有點(diǎn)太簡(jiǎn)陋了,讓我們?cè)贁U(kuò)展下a.js與b.js:

 
 
 
 
  1. // a.js 
  2. import c from './c.js'; 
  3.  
  4. // ... 
 
 
 
 
  1. // b.js 
  2. import d from './d.js'; 
  3. import e from './e.js'; 
  4.  
  5. // ... 

所以整個(gè)依賴關(guān)系是這樣:

打包器會(huì)從入口文件開始,嘗試建立模塊(即js文件)間的依賴關(guān)系,也就是剛才我們講的「順著線頭開始濾清整條線的走向」。

模塊間的依賴關(guān)系可以通過分析模塊代碼中的import 聲明語句得知。

為了能分析import 聲明語句,可以使用babel等編譯工具將模塊代碼分解為AST(抽象語法樹)。

遍歷AST,類型為ImportDeclaration的節(jié)點(diǎn)就是import聲明語句。

最后,我們將AST重新轉(zhuǎn)換為可執(zhí)行的目標(biāo)代碼,可能還需要根據(jù)代碼要執(zhí)行的宿主環(huán)境(一般為瀏覽器)對(duì)代碼做一些轉(zhuǎn)換。

比如,瀏覽器不支持import './a.js'這樣的ESM語法,那么我們需要將所有ESM語法轉(zhuǎn)為CJS語法。

 
 
 
 
  1. // 源代碼 
  2. import './a.js'; 
  3.  
  4. // 轉(zhuǎn)換后 
  5. require('./a.js'); 

所以,對(duì)于任一模塊(js文件),會(huì)經(jīng)歷:

右邊包含目標(biāo)代碼和模塊間依賴關(guān)系的數(shù)據(jù)結(jié)構(gòu)被稱為asset。

每個(gè)asset可以通過模塊間依賴關(guān)系找到依賴的模塊,重復(fù)這一過程,生成新的asset,最終形成整個(gè)應(yīng)用所有asset間的依賴關(guān)系:

應(yīng)用完整的依賴關(guān)系被稱為「依賴圖」(dependency graph)。

打包代碼

接下來,只需要遍歷「依賴圖」,將所有asset的目標(biāo)代碼打包在一起就行。

所有代碼會(huì)被打包在一個(gè)「立即執(zhí)行函數(shù)」中:

 
 
 
 
  1. (function(modules) { 
  2.   // 打包好的代碼 
  3. })(modules) 

modules中保存了所有asset及他們之間的依賴關(guān)系。

如果你對(duì)modules的細(xì)節(jié)感興趣,可以去文末倉庫里翻代碼

剛才說過,asset的目標(biāo)代碼是CJS規(guī)范的,類似:

 
 
 
 
  1. // entry.js 
  2.  
  3. require('./a.js'); 
  4. require('./b.js'); 

這意味著我們需要實(shí)現(xiàn):

  • require方法(用于引入依賴的其他asset的目標(biāo)代碼)
  • module對(duì)象(用于保存當(dāng)前asset的目標(biāo)代碼執(zhí)行后導(dǎo)出的數(shù)據(jù))

同時(shí),為了防止不同asset的目標(biāo)代碼中的變量互相污染,每個(gè)目標(biāo)代碼需要獨(dú)立的作用域。

我們將目標(biāo)代碼包裹在函數(shù)中:

 
 
 
 
  1. // 我們操作的是字符串模版 
  2. `function (require, module, exports) { 
  3.   ${asset.code} 
  4. }` 

所以,最終打包的結(jié)果為:

 
 
 
 
  1. (function(modules) { 
  2.   function require() {// ...} 
  3.    
  4.   require(入口asset的ID) 
  5. })(modules) 

這段字符串被包裹在瀏覽器  

這段字符串被包裹在瀏覽器