Node.js プロジェクトの ES Modules によるモダナイゼーション
Olivia Novak
Dev Intern · Leapcell

長年、Node.js 開発者は主に CommonJS をモジュール管理に依存し、require() と module.exports を使用してコードを整理し共有してきました。このシステムはうまく機能し、堅牢なエコシステムを育んできました。しかし、JavaScript 自体で ECMAScript Modules (ESM) が標準化されるにつれて、避けられない変化が始まりました。ESM は静的解析の利点、より優れたツリーシェイキング機能を提供し、Node.js をブラウザ側のモジュールローディングと一致させ、Unified JavaScript 開発への道を開きます。多くの最新ライブラリやフレームワークがデフォルトで ESM を採用しており、CommonJS に留まっているプロジェクトはやや時代遅れに感じられたり、互換性の問題に直面したりしています。この進化により、多くの既存アプリケーションにとって重要な疑問が生じます。確立された CommonJS Node.js プロジェクトを将来性のある ES Modules に移行するにはどうすればよいでしょうか?その答えは、多くの場合、package.json ファイル内のシンプルでありながら強力な設定、すなわち "type": "module" にあります。この記事では、この移行を理解し実行するためのガイダンスを提供し、Node.js プロジェクトが最新のモジュールシステムを採用することを保証します。
モジュール移行の理解
移行に進む前に、この議論の中心となるいくつかの基本的な概念を明確にしましょう。
主要な用語
-
CommonJS (CJS): Node.js の元のデフォルトモジュールシステムです。モジュールをインポートするために
require()を使用し、モジュールを公開するためにmodule.exportsまたはexportsを使用します。同期的であり、モジュールが一つずつロードされることを意味します。// CommonJS の例 const express = require('express'); function greet(name) { return `Hello, ${name}!`; } module.exports = greet; -
ECMAScript Modules (ESM): ブラウザと Node.js でネイティブにサポートされている、JavaScript の公式モジュール標準です。
importおよびexportステートメントを使用します。ESM は非同期設計であり、より優れたパフォーマンス最適化を可能にします。// ESM の例 import express from 'express'; export function greet(name) { return `Hello, ${name}!`; } -
package.jsonのtypeフィールド: Node.js 13.2 で導入されたこのフィールドにより、開発者はパッケージ内のすべての.jsファイルのモジュールシステムを指定できます。"type": "commonjs": (指定しない場合のデフォルト) すべての.jsファイルは CommonJS として扱われます。"type": "module": すべての.jsファイルは ES Modules として扱われます。
"type": "module" の仕組み
package.json で "type": "module" を設定すると、Node.js はそのパッケージ内の .js ファイルのデフォルトパーサーを変更します。CommonJS として解釈する代わりに、ES Modules として扱います。これにはいくつかの連鎖的な効果があります。
- デフォルトモジュールシステムの反転: プロジェクト内のすべての
.jsファイル(およびサブディレクトリ)でrequire()またはmodule.exportsを使用しようとすると、エラーが発生し、importおよびexport構文が必要になります。 - ファイル拡張子のオーバーライド:
.cjsまたは.mjsファイル拡張子を明示的に使用することで、ESM プロジェクト内(または CommonJS プロジェクト内)の CommonJS ファイル(または ESM ファイル)を引き続き使用できます。.cjsファイルは、package.jsonのtypeフィールドに関係なく、常に CommonJS として扱われます。.mjsファイルは、package.jsonのtypeフィールドに関係なく、常に ES Module として扱われます。 これにより、移行中に段階的な移行を可能にする柔軟性が提供されます。
実用的な移行手順
シンプルな CommonJS Node.js プロジェクトを ES Modules に移行する手順を見ていきましょう。
初期 CommonJS プロジェクト構造
app.js とユーティリティモジュール utils.js を持つプロジェクトを考えます。
package.json (初期):
{ "name": "cjs-project", "version": "1.0.0", "main": "app.js", "scripts": { "start": "node app.js" } }
utils.js (CommonJS):
// utils.js function add(a, b) { return a + b; } function subtract(a, b) { return a - b; } module.exports = { add, subtract };
app.js (CommonJS):
// app.js const math = require('./utils'); const express = require('express'); // express がインストールされていると仮定 const app = express(); const port = 3000; console.log('Addition:', math.add(5, 3)); // 出力: Addition: 8 console.log('Subtraction:', math.subtract(10, 4)); // 出力: Subtraction: 6 app.get('/', (req, res) => { res.send('Hello from CommonJS app!'); }); app.listen(port, () => { console.log(`CommonJS app listening at http://localhost:${port}`); });
これを実行するには、express がインストールされていることを確認し (npm i express)、次に npm start を実行します。
ステップ 1: package.json の更新
最初で最も重要なステップは、"type": "module" を package.json に追加することです。
package.json (変更後):
{ "name": "cjs-project", "version": "1.0.0", "main": "app.js", "type": "module", <-- この行を追加 "scripts": { "start": "node app.js" } }
これで npm start を試すと、Node.js が古い CommonJS ファイルを ES Modules として解析しようとするため、SyntaxError: Unexpected token 'export' や ReferenceError: require is not defined のようなエラーが発生する可能性があります。
ステップ 2: CommonJS 構文を ES Module 構文に変換
CommonJS ファイルを調べて require()/module.exports を import/export に変換する必要があります。
utils.js (ESM 変換後):
// utils.js export function add(a, b) { return a + b; } export function subtract(a, b) { return a - b; }
注意: module.exports = { add, subtract } の場合、export default { add, subtract } と app.js で import math from './utils.js' を使用することもできます。ただし、通常は名前付きエクスポートが推奨されます。
app.js (ESM 変換後):
// app.js import { add, subtract } from './utils.js'; // .js 拡張子に注意! import express from 'express'; // npm パッケージの通常のインポート const app = express(); const port = 3000; console.log('Addition:', add(5, 3)); console.log('Subtraction:', subtract(10, 4)); app.get('/', (req, res) => { res.send('Hello from ESM app!'); }); app.listen(port, () => { console.log(`ESM app listening at http://localhost:${port}`); });
変換中の重要な考慮事項:
- ファイル拡張子: ローカル ES Modules をインポートする場合、完全なファイル拡張子(例:
./utils.js)を含める 必要があります。これは CommonJS とは重要な違いであり、CommonJS では拡張子が省略されることがよくありました。Node.js はインストールされているパッケージ(expressなど)のベア指定子を解決しますが、相対パスや絶対パスは解決しません。 __dirnameおよび__filename: これらの CommonJS 固有のグローバル変数は ES Modules では利用できません。import.meta.urlと Node.js のpathおよびfileURLToPathモジュールを使用して、その機能を再作成できます。import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); console.log('Current directory:', __dirname); console.log('Current file:', __filename);- JSON のインポート: CommonJS では、
require('./data.json')を使用できました。ESM では、一部の Node.js バージョンで直接 JSON のインポートがまだ実験的です({ type: 'json' }を使用してimport data from './data.json')。より広範な互換性のために、fs.readFileとJSON.parseを使用して JSON ファイルを読み取る必要がある場合があります。 - 動的インポート / Interop での
require: ESM ファイルから CommonJS モジュールをrequire()する必要がある場合(例: ESM バージョンを公開していないレガシーライブラリ)、動的インポートにはimport()を使用できますが、直接のrequire()は利用できません。単純な Interop を動的ロードなしで行う場合、多くの場合、パッケージ自体が ESM 互換性を提供しているか、ラッパーが必要になることがあります。
ステップ 3: 移行されたプロジェクトの実行
関連するすべてのファイルを変換した後、プロジェクトを再度実行します。
npm start
Node.js アプリケーションは、完全に ES Modules を使用して実行され、新しいメッセージをログに記録し、正しい HTTP レスポンスを提供する必要があります。
混合モジュール環境の処理
大規模なプロジェクトで一度にすべてを変換できない場合、または頑なに CommonJS のみ(ただし、ほとんどのものは現在デュアルパブリッシュまたは ESM 互換です)のモジュールに依存している場合はどうでしょうか?ここで .cjs および .mjs 拡張子が非常に役立ちます。
例: ESM プロジェクトで CommonJS ファイルを保持する
type: "module" プロジェクトで、すぐに変換できない legacy.cjs(CommonJS ファイル)があると想像してください。
legacy.cjs:
// legacy.cjs module.exports = function () { return "This is a legacy CommonJS function!"; };
ESM ファイルからこれをインポートできます。
app.js (.cjs のインポート用に変更):
import { add, subtract } from './utils.js'; import express from 'express'; import legacyFunc from './legacy.cjs'; // Node.js は .cjs 拡張子のため、これが CJS であることを認識しています。 const app = express(); const port = 3000; console.log('Addition:', add(5, 3)); console.log('Subtraction:', subtract(10, 4)); console.log('Legacy:', legacyFunc()); // 出力: Legacy: This is a legacy CommonJS function! app.get('/', (req, res) => { res.send('Hello from ESM app!'); }); app.listen(port, () => { console.log(`ESM app listening at http://localhost:${port}`); });
これにより、type: "module" が デフォルト を設定しますが、.cjs および .mjs は、移行中の混合コードベースの管理や特定のユースケースの回避策として重要なエスケープハッチを提供する方法が示されます。
結論
package.json で "type": "module" を設定することにより、CommonJS から ES Modules への Node.js プロジェクトの移行は、JavaScript コードベースのモダナイゼーションに向けた重要なステップです。require/module.exports の import/export への体系的な変換、およびファイル拡張子、__dirname、__filename の慎重な処理が含まれますが、より広範な JavaScript エコシステムとの整合性、ツールの改善、将来の互換性の利点は、その労力に見合う価値があります。よりクリーンで、保守しやすく、最新の Node.js アプリケーションのために ES Modules を採用しましょう。この package.json の小さな変更は、Node.js におけるモジュラー JavaScript の未来を解き放ちます。