CommonJS から ES Modules への移行
多くの Node.js プロジェクトが CommonJS から ES Modules への移行を進めています。この記事では、移行の手順と注意点を解説します。
移行前の準備
まず、現在のプロジェクトの依存関係を確認します。使用しているパッケージが ES Modules に対応しているかを調べましょう。
# 依存パッケージのESM対応状況を確認
npm ls基本的な移行手順
package.json に “type”: “module” を追加
require() を import に書き換え
module.exports を export に書き換え
__dirname / __filename を修正
Step 1: package.json の変更
{
"name": "my-project",
"type": "module",
"version": "1.0.0"
}この変更により、すべての .js ファイルが ES Modules として扱われます。
Step 2: require → import
// Before (CommonJS)
const express = require('express');
const { readFile } = require('fs');
const path = require('path');
const utils = require('./utils');
// After (ES Modules)
import express from 'express';
import { readFile } from 'fs';
import path from 'path';
import utils from './utils.js'; // 拡張子が必要Step 3: exports → export
// Before (CommonJS)
function greet(name) {
return `Hello, ${name}!`;
}
module.exports = { greet };
module.exports.VERSION = '1.0';
// After (ES Modules)
export function greet(name) {
return `Hello, ${name}!`;
}
export const VERSION = '1.0';Step 4: __dirname と __filename の修正
ES Modules では __dirname と __filename が使えません。
// Before (CommonJS)
const configPath = path.join(__dirname, 'config.json');
// After (ES Modules)
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const configPath = join(__dirname, 'config.json');JSON ファイルの読み込み
ES Modules では JSON を直接 import できない環境もあります。
// Before (CommonJS)
const config = require('./config.json');
// After (ES Modules) - 方法1: assert を使用(Node.js 17.5+)
import config from './config.json' with { type: 'json' };
// After (ES Modules) - 方法2: fs で読み込む
import { readFileSync } from 'fs';
const config = JSON.parse(readFileSync('./config.json', 'utf-8'));動的 require の移行
// Before (CommonJS)
const plugin = require(`./plugins/${name}`);
// After (ES Modules)
const plugin = await import(`./plugins/${name}.js`);条件付き require の移行
// Before (CommonJS)
let db;
if (process.env.NODE_ENV === 'production') {
db = require('./db-prod');
} else {
db = require('./db-dev');
}
// After (ES Modules)
const dbModule = process.env.NODE_ENV === 'production'
? './db-prod.js'
: './db-dev.js';
const db = await import(dbModule);段階的な移行
一度にすべてを移行するのが難しい場合、段階的に進められます。
方法1: .mjs を使用
新しいファイルは .mjs で作成し、既存の .js は CommonJS のまま維持。
方法2: サブディレクトリごとに移行
移行済みのディレクトリに package.json を置いて “type”: “module” を設定。
互換性維持パターン
ESM と CJS の両方から使えるライブラリを公開する場合は、デュアルパッケージとして構成します。
{
"name": "my-library",
"type": "module",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
}
}よくある問題と解決策
| 問題 | 解決策 |
|---|---|
| 拡張子エラー | import に .js を追加 |
| __dirname が未定義 | import.meta.url から導出 |
| JSON インポートエラー | with { type: 'json' } または fs.readFileSync |
| CJS パッケージが動かない | default インポートを確認 |
移行は一度に完了させる必要はありません。プロジェクトの状況に応じて、段階的に進めていくのが現実的です。