首頁  >  文章  >  web前端  >  我寫了一個模組捆綁器。筆記等

我寫了一個模組捆綁器。筆記等

王林
王林原創
2024-07-25 03:10:43427瀏覽

I wrote a module bundler. notes, etc

我建立了一個簡單的 JavaScript 捆綁器,結果比我預期的要容易得多。我將分享我在這篇文章中學到的所有知識。

在編寫大型應用程式時,最好將 JavaScript 原始程式碼劃分為單獨的 js 文件,但是使用多個腳本標籤將這些文件添加到 html 文件中會帶來新問題,例如

  • 全域命名空間的污染。

  • 比賽條件。

模組捆綁器將不同檔案中的原始程式碼合併到一個大檔案中,幫助我們享受抽象的好處,同時避免缺點。

模組捆綁器通常分兩步驟完成此操作。

  1. 從入口檔案開始尋找所有的JavaScript來源檔案。這稱為依賴解析,產生的映射稱為依賴圖。
  2. 使用依賴圖產生捆綁包:可以在瀏覽器中執行的一大串 JavaScript 原始碼。這可以寫入文件並使用腳本標籤新增到 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。
對每個依賴項執行檢查模組是否已存在,以防止模組重複和無限循環相依性。
最後我們返回我們的圖表,它現在包含我們應用程式的所有模組。

捆綁

依賴圖完成後,產生套件將涉及兩個步驟

  1. Wrapping each module in a function. This creates the idea of each module having its own scope
  2. Wrapping the module in a runtime.

Wrapping each module

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.

Creating the runtime

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中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn