メインコンテンツへスキップ

ElectronにおけるESモジュール(ESM)

はじめに

ECMAScriptモジュール(ESM)形式は、JavaScriptパッケージをロードする標準的な方法です。

ChromiumとNode.jsはそれぞれ独自のESM仕様の実装を持っており、Electronはコンテキストに応じて使用するモジュールローダーを選択します。

このドキュメントでは、ElectronにおけるESMの制限事項と、Electron、Node.js、ChromiumにおけるESMの違いについて概説します。

情報

この機能はelectron@28.0.0で追加されました。

概要:ESMサポートマトリックス

この表は、ESMがサポートされている場所と、どのESMローダーが使用されているかの概要を示しています。

プロセスESMローダープリロードでのESMローダー適用可能な要件
メインプロセスNode.jsN/A
レンダラー(サンドボックス化)Chromiumサポートされていません
レンダラー(サンドボックス化解除&コンテキスト分離)ChromiumNode.js
レンダラー(サンドボックス化解除&コンテキスト非分離)ChromiumNode.js

メインプロセス

ElectronのメインプロセスはNode.jsコンテキストで実行され、そのESMローダーを使用します。NodeのESMドキュメントに従って使用してください。メインプロセスでファイルのESMを有効にするには、次の条件のいずれかを満たす必要があります。

  • ファイルが.mjs拡張子で終わっている。
  • 最も近い親のpackage.jsonで"type": "module"が設定されている。

詳細については、Nodeのモジュールシステムの決定ドキュメントを参照してください。

注意点

アプリのreadyイベントの前に、awaitを積極的に使用する必要があります。

ESモジュールは**非同期**にロードされます。これは、メインプロセスエントリポイントのインポートからの副作用のみがreadyイベントの前に実行されることを意味します。

これは、特定のElectron API(例:app.setPath)は、アプリのreadyイベントが発行される**前**に呼び出す必要があるため重要です。

Node.js ESMでトップレベルのawaitが使用できるようになったため、readyイベントの前に実行する必要があるすべてのPromiseをawaitしてください。 そうしないと、コードが実行される前にアプリがreadyになる可能性があります。

これは、動的なESMインポートステートメント(静的インポートは影響を受けません)で特に重要です。たとえば、index.mjsがトップレベルでimport('./set-up-paths.mjs')を呼び出す場合、動的インポートが解決されるまでにアプリはすでにreadyになっている可能性があります。

index.mjs(メインプロセス)
// add an await call here to guarantee that path setup will finish before `ready`
import('./set-up-paths.mjs')

app.whenReady().then(() => {
console.log('This code may execute before the above import')
})
トランスパイラの変換

JavaScriptトランスパイラ(例:Babel、TypeScript)は、Node.jsがESMインポートをサポートする前に、これらの呼び出しをCommonJSのrequire呼び出しに変換することにより、歴史的にESモジュール構文をサポートしていました。

例:@babel/plugin-transform-modules-commonjs

@babel/plugin-transform-modules-commonjsプラグインは、ESMインポートをrequire呼び出しに変換します。正確な構文は、importInterop設定によって異なります。

@babel/plugin-transform-modules-commonjs
import foo from "foo";
import { bar } from "bar";
foo;
bar;

// with "importInterop: node", compiles to ...

"use strict";

var _foo = require("foo");
var _bar = require("bar");

_foo;
_bar.bar;

これらのCommonJS呼び出しは、モジュールコードを同期的にロードします。トランスパイルされたCJSコードをネイティブESMに移行する場合は、CJSとESMのタイミングの違いに注意してください。

レンダラープロセス

ElectronのレンダラープロセスはChromiumコンテキストで実行され、ChromiumのESMローダーを使用します。実際には、これはimportステートメントが

  • Node.js組み込みモジュールにアクセスできない
  • node_modulesからnpmパッケージをロードできない
<script type="module">
import { exists } from 'node:fs' // ❌ will not work!
</script>

ことを意味します。npm経由でJavaScriptパッケージをレンダラープロセスに直接ロードする場合は、webpackやViteなどのバンドラーを使用して、クライアント側の使用のためにコードをコンパイルすることをお勧めします。

プリロードスクリプト

レンダラーのプリロードスクリプトは、利用可能な場合、Node.js ESMローダーを使用します。ESMの可用性は、レンダラーのsandboxcontextIsolation設定の値に依存し、ESMローディングの非同期性によるいくつかの注意点があります。

注意点

ESMプリロードスクリプトは.mjs拡張子を持つ必要があります。

プリロードスクリプトは"type": "module"フィールドを無視するため、ESMプリロードスクリプトでは.mjsファイル拡張子を使用する必要があります。

サンドボックス化されたプリロードスクリプトはESMインポートを使用できません。

サンドボックス化されたプリロードスクリプトは、ESMコンテキストなしでプレーンなJavaScriptとして実行されます。外部モジュールを使用する必要がある場合は、プリロードコードにバンドラーを使用することをお勧めします。electron APIのロードは引き続きrequire('electron')で行われます。

サンドボックス化の詳細については、プロセスサンドボックス化のドキュメントを参照してください。

サンドボックス化解除されたESMプリロードスクリプトは、コンテンツのないページではページロード後に実行されます。

レンダラーがロードしたページのレスポンスボディが完全に空の場合(つまり、Content-Length: 0)、そのプリロードスクリプトはページロードをブロックせず、競合状態が発生する可能性があります。

これが影響する場合は、レスポンスボディに何か(例:空のhtmlタグ(<html></html>))を含めるか、ページロードをブロックするCommonJSプリロードスクリプト(.jsまたは.cjs)に戻してください。

動的なNode.js ESMインポートを使用するには、ESMプリロードスクリプトをコンテキスト分離する必要があります。

サンドボックス化解除されたレンダラープロセスでcontextIsolationフラグが有効になっていない場合、NodeのESMローダーを介してファイルを動的にimport()することはできません。

preload.mjs
// ❌ these won't work without context isolation
const fs = await import('node:fs')
await import('./foo')

これは、Chromiumの動的なESM import()関数が通常レンダラープロセスで優先され、コンテキスト分離がないと、動的インポートステートメントでNode.jsが使用可能かどうかを知る方法がないためです。コンテキスト分離を有効にすると、レンダラーの分離されたプリロードコンテキストからのimport()ステートメントをNode.jsモジュールローダーにルーティングできます。