独自のモジュールバンドルを書く


私は、ビルトイン・ツールが際限なくおもしろいとわかります、そして、我々は今日、多くの' emを持ちそうです.現在、フレームワークを選択するのは簡単です.
最適なビルドツールを選択するよりも.あなたがフロントエンドの開発者であるならば
モジュール周辺のバンドルまたはビルドツール.あなたはどのようにそれぞれのビルドツールが動作するだろうか?どのようにビルドツールを学びましょう
基本的なものを構築することで、内部で動作します.
注:このブログはTan Li Haun's blog on module bundlerからインスピレーションを得ました.彼のブログでは、彼はネットパックの方法でバンドルを構築しました.

モジュールバンドルは何ですか?


バンドルは、単一の複雑な/大規模なJavaScriptファイルに、我々が書くJavaScriptコードの異なる部分を束ねるのを助けます.
また、JavaScriptではないファイルをサポートするためにLoaderを提供することができますので、イメージ資産、CSSの資産など、また
私たちのJSファイル内にバンドルされ、ブラウザに簡単に提供できます.我々は長い間、これを行う
ブラウザはモジュールシステムをサポートしませんでした.
but it is not completely true now a days.
したがって、ビルドツールにエントリポイントを与えると、依存関係のすべての依存関係とサブ依存性が
束ねられている.
基本的なJavaScriptモジュールバンドルを構築するには、次のことを理解してください.
  • インポートされたファイルの依存関係を解決します.
  • バンドルから未使用の依存関係を削除します.
  • は、含まれるファイルの順序を維持します.
  • NodeRuleモジュールと比較的importモジュールを区別することでimport文を解決します.
  • そこで、我々のコードがバンドルされる2つの方法があります
    ファイルに続いて
    // add.js
    const add = (a, b) => {
      return a + b;
    };
    export default add;
    
    // diff.js
    const diff = (a, b) => {
      return a - b;
    };
    export default diff;
    
    // app.js
    import add from "./add.js";
    import diff from "./diff.js";
    
    console.log(add(1, 2));
    console.log(diff(2, 1));
    

    Webpack


    const modulemap = {
      "add.js": function (exports, require) {
        exports.default = function add(a, b) {
          return a + b;
        };
      },
      "diff.js": function (exports, require) {
        exports.default = function diff(a, b) {
          return a - b;
        };
      },
      "app.js": function (exports, require) {
        const add = require("add.js").default;
        const diff = require("diff.js").default;
    
        console.log(add(1, 2));
        console.log(diff(2, 1));
      },
    };
    
    上記はクリーンアップされたコードclick here、チェックする
    Webpackによる実際のバンドルされたコードを返します.
    つのファイルを追加します.js、diffj . js、およびapp.JSアプリ.JSは最初の2つのモジュールをインポートし、コンソールステートメントも持っています.
    上の例からわかるように
  • Webpackは、モジュールごとにモジュールマップを作成します.マップ名はプロパティ名と内容として作成されました
    プロパティの内部には、各モジュールからのコード付きメソッドがあります.
  • も、各々の方法は輸出をして、各々のモジュールの範囲内で内容を輸入して、輸出するために議論を必要とします.
  • このように我々のdevサーバが起動されると、webpackはエントリパスを使用し、上記のモジュールを作成します
    マップは、バンドルされたコードのサービスを開始します.
  • ロールプ


    const add = (a, b) => {
      return a + b;
    };
    
    const diff = (a, b) => {
      return a - b;
    };
    
    console.log(add(1, 2));
    console.log(diff(2, 1));
    
    一見すると、束のロールアップの方法は、光とまっすぐ前方に、それは
    temporal dead zoneを避けるための依存関係
    そして最後に、エントリポイントは、バンドルされたコードの最後の部分に存在します.したがって、我々はロールアップの方法を模倣しようとすることができます
    このブログに参加すること.

    モジュールバンドル


    以下は、独自のモジュールバンドルをビルドする手順です.
  • 依存関係を持つモジュールグラフを作成します.
  • モジュールグラフに関するモジュールをバンドルします.
  • 束のコードをターゲットの場所に書き込みます.
  • function builder({ input, ouput }) {
      // create module graph
      const moduleGraph = createModuleGraph(input);
      // bundle the modules
      const bundledCode = bundle(moduleGraph);
      // write the bundled code in the output location
      fs.writeFileSync(output, bundledCode, "utf-8");
    }
    

    モジュールグラフの作成


    モジュールのパスについての情報を保持するモジュールクラスを書く必要があります.
    依存性、コンテンツ、ASTなど、私たちは、それぞれの内容を操作するための
    ファイルと依存性を知っている
    check out this blog .ASTの構築
    JavaScriptファイルはapp.jsパッケージを使用します.
    const babel = require("@babel/core");
    
    class ModuleGraph {
      constructor(input) {
        this.path = input;
        // get content of the current module
        this.content = fs.readFileSync(input, "utf-8");
        // will return an ast of the module
        this.ast = babel.parseSync(this.content);
      }
    }
    
    モジュールのASTを得るために、BabelのParseSyncメソッドを使用できます.したがって、上記のクラスを使用してモジュールオブジェクトを作成できます
    すべての必要な情報を.では、モジュール依存グラフの作成方法を見てみましょう.
    function createModuleGraph(input) {
      return new ModuleGraph(input);
    }
    
    このメソッドは依存グラフを作成するために呼び出されます.しかし、上記のModulegraphクラスから、我々は何も持ちません
    依存関係の情報によって、モジュールグラフクラスを少し変更しましょう.
    class ModuleGraph {
      constructor(input) {
        this.path = input;
        this.content = fs.readFileSync(input, "utf-8");
        this.ast = babel.parseSync(this.content);
        // store the dependencies of the current module
        this.dependencies = this.getDependencies();
      }
    
      getDependencies() {
        return (
          this.ast.program.body
            // get import statements
            .filter((node) => node.type === "ImportDeclaration")
            .map((node) => node.source.value)
            // resolve the path of the imports
            .map((currentPath) => resolveRequest(this.path, currentPath))
            // create module graph class for the resolved dependencies
            .map((absolutePath) => createModuleGraph(absolutePath))
        );
      }
    }
    
    上記のコードから、我々はそれを見ることができます
  • ASTからのインポートを取得します.
  • 依存関係のパスを作成し、依存関係のモジュールグラフを作成します.
  • ここで依存性を解決するのは非常に難しいです.
    依存関係を解決するには簡単にするために、使用することによってノードJSモジュールのインポートの解決alogrithmに従うことができます@babel/coreと、その親モジュールとカレントモジュールのdirnameに参加します.
    function resolveRequest(requester, requestedPath) {
      return path.join(path.dirname(requester), requestedPath);
    }
    
    アプリならば.jsは入力として渡され、次のモジュールグラフが作成されます.
    ModuleGraph {
      path: './test/app.js',
      content: 'import add from "./add.js";\n' +
        'import diff from "./diff.js";\n' +
        '\n' +
        'console.log(add(1, 2));\n' +
        'console.log(diff(2, 1));\n',
      ast: Node {
        type: 'File',
        start: 0,
        end: 108,
        loc: SourceLocation {
          start: [Position],
          end: [Position],
          filename: undefined,
          identifierName: undefined
        },
        errors: [],
        program: Node {
          type: 'Program',
          start: 0,
          end: 108,
          loc: [SourceLocation],
          sourceType: 'module',
          interpreter: null,
          body: [Array],
          directives: []
        },
        comments: []
      },
      dependencies: [
        ModuleGraph {
          path: 'test/add.js',
          content: 'const add = (a, b) => {\n  return a + b;\n};\n\nexport default add;\n',
          ast: [Node],
          dependencies: []
        },
        ModuleGraph {
          path: 'test/diff.js',
          content: 'const diff = (a, b) => {\n  return a - b;\n};\n\nexport default diff;\n',
          ast: [Node],
          dependencies: []
        }
      ]
    }
    

    バンドル


    モジュールグラフを作成した後、次のステップはバンドルされたJSコードを作成することです.グラフなので、小さな文章を書きました
    snipetは、モジュールの内容をバンドルされる順序でグラフとストアモジュールの内容を横断します
    実際のモジュールの前に来る-深さの最初の検索-バンドルのrolulup方法)
    function build(graph) {
      let modules = dfs(graph);
    }
    
    function dfs(graph) {
      const modules = [];
      collect(graph, modules);
      return modules;
    
      function collect(module, modules) {
        modules.push(module);
        module.dependencies.forEach((dependency) => collect(dependency, modules));
      }
    }
    
    今、我々はモジュールを集めたので、私たちが内容を連結することができるようにバンドルされなければなりません
    インポート文がまだあります.そこで、Babelのtransformfromastsyncメソッドを使用して、輸出入を削除しようとします
    文.
    function bundle(graph) {
      let modules = collectModules(graph);
      let code = "";
      for (var i = modules.length - 1; i >= 0; i--) {
        let module = modules[i];
        const t = babel.transformFromAstSync(module.ast, module.content, {
          ast: true,
          plugins: [
            function () {
              return {
                visitor: {
                  ImportDeclaration(path) {
                    path.remove();
                  },
                  ExportDefaultDeclaration(path) {
                    path.remove();
                  },
                },
              };
            },
          ],
        });
        code += `${t.code}\n`;
      }
      return code;
    }
    
    ::チップ
    ここでは、入力モジュールのエクスポート文を削除していますが、これは理想的ではありませんので、入力モジュール
    また、単独でそのモジュールのエクスポート宣言を削除しません.
    ::

    3 .ターゲットの場所に書き込む


    最後に、path.joinを使用して、ターゲット位置にバンドルされたコードを書き込むことができます
    出力のディレクトリが存在しているかどうかを出力します(すなわち、出力位置が' dist/index . js 'であれば、
    フォルダが存在します.そこで、スタックオーバーフローから小さなスニペットをコピーしてディレクトリを作成してファイルを作成します.
    存在しない場合は
    function writeFileSyncRecursive(filename, content, charset) {
      const folders = filename.split(path.sep).slice(0, -1);
      if (folders.length) {
        // create folder path if it doesn't exist
        folders.reduce((last, folder) => {
          const folderPath = last ? last + path.sep + folder : folder;
          if (!fs.existsSync(folderPath)) {
            fs.mkdirSync(folderPath);
          }
          return folderPath;
        });
      }
      fs.writeFileSync(filename, content, charset);
    }
    
    現在、fs.writeFileSyncとして入力し、ビルダー機能にwriteFileSyncとして出力すると、次のバンドルされます
    コード
    const diff = (a, b) => {
      return a - b;
    };
    
    const add = (a, b) => {
      return a + b;
    };
    
    console.log(add(1, 2));
    console.log(diff(2, 1));
    
    したがって、我々はロールラップの方法に従って独自のモジュールバンドルを書きました.また、いくつかの追加オプションをサポートすることができますて
    terserを使用してコードの制限とマンリングのように、我々はまた、app.jsをサポートすることができます
    ILIFE式でバンドルをラップすることによってフォーマット.これは、バンドルがどのように動作するかに関する基本的な例ですから
    いくつかのstuffsを通してスキミングしたが、実際にはモジュールのバンドルは非常に複雑で興味深いことを学ぶことができます.
    githubで全体のコードをチェックしてください