これも結構ややこしいので纏めました。
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); });
コメント