これも結構ややこしいので纏めました。
ESモジュールとCommonJS
ESモジュールは最新の標準であり、今後のJavaScript開発において主流となる方向性にあります。CommonJSは依然として多くのプロジェクトで使用されていますが、ESMへの移行が推奨されています。 しかし、古いライブラリなどはESモジュールに対応していない場合もある。
ESモジュール(ESM)の概要 —import
- 正式名称: ECMAScript Modules(ESM)
- 導入時期: ECMAScript 2015(ES6)で標準化、Node.js 14より使用可能。
- 目的: JavaScriptにおける公式なモジュールシステムの提供。より構造化されたコード管理と再利用性の向上を目指しています。
- node.jsの場合、package.jsonに”type”:”module”を設定することで、デフォルトで
.jsファイルがESモジュールとして扱われます。 設定しない場合、.cjsはCommonJS、mjsはESモジュールとして認識されます。 - ブラウザの場合、
<script type="module">を指定してimportを使う。
ESモジュール(ESM)の特徴
- 静的解析が可能: インポート/エクスポートがファイルの先頭で行われるため、ツールが依存関係を容易に解析・最適化できます。
- ネイティブサポート: 現代のブラウザやNode.jsがネイティブでサポートしており、追加のトランスパイラなしで使用可能です。
- ツールチェーンとの互換性: WebpackやRollupなどのモジュールバンドラもESMを前提に設計されています。
対比: CommonJS(CJS)—require
- 歴史的背景: Node.jsがモジュールシステムを導入する際に採用された形式です。
- 動的インポート: require()は動的にモジュールを読み込むため、静的解析が困難です。
- 互換性: 多くの既存のNode.jsパッケージがCommonJS形式で提供されています。
require と import の違い、メリット・デメリット比較表
以下の表で、CommonJSのrequireとESMのimportの主な違い、およびそれぞれのメリット・デメリットを比較します。
| 項目 | CommonJS (require) | ESモジュール (import) |
|---|---|---|
| 導入時期 | Node.jsの初期から存在 | ECMAScript 2015(ES6)で標準化 |
| シンタックス | javascript<br>const module = require('module');<br>module.exports = ...;<br> | javascript<br>import module from 'module';<br>export default ...;<br> |
| モジュールの特性 | 動的にモジュールを読み込む。実行時にrequireが呼び出される。 | 静的な構文。コンパイル時にモジュールの依存関係が解析される。 |
| 非同期サポート | 同期的にモジュールを読み込む。 | ネイティブで非同期モジュールローディングをサポート(import() を使用) |
| ネイティブサポート | Node.jsおよび一部のツールでサポートされているが、ブラウザでは基本的に使用不可。 | 現代のブラウザや最新のNode.jsがネイティブでサポート。 |
| ツールチェーン | 既存の多くのツールやライブラリがCommonJSに対応。 | モダンなビルドツールやバンドラがESMを前提に設計されている。 |
| ホットリロード | 動的にモジュールを再読み込みするのが難しい。 | 静的なインポートにより、より効率的なホットリロードが可能。 |
| デフォルトエクスポート | デフォルトエクスポートの概念がない。 | export default による明確なデフォルトエクスポートが可能。 |
| 互換性 | 多くの既存プロジェクトやライブラリで広く使用されている。 | 既存のCommonJSモジュールとの互換性に制限がある。 |
| パフォーマンス | 同期的な読み込みのため、特にサーバーサイドではパフォーマンスが安定している。 | ネイティブサポートされた非同期読み込みにより、ブラウザ環境でのパフォーマンスが向上。 |
| エラーハンドリング | try-catch を使用して動的インポート時のエラーを捕捉可能。 | import 文自体は静的であり、import() を使用することで非同期的なエラーハンドリングが可能。 |
メリット・デメリットのまとめ
| 形式 | メリット | デメリット |
|---|---|---|
CommonJS (require) | – 広範な互換性と成熟度 – シンプルな同期的読み込み – 既存の多くのパッケージが対応 | – 静的解析が困難 – ブラウザでのネイティブサポートが限定的 – 名前付きエクスポートが不明確 |
ESモジュール (import) | – 静的解析により最適化が容易 – ネイティブのブラウザサポート – 名前付きエクスポートやデフォルトエクスポートが明確 | – 一部の古いツールやライブラリとの互換性が低い – CommonJSとの混在が複雑 – 非同期的な読み込みが必要な |
requireとimport共存方法
基本はimportを使い、createRequireでrequireを作って、importに対応していないモジュールを取り込む。
ESモジュールベースの場合
requireを作ってESM対応していないライブラリを取り込みます。
// example.mjs
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
// ESMで可能なモジュールのインポート
import fs from 'fs/promises';
// CommonJSモジュールのインポート
const express = require('express');
CommonJSベースの場合
動的importでCommonJS対応していないライブラリを取り込みます。
const { app, BrowserWindow,WebContentsView , ipcMain } = require('electron');
const path = require('path');
async function listRunningApplications() {
try {
// 動的インポートで ps-list を読み込む
const psListModule = await import('ps-list');
// ps-list のデフォルトエクスポートを取得
const psList = psListModule.default || psListModule;
const processes = await psList();
// Extract unique process names
const uniqueProcessNames = [
...new Set(processes.map((proc) => proc.name)),
];
console.log('Currently Running Applications:');
uniqueProcessNames.forEach((name, index) => {
console.log(`${index + 1}. ${name}`);
});
// Optionally, send this data to the renderer process
if (mainWindow) {
mainWindow.webContents.send('process-list', uniqueProcessNames);
}
} catch (error) {
console.error('Error fetching process list:', error);
}
}
helperライブラリなどの記述方法
ES Moduleの場合
// helper.js
export function extractAddress(externalObj) {
const externalStr = externalObj.toString(); // 例: "[External: 574400f46430]"
const match = externalStr.match(/\\[External:\s+([0-9a-fA-F]+)\\]/);
if (match && match[1]) {
return `0x${match[1]}`;
}
throw new Error('アドレスの抽出に失敗しました。');
}
CommonJSの場合
// helper.js
function extractAddress(externalObj) {
const externalStr = externalObj.toString(); // 例: "[External: 574400f46430]"
const match = externalStr.match(/\\[External:\s+([0-9a-fA-F]+)\\]/);
if (match && match[1]) {
return `0x${match[1]}`;
}
throw new Error('アドレスの抽出に失敗しました。');
}
// 関数をエクスポートする(CommonJS形式)
module.exports = { extractAddress };
main.jsには (ES Module)
import { extractAddress } from './helper.js';
main.jsには (CommonJS)
const { extractAddress } = require('./helper');
その他 electronの場合
以下の様に配置されている時、main.jsをESMで使うには、同じディレクトリ下にあるsrc/package.jsonに『”type”:”module”』を書込む。

preloader.jsはElectron20からsandbox化されており、node.jsの全ての機能が使えません。 preloader.jsはipc通信する為だけに使うようになったようです。 なのでimportは使えずrequireしか使えません。
またelectronの場合、renderer.jsではnode.jsのパッケージを使用できないので、ipcMain通信を使ってmain.jsやreploader.jsで処理をしてもらって、その返事だけを受け取る方法を使います。 下の例ではpreloader.jsで『api』と言う名前でsendとreceiveメソッドを持ったオブジェクトを作って、renderer.jsが使えるように公開(expose)しています。 この例では、
- renderer.jsのwindow.api.sendで=>preloader.js
- preloader.jsの sendで受けて、”toMain”チャンネルで=>main.js
- main.jsのipcMain.onで受けて、event.sender.sendで=>preloader.js
- preloader.jsのreceiveで受けて、ipcRenderer.onで=>renderer.js
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
// 安全なAPIを定義してレンダラーに公開
contextBridge.exposeInMainWorld('api', {
send: (channel, data) => {
// 許可されたチャンネルのみ通信を許可
const validChannels = ['toMain'];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
const validChannels = ['fromMain'];
if (validChannels.includes(channel)) {
// リスナー登録
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
}
});
main.js
app.whenReady().then(createMainWindow);
// IPCイベントのハンドリング例
ipcMain.on('toMain', (event, args) => {
console.log('Received from renderer:', args);
// 必要な処理を実行
event.sender.send('fromMain', 'データを受け取りました');
});
renderer.js
// renderer.js
// 何らかのイベント(例: ボタンクリック)でメインプロセスにデータを送信
document.getElementById('sendButton').addEventListener('click', () => {
window.api.send('toMain', 'こんにちは、メインプロセス!');
});
// メインプロセスからのメッセージを受信
window.api.receive('fromMain', (data) => {
console.log('Received from main:', data);
});
index.html
// renderer.js
// 何らかのイベント(例: ボタンクリック)でメインプロセスにデータを送信
document.getElementById('sendButton').addEventListener('click', () => {
window.api.send('toMain', 'こんにちは、メインプロセス!');
});
// メインプロセスからのメッセージを受信
window.api.receive('fromMain', (data) => {
console.log('Received from main:', data);
});

コメント