Javascriptモジュール化指北


前言
Webテクノロジーの発展と依存インフラストラクチャの整備に伴い、フロントエンドの分野はブラウザからサービス側(Node.js)、デスクトップ側(PC、Android、iOS)、さらにはIoTに拡大し、JavaScriptはこれらのアプリケーションのコア部分を搭載し、その規模化と複雑さの倍増に伴い、そのソフトウェアエンジニアリングシステムも構築され(協同開発、ユニットテスト、需要と欠陥管理など)、モジュール化プログラミングの需要は日増しに切実になっている.
JavaScriptのモジュール化プログラミングに対するサポートはまだ規範化されておらず、この重任に耐えられない.一時、江湖侠士は身を挺して出て、一路荊を羽織って棘を斬り、刀耕火種から未来向けのモジュール化案に移行した.
コンセプト
モジュール化されたプログラミングは、いくつかの__を組み合わせることです.相対的に独立して多重化可能なモジュール_機能の実現を行い、その最も核心的な2つの部分は__である.モジュールを定義_および_導入モジュール_;
  • モジュールを定義する場合、各モジュール内部の実行ロジックは外部に感知されず、一部の方法とデータを導出(露出)するだけである.
  • がモジュールを導入すると、同期/非同期で導入するコードをロードし、その露出した方法とデータを実行し、取得する.

  • 刀耕火種
    JavaScript言語レベルではモジュール化されたソリューションは提供されていませんが、これを利用してオブジェクト向けの言語プロパティ、追加_デザインパターン_加持は、簡単なモジュール化されたアーキテクチャを実現することができます.古典的な一例は単例モードモードを利用してモジュール化を実現し、モジュールをより良いパッケージ化することができ、一部の情報だけをモジュールを使用する必要がある場所に暴露することができる.
    // Define a module
    var moduleA = (function ($, doc) {
      var methodA = function() {};
      var dataA = {};
      return {
        methodA: methodA,
        dataA: dataA
      };
    })(jQuery, document);
    
    // Use a module
    var result = moduleA.mehodA();

    直感的に見ると、直ちに関数(IIFE)を実行することによって依存を宣言し、データを導出することは、現在のモジュール化スキームと大きな違いはないが、本質的には千差万別で、満たすことができないいくつかの重要な特性がある.
  • モジュールを定義する場合、宣言された依存は強制的に自動的に導入されない.すなわち、モジュールを定義する前に、依存するモジュールコードを手動で導入しなければならない.
  • モジュールを定義すると、そのコードはすでに実行プロセスを完了し、オンデマンドロードを実現できない.
  • ファイル間でモジュールを使用する場合は、モジュールをグローバル変数(window)にマウントする必要があります.

  • AMD&CMD二分天下
    余談:年代が古いため、この2つのモジュール化案は次第に歴史の舞台から薄れ、具体的な特性はもう詳しく話さない.
    「刀耕火種」時代に残された需要を解決するために、AMDとCMDのモジュール化規範が登場し、ブラウザ側での非同期モジュール化プログラミングの需要を解決した.その最も核心的な原理はscriptとイベントリスニングを動的にロードすることによってモジュールを非同期にロードすることである.
    AMDとCMDの最も代表的な2つの作品はそれぞれrequireに対応する.jsとsea.js;主な違いは、require.jsのデフォルトは宣言時に実行する、sea.jsは怠け者のロードとオンデマンドの使用を推奨する.なお,CMD仕様の書き方はCommonJSに極めて近いので,少し修正するだけでCommonJSで利用できる.次のCaseを参考にすると理解に役立ちます.
    // AMD
    define(['./a','./b'], function (moduleA, moduleB) {
      //     
      moduleA.mehodA();
      console.log(moduleB.dataB);
      //     
      return {};
    });
     
    // CMD
    define(function (requie, exports, module) {
      //     
      var moduleA = require('./a');
      moduleA.mehodA();     
    
      //     
      if (needModuleB) {
        var moduleB = requie('./b');
        moduleB.methodB();
      }
      //     
      exports = {};
    });

    CommonJS
    2009年ryリリースNode.jsの最初のバージョンで、CommonJSはその中の最も核心的な特性の一つとして、サービス端の下のシーンに適用されます.数年来の考察と時間の洗礼、および先端工程化の十分な支持、CommonJSはNodeに広く運用されている.jsとブラウザ;
    // Core Module
    const cp = require('child_process');
    // Npm Module
    const axios = require('axios');
    // Custom Module
    const foo = require('./foo');
    
    module.exports = { axios };
    exports.foo = foo;

    きかく
  • module(Object):モジュール自体
  • exports(*):モジュールのエクスポート部分、すなわち露出したコンテンツ
  • require(Function):モジュールの関数をロードし、ターゲットモジュールのエクスポート値(ベースタイプはコピー、参照タイプは浅いコピー)を取得し、組み込みモジュール、npmモジュール、カスタムモジュール
  • をロードできます.
    インプリメンテーション
    1、モジュール定義
    デフォルトは任意です.node .js .jsonファイルはすべて規範に合致するモジュールである.
    2、モジュールの導入
    まず、キャッシュ(require.cache)からモジュールを優先的に読み込み、キャッシュにヒットしなかった場合はパス解析を行い、異なるタイプのモジュールで処理します.
  • はモジュールを内蔵し、メモリから直接ロードする.
  • 外部モジュールは、まずファイルアドレスの位置決めを行い、その後コンパイルと実行を行い、最終的に対応する導出値を得る.

  • ここで、Nodeは、コンパイル中に取得したJavaScriptファイルの内容を次のようにまとめました.
    (function (exports, require, module, __filename, __dirname) {
        var circle = require('./circle.js');
        console.log('The area of a circle of radius 4 is ' + circle.area(4));
    });

    特性の概要
  • モジュール宣言と論理導入を同期して実行し、ループ依存などの複雑な依存参照を分析する際に注意しなければならない.
  • キャッシュメカニズムで、性能がより優れ、同時にメモリの占有量を制限した.
  • Moduleモジュールは改造の柔軟性が高く、熱更新、任意のファイルタイプモジュールのサポートなどのカスタムニーズを実現することができます.

  • ESModule(推奨使用)
    ES Moduleは言語レベルのモジュール化案であり、ES 2015によって提案され、その規範はCommonJSに比べて、導出された値はいずれも複数の属性や方法を備えた対象と見なすことができ、互いに互換性を実現することができる.しかし、書き方はES Moduleがより簡潔で、Pythonに近い.
    import fs from 'fs';
    import color from 'color';
    import service, { getArticles } from '../service'; 
    
    export default service;
    export const getArticles = getArticles;

    主な違いは次のとおりです.
  • ESModuleは静的コード分析、すなわちコードコンパイル時にモジュールのロードを行い、実行時より前に依存関係が確定した(ループ参照の問題を解決できる).
  • ESModuleキーワード:import exportおよび固有のdefaultキーワードで、デフォルトのエクスポート値を決定します.
  • ESModuleで導出された値は であり、基礎タイプと複雑タイプにかかわらず、CommonJSでrequireは値のコピーであり、複雑タイプは値の浅いコピーである.
  • // a.js
    export let a = 1;
    export function caculate() {
      a++;
    };
    
    // b.js
    import { a, caculate } from 'a.js';
    
    console.log(a); // 1
    caculate();
    console.log(a); // 2
    
    a = 2; // Syntax Error: "a" is read-only

    UMD
    層の自己実行関数を通じて各種のモジュール化規範の書き方を互換化し、AMD/CMD/CommonJSなどのモジュール化規範を互換化し、コードを貼ることは千言万語より優れている.特に注意しなければならないのはESModuleが静的コードを分析するため、このような運行時の方案は使用できない.この時CommonJSを通じて互換化する.
    (function (global, factory) {
      if (typeof exports === 'object') {   
        module.exports = factory();
      } else if (typeof define === 'function' && define.amd) {
        define(factory);
      } else {
        this.eventUtil = factory();
      }
    })(this, function (exports) {
     ​ // Define Module
      Object.defineProperty(exports, "__esModule", {
        value: true
      });
      exports.default = 42;
    });

    コンストラクションツールでの実装
    ブラウザ環境でモジュール化されたコードを実行するためには、いくつかのモジュール化パッケージのツールを使用してパッケージ化する必要があります(webpackを例にとります).プロジェクトエントリを定義した後、依存の分析を迅速に行い、その後、すべての依存モジュールをブラウザ兼容の対応するモジュール化規範の実現に変換します.
    モジュール化の基礎
    上の紹介から、私たちはすでにその規範と実現に対して一定の理解を持っています.ブラウザではCommonJS仕様を実装するには、module/exports/require/globalといういくつかの属性を実装する必要があります.ブラウザではファイルシステムにアクセスできないため、requireプロセスにおけるファイルの位置決めは、対応するJSフラグメントをロードするように変更する必要があります(webpackでは、関数パラメータによる依存の導入が採用されています).具体的なインプリメンテーションは、tiny-browser-requireを参照してください.
    Webpackパッケージ化されたコードスナップショットは以下の通りで、コメントのタイミングを注意してください.
    (function (modules) {
      // The module cache
      var installedModules = {};
      // The require function
      function __webpack_require__(moduleId) {}
      return __webpack_require__(0); // ---> 0
    })
    ({
      0: function (module, exports, __webpack_require__) {
        // Define module A
        var moduleB = __webpack_require__(1); // ---> 1
      },
      1: function (module, exports, __webpack_require__) {
        // Define module B
        exports = {}; // ---> 2
      }
    });

    実際、ES Moduleの処理はCommonJSとほぼ同じで、モジュールの定義やモジュールの導入時に処理されるだけです.EsModule IDは、構文の違いと互換性があります.
    非同期および拡張
    1、ブラウザ環境では、ネットワーク資源が大きく制限されているため、パッケージ化されたファイルは体積が大きいと、ページ性能の損失が大きいため、構築されたターゲットファイルを分割する必要があり、モジュールも動的ロードをサポートする必要がある.
    Webpackは2つの方法を提供しています.Ensure()とimport()はモジュールのダイナミックロードを推奨します.その原理については、前述したAMD&CMDの見解とほぼ同じです.import()は実行後、Promiseオブジェクトを返します.ここでの作業は、ダイナミックにscriptラベルを追加することにほかならませんが、onload/onerrorイベントでさらに処理されます.
    2、require関数は完全にカスタマイズされているため、requireを修正するなど、モジュール化でより多くの特性を実現することができます.resolveまたはModule.extensionsは、css/.jsx/.vue/ピクチャなどのファイルもモジュール化に使用できます.
    付録1:特性一覧
    モジュラ仕様
    ロードモード
    ロードタイミング
    実行環境
    コメント
    AMD
    非同期
    実行時
    エクスプローラ
    CMD
    非同期
    実行時
    エクスプローラ
    静解析に基づいて依存し、require時にmodule ready
    CommonJS
    同期/非同期
    実行時
    ブラウザ/ノード
    ES Module
    同期/非同期
    コンパイルフェーズ
    ブラウザ/ノード
    import()による非同期ロード
    付録2:参考
  • AMDモジュール化仕様:https://github.com/amdjs/amdjs-api/wiki/AMD
  • CMDモジュール定義仕様:https://github.com/seajs/seajs/issues/242
  • webpackモジュール関連ドキュメント:https://webpack.js.org/concepts/modules/
  • ブラウザがCommonJSモジュールをロードする原理と実現:http://www.ruanyifeng.com/blog/2015/05/commonjs-in-browser.html