日常の開発で Nodejs を使用する場合、よく require を使用して 2 種類のモジュールをインポートします。1 つは自分で作成したモジュール、もう 1 つは Node の文件模块
と呼ばれます。これは、 os
、 fs
、その他のモジュールなど、使用するために提供される Node の組み込みモジュールです。これらのモジュールは核心模块
と呼ばれます。
ファイルモジュールとコアモジュールの違いは、ノードに組み込まれているかどうかだけでなく、ファイルの場所、モジュールのコンパイルおよび実行プロセスにも明らかな違いがあることに注意してください。 。それだけでなく、ファイル モジュールは、通常のファイル モジュール、カスタム モジュール、C/C++ 拡張モジュールなどに細分化することもできます。モジュールが異なれば、ファイルの配置、コンパイル、その他のプロセスにおいても多くの詳細が異なります。
この記事では、これらの問題に対処し、ファイル モジュールとコア モジュールの概念、およびファイルの場所、コンパイル、または実行で注意する必要がある特定のプロセスと詳細を明確にします。
ファイルモジュールから始めましょう。
ファイルモジュールとは何ですか?
Node では、 .、.. 或/
で始まるモジュール識別子を使用する (つまり、相対パスまたは絶対パスを使用する) 必要なモジュールは、ファイル モジュールとして扱われます。さらに、相対パスや絶対パスを含まず、コア モジュールではない特別なタイプのモジュールがありますが、Node がこのタイプの模块路径
を見つけると、模块路径
してモジュールを1つずつ検索するこのタイプのモジュールをカスタムモジュールと呼びます。
したがって、ファイルモジュールには、パス付きの通常のファイルモジュールと、パスのないカスタムモジュールの2種類が存在します。
ファイル モジュールは実行時に動的にロードされます。これには完全なファイルの場所、コンパイル、実行プロセスが必要ですが、コア モジュールよりも時間がかかります。
ファイルの配置に関して、Node はこれら 2 種類のファイル モジュールを異なる方法で処理します。これら 2 種類のファイル モジュールの検索プロセスを詳しく見てみましょう。
のファイルモジュールは、運ぶパスが明確なため、検索に時間がかからず、以下で紹介するカスタムモジュールよりも検索効率が高くなります。ただし、まだ注意すべき点が 2 つあります。
まず、通常の状況では、require を使用してファイル モジュールを導入する場合、通常、次のようにファイル拡張子は指定されません
。const math = require("math");
拡張子が指定されていないため、Node は最終的なファイルを決定できません。この場合、Node は.js、.json、.node
の順に拡張子を完成させ、それらを 1 つずつ試します。このプロセスは文件扩展名分析
と呼ばれます。
実際の開発では、特定のファイルを要求するだけでなく、通常は次のようにディレクトリも指定することにも注意してください。
const axios = require("../network");
この場合、ノードは最初にファイルを実行します。拡張子解析。対応するファイルが見つからなくてもディレクトリが取得された場合、Node はそのディレクトリをパッケージとして扱います。
具体的には、Nodeはディレクトリ内のpackage.json
のmain
フィールドが指すファイルを検索結果として返します。 main が指すファイルが間違っている場合、またはpackage.json
ファイルがまったく存在しない場合、Node はindex
デフォルトのファイル名として使用し、 .js
と.node
使用して拡張子の分析を実行し、ターゲット ファイルを検索します。見つからない場合はエラーがスローされます。
(もちろん、Node には CJS と ESM という 2 種類のモジュール システムがあるため、メイン フィールドの検索に加えて、他のメソッドも使用します。この記事の範囲外であるため、詳細は説明しません。 )
先ほど触れましたが、Node がカスタム モジュールを検索するときはモジュール パスが使用されます。では、モジュール パスとは何でしょうか。
モジュールの解析に詳しい人は、モジュールのパスがパスで構成される配列であることを次の例で確認できるはずです。
// example.js console.log(module.paths);
結果を出力します:
ご覧のとおり、Node のモジュールにはモジュール パス配列があり、これはmodule.paths
に保存され、現在のモジュールが参照するカスタム モジュールを Node が検索する方法を指定するために使用されます。
具体的には、Node はモジュール パス配列を走査し、各パスを 1 つずつ試し、そのパスに対応するnode_modules
ディレクトリに指定されたカスタム モジュールがあるかどうかを確認します。存在しない場合は、そのパスに到達するまで上向きに段階的に再帰します。ルートディレクトリのnode_modules
ディレクトリ。ターゲットモジュールが見つかるまで、見つからない場合はエラーがスローされます。
node_modules
ディレクトリを段階的に再帰的に検索することがカスタム モジュールを見つけるための Node の戦略であり、モジュール パスはこの戦略の特定の実装であることがわかります。
同時に、カスタムモジュールを検索する場合、レベルが深くなるほど、対応する検索に時間がかかるという結論にも達しました。したがって、コアモジュールや通常のファイルモジュールと比較して、カスタムモジュールのロード速度は最も遅くなります。
もちろん、モジュールパスに基づいて検索されるのはディレクトリのみであり、特定のファイルが検索されるわけではありません。ディレクトリを検索した後、Node は上記のパッケージ処理プロセスに従って検索します。その具体的なプロセスについては再度説明しません。
以上が通常のファイルモジュールとカスタムモジュールのファイル配置処理と注意事項です。次に、2 種類のモジュールがどのようにコンパイルおよび実行されるかを見てみましょう。
、require が指すファイルが見つかった場合、通常、モジュール識別子には拡張子がありません。上記のファイル拡張子の分析によれば、Node が次のファイルのコンパイルと実行をサポートしていることがわかります。 3 つの拡張子:
JavaScript ファイル。ファイルはfs
モジュールを通じて同期的に読み取られ、コンパイルされて実行されます。 .node
ファイルと.json
ファイルを除き、他のファイルは.js
ファイルとしてロードされます。
.node
ファイル。C/C++ で記述した後にコンパイルおよび生成される拡張ファイルです。Node はprocess.dlopen()
メソッドを通じてファイルを読み込みます。
json ファイルの場合、 fs
モジュールを通じてファイルを同期的に読み取った後、 JSON.parse()
を使用して解析し、結果を返します。
ファイル モジュールをコンパイルして実行する前に、Node は以下に示すようにモジュール ラッパーを使用してファイル モジュールをラップします。
(function(exports, require, module, __filename, __dirname) { //モジュール コード});
モジュール ラッパーを通じて、Node がモジュールを関数スコープにパッケージ化し、それを他のスコープから分離して、変数の名前の競合やグローバル スコープの汚染などの問題を回避していることがわかります。時間を渡すことにより、exports パラメータと require パラメータを使用すると、モジュールが必要なインポート機能とエクスポート機能を持つことができます。これは Node のモジュールの実装です。
モジュール ラッパーを理解したら、まず json ファイルのコンパイルと実行のプロセスを見てみましょう。
json ファイルのコンパイルと実行は最も簡単です。 fs
モジュールを通じて JSON ファイルの内容を同期的に読み取った後、Node は JSON.parse() を使用して JavaScript オブジェクトを解析し、それをモジュールのエクスポート オブジェクトに割り当て、最後にそれを参照するモジュールに返します。プロセスは非常に単純かつ粗雑です。
: モジュール ラッパーを使用して JavaScript ファイルをラップした後、ラップされたコードはvm
モジュールのrunInThisContext()
(eval と同様) メソッドを通じて実行され、関数オブジェクトが返されます。
次に、JavaScript モジュールのexports、require、moduleおよびその他のパラメータが実行のためにこの関数に渡され、実行後にモジュールのexports属性が呼び出し元に返されます。これがJavaScriptファイルのコンパイルと実行のプロセスです。
C/C++ 拡張モジュールのコンパイルと実行を説明する前に、まず C/C++ 拡張モジュールとは何かを紹介します。
C/C++ 拡張モジュールは、名前が示すように、C/C++ で記述されており、ロード後にコンパイルする必要がないことです。直接実行された後、JavaScript モジュールよりもわずかに高速にロードされます。 JS で作成されたファイル モジュールと比較して、C/C++ 拡張モジュールには明らかなパフォーマンス上の利点があります。 Node コア モジュールでカバーできない機能や、特定のパフォーマンス要件がある機能については、ユーザーは C/C++ 拡張モジュールを作成して目的を達成できます。
では、 .node
ファイルとは何ですか?また、C/C++ 拡張モジュールとどのような関係があるのでしょうか?
実際、記述された C/C++ 拡張モジュールがコンパイルされると、 .node
ファイルが生成されます。つまり、モジュールのユーザーとして、C/C++ 拡張モジュールのソース コードを直接導入するのではなく、C/C++ 拡張モジュールのコンパイル済みバイナリ ファイルを導入します。したがって、 .node
ファイルをコンパイルする必要はありません。Node は.node
ファイルを見つけたら、そのファイルをロードして実行するだけで済みます。実行中に、モジュールのエクスポート オブジェクトにデータが設定され、呼び出し元に返されます。
C/C++ 拡張モジュールのコンパイルによって生成される.node
ファイルは、プラットフォームごとに異なる形式になることに注意してください。 *nix
システムでは、C/C++ 拡張モジュールは、g++/gcc などのコンパイラによってダイナミック リンク共有オブジェクト ファイルにコンパイルされます。拡張子は.so
です。Windows ではWindows
Visual C++ コンパイラによってダイナミック リンク ライブラリ ファイルにコンパイルされ、拡張子は.dll
です。ただし、実際に使用する拡張子は.node
です。実際、 .node
の拡張子は、 Windows
では .dll ファイルであり、 *nix
では.dll
ファイルです.so
。
Node は必要な.node
ファイルを見つけた後、 process.dlopen()
メソッドを呼び出してファイルをロードして実行します。 .node
ファイルはプラットフォームごとにファイル形式が異なるため、クロスプラットフォーム実装を実現するために、 dlopen()
メソッドはWindows
および*nix
プラットフォームで異なる実装を持ち、 libuv
互換性レイヤーを通じてカプセル化されます。次の図は、さまざまなプラットフォームでの C/C++ 拡張モジュールのコンパイルとロードのプロセスを示しています。
コア モジュールは、ノード ソース コードのコンパイル プロセス中にバイナリ実行可能ファイルにコンパイルされます。 Node プロセスが開始されると、いくつかのコア モジュールがメモリに直接ロードされるため、これらのコア モジュールが導入されると、ファイルの場所とコンパイルと実行の 2 つのステップが省略され、パス内のファイル モジュールの前に判定されます。そのため、読み込み速度が最も速くなります。
コア モジュールは実際には、C/C++ と JavaScript で記述された 2 つの部分に分かれており、C/C++ ファイルは Node プロジェクトの src ディレクトリに保存され、JavaScript ファイルは lib ディレクトリに保存されます。明らかに、モジュールのこれら 2 つの部分のコンパイルおよび実行プロセスは異なります。
JavaScript コア モジュールのコンパイルでは、Node ソース コードのコンパイル プロセス中に、Node は V8 に付属する js2c.py ツールを使用して、JavaScript コア モジュールを含むすべての組み込み JavaScript コードを変換します。 C++ に変換すると、JavaScript コードが文字列としてノードの名前空間に保存されます。 Node プロセスを開始すると、JavaScript コードがメモリに直接ロードされます。
JavaScript コア モジュールが導入されると、Node はprocess.binding()
を呼び出し、モジュール識別子の分析を通じてメモリ内の位置を特定し、それを取得します。 JavaScript コア モジュールも取り出された後、モジュール ラッパーによってラップされてから実行され、exports オブジェクトがエクスポートされて呼び出し元に返されます。
コア モジュール内でコンパイルおよび実行されます。一部のモジュールはすべて C/C++ で記述されており、一部のモジュールはコア部分が C/C++ によって完成され、その他の部分はパフォーマンス要件を満たすために JavaScript によってパッケージ化またはエクスポートされます。 buffer
、 fs
、 os
などのモジュールは部分的に C/C++ で書かれています。 C++ モジュールがメイン部分の内側にコアを実装し、JavaScript モジュールがメイン部分の外側でカプセル化を実装するこのモデルは、Node のパフォーマンスを向上させる一般的な方法です。
純粋な C/C++ で書かれたコア モジュールの部分は、 node_fs
、 node_os
などの組み込みモジュールと呼ばれます。これらは通常、ユーザーによって直接呼び出されることはありませんが、JavaScript コア モジュールに直接依存します。したがって、Node のコア モジュールの導入プロセスには、次のような参照チェーンがあります。
では、JavaScript コア モジュールはどのようにして組み込みモジュールをロードするのでしょうか?
process.binding()
メソッドを覚えていますか? Node は、このメソッドを呼び出すことによって JavaScript コア モジュールをメモリから削除します。このメソッドは、組み込みモジュールのロードを支援するために JavaScript コア モジュールにも適用されます。
このメソッドの実装に特有の、組み込みモジュールをロードするときは、最初に空のエクスポート オブジェクトを作成し、次にget_builtin_module()
メソッドを呼び出して組み込みモジュール オブジェクトを取り出し、 register_func()
を実行してエクスポート オブジェクトを埋めます。そして最後にそれを呼び出し元に返してエクスポートを完了します。組み込みモジュールのロードと実行のプロセスです。
上記の分析を通じて、os モジュールを例としてコア モジュールなどのリファレンス チェーンを導入する一般的なプロセスは次のようになります。
要約すると、OS モジュールの導入プロセスには、JavaScript ファイル モジュールの導入、JavaScript コア モジュールの読み込みと実行、組み込みモジュールの読み込みと実行が含まれます。このプロセスは非常に面倒で複雑です。モジュールの呼び出し側にとっては、基礎となるシールドが保護されているためです。複雑な実装と詳細については、モジュール全体を require() を介して簡単にインポートできます。これは非常に簡単です。フレンドリー。
この記事では、ファイル モジュールとコア モジュールの基本概念と、ファイルの場所、コンパイル、または実行で注意する必要がある特定のプロセスと詳細について紹介します。具体的には、
ファイルモジュールは、さまざまなファイル配置プロセスに従って、通常のファイルモジュールとカスタムモジュールに分けることができます。通常のファイル モジュールはパスが明確であるため、直接見つけることができますが、場合によってはファイル拡張子分析やディレクトリ分析のプロセスが必要になります。カスタム モジュールはモジュール パスに基づいて検索され、検索が成功した後、ディレクトリ分析を通じて最終的なファイルの場所が特定されます。 。
ファイル モジュールは、さまざまなコンパイルおよび実行プロセスに従って、JavaScript モジュールと C/C++ 拡張モジュールに分類できます。 JavaScript モジュールはモジュール ラッパーによってパッケージ化された後、 vm
モジュールのrunInThisContext
メソッドを通じて実行されます。C/C++ 拡張モジュールはコンパイル後にすでに生成された実行可能ファイルであるため、直接実行でき、エクスポートされたオブジェクトが返されます。発信者に。
コアモジュールはJavaScriptコアモジュールと組み込みモジュールに分かれています。 JavaScript コア モジュールは、Node プロセスの開始時にメモリにロードされ、 process.binding()
メソッドを通じて取り出され、実行されます。組み込みモジュールのコンパイルと実行はprocess.binding()
によって行われます。 get_builtin_module()
およびregister_func()
関数の処理。
さらに、コア モジュールを導入する Node のリファレンス チェーン、つまりファイル モジュール -> JavaScript コア モジュール -> 組み込みモジュールも見つかりました。また、C++ モジュールが内部でコアを完成させ、JavaScript が完成することもわかりました。 module はモジュールの書き込みメソッドを外部でカプセル化します。