我建立了一個簡單的 JavaScript 捆綁器,結果比我預期的要容易得多。我將分享我在這篇文章中學到的所有知識。
在編寫大型應用程式時,最好將 JavaScript 原始程式碼劃分為單獨的 js 文件,但是使用多個腳本標籤將這些文件添加到 html 文件中會帶來新問題,例如
全域命名空間的污染。
比賽條件。
模組捆綁器將不同檔案中的原始程式碼合併到一個大檔案中,幫助我們享受抽象的好處,同時避免缺點。
模組捆綁器通常分兩步驟完成此操作。
如前所述,我們在這裡
我們將這樣做(前面是 JavaScript 程式碼)
在文字編輯器中建立一個bundler.js 檔案並加入以下程式碼:
const bundler = (entry)=>{ const graph = createDependencyGraph(entry) const bundle = createBundle(graph) return bundle }
捆綁器功能是我們捆綁器的主要入口。它獲取檔案(入口檔案)的路徑並傳回一個字串(捆綁包)。在其中,它使用 createDependencyGraph 函數來產生依賴圖。
const createDependencyGraph = (path)=>{ const entryModule = createModule(path) /* other code */ }
createDependencyGraph 函數取得入口檔案的路徑。它使用 createModule 函數產生此文件的模組表示。
let ID = 0 const createModule = (filename)=>{ const content = fs.readFileSync(filename) const ast = babylon.parse(content, {sourceType: “module”}) const {code} = babel.transformFromAst(ast, null, { presets: ['env'] }) const dependencies = [ ] const id = ID++ traverse(ast, { ImportDeclaration: ({node})=>{ dependencies.push(node.source.value) } } return { id, filename, code, dependencies } }
createAsset 函數取得檔案的路徑並將其內容讀取到字串中。然後該字串被解析為抽象語法樹。抽象語法樹是原始碼內容的樹表示。它可以比喻為 html 文件的 DOM 樹。這使得在程式碼上運行一些功能變得更容易,例如搜尋等。
我們使用babylon解析器從模組建立一個ast。
接下來,在 babel 核心轉譯器的幫助下,我們將程式碼內容轉換為 es2015 之前的語法,以實現跨瀏覽器相容性。
然後使用 babel 中的特殊函數遍歷 ast 來尋找原始檔案的每個導入聲明(依賴項)。
然後我們將這些依賴項(相對檔案路徑的字串文字)推送到依賴項數組。
我們也建立一個 id 來唯一標識該模組並且
最後我們回傳一個代表該模組的物件。此模組包含一個 id、字串格式的檔案內容、依賴項陣列和絕對檔案路徑。
const createDependencyGraph = (path)=>{ const entryModule = createModule(path) const graph = [ entryModule ] for ( const module of graph) { module.mapping = { } module.dependencies.forEach((dep)=>{ let absolutePath = path.join(dirname, dep); let child = graph.find(mod=> mod.filename == dep) if(!child){ child = createModule(dep) graph.push(child) } module.mapping[dep] = child.id }) } return graph }
回到 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:
<!DOCTYPE html> <html> <head> <title>Module bundler</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> </head> <body> <div id='root'></div> <script src= ‘./bundler.js> <script> </body> </html In the src directory create a name.js file and add the following code
const name = “David”
export default name
also create a hello.js file and add the following code
import name from ‘./name.js’
const hello = document.getElementById(“root”)
hello.innerHTML = “hello” + name
Lastly in the index.js file of the root directory import our bundler, bundle the files and write it to a bundle.js file in the public directory
const createBundle = require(“./bundler.js”)
const run = (output , input)=>{
let bundle = creatBundle(entry)
fs.writeFileSync(bundle, ‘utf-8’)
}
run(“./public/bundle.js”, “./src/hello.js”)
Open our index.html file in the browser to see the magic. In this post we have illustrated how a simple module bundler works. This is a minimal bundler meant for understanding how these technologies work behind the hood. please like if you found this insightful and comment any questions you may have.
以上是我寫了一個模組捆綁器。筆記等的詳細內容。更多資訊請關注PHP中文網其他相關文章!