Node.jsでECMAScriptモジュールを使用する方法
コードを再利用可能なモジュールとしてパッケージ化する標準化された方法は、ECMAScriptのほとんどの歴史の中で欠けていました。統合されたソリューションがないため、CommonJS(CJS)アプローチがNode.js開発の事実上の標準となりました。これを使用します
requireと
module.exportsコードの消費と提供のために:
// Import a module
const fs = require("fs");
// Provide an export
module.exports = () => "Hello World";
ES2015、別名ES6は、ついに独自の組み込みモジュールシステムを導入しました。ECMAScriptまたはESモジュール(ESM)は、次のように依存しています。
importそして
export構文 :
// Import a default export
import fs from "fs";
// Provide a default export
export default () => "Hello World";
// Import a named export
import {helloWorld} from "./hello-world.js";
// Provide a named export
export const helloWorld = () => "Hello World";
Node.jsはv16以降、デフォルトでESMのサポートを提供しています。以前のバージョンでは、使用する必要がありました。
--experimental-modules機能を有効にするためのフラグ。ESモジュールは現在、安定していて一般的な使用に準備が整ったとされていますが、2つの異なるモジュールロード機構が存在するため、単一のプロジェクト内で両方のタイプのコードを利用することは困難です。
この記事では、NodeでESモジュールを使用する方法と、CommonJSパッケージとの相互運用性を最大化するためにできることについて説明します。
基本
新しいプロジェクトを開始し、ESMに依存したい場合、事は比較的簡単です。Node.jsは現在、完全なサポートを提供しているので、コードを別々のファイルに分割して使用できます。
importそして
exportモジュールにアクセスするためのステートメント。
残念ながら、初めの段階でいくつかの意識的な選択をする必要があります。デフォルトでは、Nodeはサポートしていません。
importそして
export
そので終わるファイルの中に
.js拡張子。ファイルに接尾辞を付けることができます
.mjs、ESMが常に利用可能な場所、またはあなたの
package.jsonファイルを含める
"type": "module"{
"name": "example-package",
"type": "module",
"dependencies": {
"..."
}
}
後者のルートを選択することは、通常、ESMのみを使用するプロジェクトにとってより便利です。ノードは
typeプロジェクトのデフォルトモジュールシステムを決定するためのフィールドです。そのモジュールシステムは常にプレーンを処理するために使用されます。
.jsファイル。手動で設定されていない場合、CJSは既存のNodeコードのエコシステムとの互換性を最大限に高めるためのデフォルトのモジュールシステムです。どちらかのファイル
.cjsまたは
.mjs拡張子は、それぞれCJSおよびESMフォーマットで常にソースとして扱われます。
ESMからCommonJSモジュールをインポートする
通常の方法でESMファイル内にCJSモジュールをインポートすることができます。
importステートメント:
// cjs-module.cjs
module.exports.helloWorld = () => console.log("Hello World");
// esm-module.mjs
import component from "./cjs-module.cjs";
component.helloWorld();
componentCJSモジュールの値に解決されることになります
module.exports上記の例は、インポート名を使ってオブジェクトのプロパティとして名前付きエクスポートにアクセスする方法を示しています。また、ESMの名前付きインポート構文を使用して特定のエクスポートにアクセスすることもできます:
import {helloWorld} from "./cjs-module.cjs";
helloWorld();
これは、CJSファイルをスキャンして提供されるエクスポートを把握する静的解析システムによって機能します。このアプローチは、CJSが「名前付きエクスポート」という概念を理解していないため、必要です。すべてのCJSモジュールには1つのエクスポートがあります。「名前付き」エクスポートは、実際には複数のプロパティ値ペアを持つオブジェクトです。
静的解析プロセスの性質上、まれな構文パターンが正しく検出されない可能性があります。このような場合は、必要なプロパティにデフォルトのエクスポートオブジェクトを介してアクセスする必要があります。
CJSからESMモジュールをインポートする
新しいESMモジュールを既存のCJSコード内で使用したい場合、状況はより複雑になります。書くことはできません
importCJSファイル内のステートメント。ただし、動的な
import()構文は機能し、組み合わせることができます
awaitモジュールに比較的便利にアクセスするために:
// esm-module.mjs
const helloWorld = () => console.log("Hello World");
export {helloWorld};
// esm-module-2.mjs
export default = () => console.log("Hello World");
// cjs-module.cjs
const loadHelloWorld = async () => {
const {helloWorld} = await import("./esm-module.mjs");
return helloWorld;
};
const helloWorld = await loadHelloWorld();
helloWorld();
const loadHelloWorld2 = async() => {
const helloWorld2 = await import("./esm-module-2.mjs");
return helloWorld2;
};
const helloWorld2 = await loadHelloWorld2();
helloWorld2();
この構造体は、ESMモジュールのデフォルトおよび名前付きエクスポートの両方に非同期的にアクセスするために使用できます。
ESモジュールを使用して現在のモジュールのパスを取得する
ESモジュールは、CJSコンテキストで利用可能なすべての馴染みのあるNode.jsのグローバル変数にアクセスできません。その上で
require()と
module.exports, あなたはアクセスできません
__dirnameまたは
__filenameこれらは、独自のファイルへのパスを知る必要があるCJSモジュールによって一般的に使用される定数です。
ESMファイルは読み取ることができます
import.meta.urlこの情報を得るために:
console.log(import.meta.url);
// file:///home/demo/module.mjs
返されたURLは現在のファイルへの絶対パスを示しています。
なぜすべての非互換性があるのか?
CJSとESMの違いは、単純な文法の変更以上に深いです。CJSは同期システムです。あなたが
require()モジュールをロードすると、Nodeはそれをディスクから直接読み込み、その内容を実行します。ESMは非同期で、スクリプトのインポートをいくつかの異なるフェーズに分割します。インポートは解析され、ストレージから非同期にロードされ、すべてのインポートが同様に取得されると実行されます。
ESMは幅広い用途に対応した現代的なモジュールロードソリューションとして設計されています。このため、ESMモジュールはウェブブラウザでの使用に適しています:それは設計上非同期であるため、遅いネットワークは問題になりません。
ESMの非同期的な性質は、CJSコードでの使用に関する制限の原因でもあります。CJSファイルはトップレベルをサポートしていません。
awaitだから使えないということです
importそれ自体で:
// this...
import component from "component.mjs";
// ...can be seen as equivalent to this...
const component = await import("component.mjs");
// ...but top-level "await" isn't available in CJS
したがって、動的を使用する必要があります。
import()内部構造の中にある
async機能。
ESモジュールに切り替えるべきですか?
簡単な答えははいです。ESモジュールはJavaScriptコードをインポートおよびエクスポートするための標準化された方法です。CJSは言語に独自のモジュールシステムがなかったときにNodeにモジュールシステムを提供しました。現在、独自のものが利用可能になったので、コミュニティの長期的な健康のためには、ECMAScript標準で説明されているアプローチを採用するのが最善です。
ESMはより強力なシステムでもあります。非同期であるため、動的インポートやURLからのリモートインポート、特定の状況でのパフォーマンス向上が得られます。また、ブラウザに直接配信されるコードなど、他のJavaScriptランタイムでもモジュールを再利用できます。
<script type="module">HTMLタグ。
それでも、移行は多くの既存のNode.jsプロジェクトにとって依然として課題です。CJSはすぐには消えません。その間、自分のライブラリのためにCJSとESMの両方のエクスポートを提供することで、移行を容易にすることができます。これは、既存のCJSエクスポートの周囲に薄いESMラッパーを書くことで最もよく達成されます。
import demoComponent from "../cjs-component-demo.js";
import exampleComponent from "../cjs-component-example.js";
export {demoComponent, exampleComponent};
このファイルを新しい中に置いてください
esmプロジェクト内のサブディレクトリ。追加する
package.jsonそれを含む横に
{"type": "module"}フィールド。
次の更新は
package.jsonプロジェクトのルートに以下の内容を含めるには:
{
"exports": {
"require": "./cjs-index.js",
"import": "./esm/esm-index.js"
}
}
これはノードに対して、あなたのパッケージのユーザーにCJSエントリポイントを提供するよう指示します。
require()輸出。いつ
import使用されると、あなたのESMラッパースクリプトが代わりに提供されます。このインポートマップ機能にはもう一つの副作用があります。ユーザーはエントリーポイントファイルで提供されたエクスポートのみに制限されます。パッケージ内のパスを介して特定のファイルを読み込むことはできません。
import demo from "example-package/path/to/file.jsインポートマップが存在する場合は無効になります。
要約
Node.jsモジュールの環境は苦痛なことがあります。特にCJSとESMの両方との互換性が必要な場合はなおさらです。ESMサポートの安定化により改善はされましたが、プロジェクトの「デフォルト」となるモジュールシステムを前もって決定する必要があります。「別の」システムからコードを読み込むことは可能ですが、特にCJSがESMに依存する場合はしばしば不快です。
大規模なプロジェクトがESMを好むアプローチとして採用するにつれて、状況は徐々に改善されるでしょう。しかし、CJSコードがこれほど多く存在する中で、全体的な移行は現実的ではなく、両方のメカニズムが数年間は併用される可能性が高く、そこに伴うすべてのトレードオフが生じるでしょう。
新しいライブラリを始める場合は、ESM互換のコードを配布していることを確認し、可能な限りデフォルトシステムとして使用するようにしてください。これにより、NodeエコシステムがESMに収束し、ES標準と再調整されることが期待されます。
