ホームページ  >  記事  >  ウェブフロントエンド  >  モジュールバンドラーを書きました。メモなど

モジュールバンドラーを書きました。メモなど

王林
王林オリジナル
2024-07-25 03:10:43427ブラウズ

I wrote a module bundler. notes, etc

シンプルな JavaScript バンドラーを構築しましたが、予想よりもはるかに簡単であることがわかりました。私が学んだすべてをこの投稿で共有します。

大規模なアプリケーションを作成する場合、JavaScript ソース コードを個別の js ファイルに分割することをお勧めします。ただし、複数のスクリプト タグを使用してこれらのファイルを HTML ドキュメントに追加すると、

などの新たな問題が発生します。
  • グローバル名前空間の汚染。

  • 競合状態。

モジュール バンドラーは、さまざまなファイルのソース コードを 1 つの大きなファイルに結合し、欠点を回避しながら抽象化の利点を享受できるようにします。

モジュール バンドラーは通常、これを 2 つのステップで実行します。

  1. エントリ ファイルから始めて、すべての JavaScript ソース ファイルを検索します。これは依存関係の解決として知られており、生成されたマップは依存関係グラフと呼ばれます。
  2. 依存関係グラフを使用してバンドル (ブラウザーで実行できる JavaScript ソース コードの大きな文字列) を生成します。これはファイルに書き込まれ、script タグを使用して 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 関数に戻り、グラフを生成するプロセスを開始できます。私たちのグラフはオブジェクトの配列であり、各オブジェクトはアプリケーションで使用される各ソース ファイルを表します。
エントリーモジュールを使用してグラフを初期化し、それをループします。これには項目が 1 つしか含まれていませんが、エントリ モジュール (および追加する他のモジュール) の依存関係配列にアクセスして、配列の末尾に項目を追加します。

依存関係の配列には、モジュールのすべての依存関係の相対ファイル パスが含まれます。配列はループされ、相対ファイル パスごとに絶対パスが最初に解決され、新しいモジュールの作成に使用されます。この子モジュールはグラフの最後にプッシュされ、すべての依存関係がモジュールに変換されるまでプロセスが最初からやり直します。
また、各モジュールは、各依存関係の相対パスを子モジュールの ID に単純にマップするマッピング オブジェクトを与えています。
モジュールの重複や無限循環依存関係を防ぐために、モジュールが既に存在するかどうかのチェックが各依存関係に対して実行されます。
最後に、アプリケーションのすべてのモジュールを含むグラフを返します。

バンドル

依存関係グラフが完了したら、バンドルを生成するには 2 つのステップが必要になります

  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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。