ウェブサイト検索

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();

component

CJSモジュールの値に解決されることになります

module.exports

上記の例は、インポート名を使ってオブジェクトのプロパティとして名前付きエクスポートにアクセスする方法を示しています。また、ESMの名前付きインポート構文を使用して特定のエクスポートにアクセスすることもできます:

import {helloWorld} from "./cjs-module.cjs";

helloWorld();

これは、CJSファイルをスキャンして提供されるエクスポートを把握する静的解析システムによって機能します。このアプローチは、CJSが「名前付きエクスポート」という概念を理解していないため、必要です。すべてのCJSモジュールには1つのエクスポートがあります。「名前付き」エクスポートは、実際には複数のプロパティ値ペアを持つオブジェクトです。

静的解析プロセスの性質上、まれな構文パターンが正しく検出されない可能性があります。このような場合は、必要なプロパティにデフォルトのエクスポートオブジェクトを介してアクセスする必要があります。

CJSからESMモジュールをインポートする

新しいESMモジュールを既存のCJSコード内で使用したい場合、状況はより複雑になります。書くことはできません

import

CJSファイル内のステートメント。ただし、動的な

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標準と再調整されることが期待されます。

関連記事: