我建立了一個簡單的 JavaScript 捆綁器,結果比我預期的要容易得多。我將分享我在這篇文章中學到的所有知識。
編寫大型應用程式時,最好將 JavaScript 原始碼劃分為單獨的 js 文件,但是使用多個腳本標籤將這些文件添加到 html 文件中會帶來新問題,例如
全域命名空間的污染。
比賽條件。
模組捆綁器將不同檔案中的原始程式碼合併到一個大檔案中,幫助我們享受抽象的好處,同時避免缺點。
模組捆綁器通常分兩步驟完成。
如前所述,我們在這裡
我們將這樣做(前面是 JavaScript 程式碼)
在文字編輯器中建立一個bundler.js 檔案並加入以下程式碼:
bundler 函數是我們bundler 的主要入口。它獲取檔案(入口檔案)的路徑並傳回一個字串(捆綁包)。在其中,它使用 createDependencyGraph 函數來產生依賴圖。
createDependencyGraph 函數取得入口檔案的路徑。它使用 createModule 函數產生此文件的模組表示。
createAsset 函數會取得檔案的路徑並將其內容讀取到字串中。然後該字串被解析為抽象語法樹。抽象語法樹是原始碼內容的樹表示。它可以比喻為 html 文件的 DOM 樹。這使得在程式碼上運行一些功能變得更容易,例如搜尋等。
我們使用babylon解析器從模組建立一個ast。
接下來,在 babel 核心轉譯器的幫助下,我們將程式碼內容轉換為 es2015 之前的語法,以實現跨瀏覽器相容性。
然後使用 babel 中的特殊函數遍歷 ast 來尋找原始檔案的每個導入聲明(依賴項)。
然後我們將這些依賴項(相對檔案路徑的字串文字)推送到依賴項數組。
我們也創建一個 id 來唯一標識該模組並且
最後我們回傳一個代表該模組的物件。此模組包含一個 id、字串格式的檔案內容、依賴項陣列和絕對檔案路徑。
回到 createDependencyGraph 函數,我們現在可以開始產生圖的過程。我們的圖表是一個物件數組,每個物件代表我們應用程式中使用的每個原始檔案。
我們使用入口模組初始化圖表,然後循環它。儘管它只包含一項,但我們透過存取入口模組(以及我們將添加的其他模組)的依賴項數組來將項目新增到數組的末尾。
dependency 陣列包含模組所有相依性的相對檔案路徑。此數組被循環,對於每個相對檔案路徑,首先解析絕對路徑並用於建立新模組。該子模組被推到圖的末尾,並且該過程重新開始,直到所有依賴項都已轉換為模組。
此外,每個模組都給出一個映射對象,該對象簡單地將每個依賴項相對路徑映射到子模組的 id。
對每個依賴項執行檢查模組是否已存在,以防止模組重複和無限循環依賴。
最後我們返回我們的圖表,它現在包含我們應用程式的所有模組。
依賴圖完成後,產生套件將涉及兩個步驟
We have to convert our module objects to strings so we can be able to write them into the bundle.js file. We do this by initializing moduleString as an empty string. Next we loop through our graph appending each module into the module string as key value pairs, with the id of a module being the key and an array containing two items: first, the module content wrapped in function (to give it scope as stated earlier) and second an object containing the mapping of its dependencies.
const wrapModules = (graph)=>{ let modules = ‘’ graph.forEach(mod => { modules += `${http://mod.id}: [ function (require, module, exports) { ${mod.code} }, ${JSON.stringify(mod.mapping)}, ],`; }); return modules }
Also to note, the function wrapping each module takes a require, export and module objects as arguments. This is because these don’t exist in the browser but since they appear in our code we will create them and pass them into these modules.
This is code that will run immediately the bundle is loaded, it will provide our modules with the require, module and module.exports objects.
const bundle = (graph)=>{ let modules = wrapModules(graph) const result = ` (function(modules) { function require(id) { const [fn, mapping] = modules[id]; function localRequire(name) { return require(mapping[name]); } const module = { exports : {} }; fn(localRequire, module, module.exports); return module.exports; } require(0); })({${modules}})`; return result; }
We use an immediately invoked function expression that takes our module object as an argument. Inside it we define our require function that gets a module from our module object using its id.
It constructs a localRequire function specific to a particular module to map file path string to id. And a module object with an empty exports property
It runs our module code, passing the localrequire, module and exports object as arguments and then returns module.exports just like a node js module would.
Finally we call require on our entry module (index 0).
To test our bundler, in the working directory of our bundler.js file create an index.js file and two directories: a src and a public directory.
In the public directory create an index.html file, and add the following code in the body tag: