[NodeJsシリーズ]NodeJsモジュール機構


注:1.本文に関わるnodejsのソースコードは、特別な説明がなければ、すべてv 10.14.1に基づく.
NodeJsシリーズに興味があれば、WeChat公式アカウントに注目してください.フロントエンドの神盾局またはgithub NodeJsシリーズの記事です.
Nodejsでモジュールの実現
このセクションは主にNodeJsのソースコードに基づいて、モジュールの実装について簡単な概観を行います.requireを使用してモジュールを導入すると、概観的には、2つのステップ:経路分析とモジュールロード
パス解析
パス分析は実はモジュール検索の過程です.resoveFilename関数が実装されます.
一例を通して、説明を展開します.
const http = require('http');
const moduleA = requie('./parent/moduleA');
この例では、コアモジュールhttpとカスタムモジュールmoduleAとの2つの異なるタイプのモジュールを導入する.
コアモジュールの場合、_resolveFilenameは、検索ステップをスキップして、直接に戻り、次の処理に移る.
if (NativeModule.nonInternalExists(request)) {
    //    request        'http'
    return request;
}
カスタムモジュールには、以下のようなケースがあります.
  • ファイルモジュール
  • カタログモジュール
  • node_からmodulesディレクトリ負荷
  • グローバルディレクトリ負荷
  • これらは公式文書で明らかにされているので、ここでは詳しく説明しません.
    モジュールが存在する場合、_resolveFilenameは、/Users/xxx/Desktop/practice/node/module/parent/moduleA.jsのようなモジュールの絶対パスを返す.
    モジュールをロード
    モジュールアドレスを取得すると、Nodeはモジュールのロードに着手します.
    まず、Nodeはモジュールがキャッシュ中にあるかどうかを確認します.
    // filename        
    var cachedModule = Module._cache[filename];
    if (cachedModule) {
        updateChildren(parent, cachedModule, true);
        return cachedModule.exports;
    }
    存在すれば対応キャッシュ内容に戻り、存在しない場合はさらにこのモジュールがコアモジュールかどうかを判断する.
    if (NativeModule.nonInternalExists(filename)) {
        return NativeModule.require(filename);
    }
    モジュールがキャッシュ中にもコアモジュールでもない場合、Nodeは新たなモジュールオブジェクトを実装します.
    
    function Module(id, parent){
      //          
      this.id = id;
      //       
      this.exports = {};
      //     
      this.parent = parent;
      this.filename = null;
      //         
      this.loaded = false;
      //    
      this.children = [];
    }
    
    var module = new Module(filename, parent);
    そして、Nodeは経路によってロードを試みる.
    function tryModuleLoad(module, filename) {
      var threw = true;
      try {
        module.load(filename);
        threw = false;
      } finally {
        if (threw) {
          delete Module._cache[filename];
        }
      }
    }
    異なるファイルの拡張子については、ロード方法が異なります.
  • .jsファイル
  • ファイルの内容をfs同期で読み取って指定関数に包む:
    Module.wrapper = [
      '(function (exports, require, module, __filename, __dirname) { ',
      '
    });' ];
    呼び出しはこの関数を実行します.
    compiledWrapper.call(this.exports, this.exports, require, this,
                                      filename, dirname);
  • .jsonファイル
  • ファイルの内容をfs同期で読み込み、JSON.parseで解析して内容に戻ります.
    var content = fs.readFileSync(filename, 'utf8');
    try {
        module.exports = JSON.parse(stripBOM(content));
    } catch (err) {
        err.message = filename + ': ' + err.message;
        throw err;
    }
  • .node
  • これはC/C++で作成した拡張ファイルで、最後にコンパイルしたファイルをdleopen()でロードします.
    return process.dlopen(module, path.toNamespacedPath(filename));
  • .mjs
  • これはES 6モジュールの拡張ファイルを処理するためのもので、NodeJsがv 8.5.0後に追加した特性です.このような拡張子のファイルは、ES 6モジュール文法importを使用してのみ導入できます.そうでないとエラーが発生します.
    throw new ERR_REQUIRE_ESM(filename);
    すべてがうまくいけば、exportsの対象に付加された内容に戻ります.
    return module.exports;
    モジュール循環依存性
    次にモジュールの循環依存性の問題を探ってみましょう.モジュール1依存モジュール2、モジュール2依存モジュール1は、何が発生しますか?
    ここでは、comonjsの状況だけを探究します.
    このために、私達は2つのファイルを作成しました.module-a.jsとmodule-b.js、彼らに相互参照させます.
    module-a.js
    console.log('      A   ');
    exports.a = 2;
    require('./module-b.js');
    exports.b = 3;
    console.log('A       ');
    module-b.js
    console.log('      B   ');
    let moduleA = require('./module-a.js');
    console.log(moduleA.a,moduleA.b)
    console.log('B       ');
    --experimental-modulesを実行すると、コンソール出力が見られます.
         A   
         B   
    2 undefined
    B       
    A       
    このとき、各module-a.jsは同期して実行されるので、requireが完全にローディングされる前にmodule-aをローディングする必要があり、./module-bにとって、module-aオブジェクトには属性exportsだけが付加されており、属性abのローディングが完了した後に値が付与される.
    QA
  • モジュールキャッシュはどうやって削除しますか?
  • 対応するモジュールのキャッシュは、./module-bによって削除されてもよく、moduleIdはモジュールの絶対パスを表しています.一般的に、いくつかのモジュールを熱的に更新する必要がある場合、この特性を使用して、例を挙げます.
    // hot-reload.js
    console.log('this is hot reload module');
    
    // index.js
    const path = require('path');
    const fs = require('fs');
    const hotReloadId = path.join(__dirname,'./hot-reload.js');
    const watcher = fs.watch(hotReloadId);
    watcher.on('change',(eventType,filename)=>{
        if(eventType === 'change'){
            delete require.cache[hotReloadId];
            require(hotReloadId);
        }
    });
  • NodeでES 6モジュールが使えますか?
  • 8.5.0バージョンから、NodeJsはオリジナルES 6モジュールをサポートし始めました.この機能を有効にするには2つの条件が必要です.
  • ES 6モジュールを使用するすべてのファイル拡張子は、mjs
  • でなければなりません.
  • コマンドラインオプション--experimental-modules
  • node--experimental-modules index.mjs
    node --experimental-modules index.mjs
    しかし、NodeJs v 10.15.0まで、ES 6モジュールのサポートはまだ実験的です.筆者は会社のプロジェクトで使うことを勧めません.
    参照
  • nodejs-loader.js
  • 朴霊.深入浅出Node.js