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.js | N/A | |
レンダラー(サンドボックス化) | Chromium | サポートされていません | |
レンダラー(サンドボックス化解除&コンテキスト分離) | Chromium | Node.js | |
レンダラー(サンドボックス化解除&コンテキスト非分離) | Chromium | Node.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
になっている可能性があります。
// 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
設定によって異なります。
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の可用性は、レンダラーのsandbox
とcontextIsolation
設定の値に依存し、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()
することはできません。
// ❌ these won't work without context isolation
const fs = await import('node:fs')
await import('./foo')
これは、Chromiumの動的なESM import()
関数が通常レンダラープロセスで優先され、コンテキスト分離がないと、動的インポートステートメントでNode.jsが使用可能かどうかを知る方法がないためです。コンテキスト分離を有効にすると、レンダラーの分離されたプリロードコンテキストからのimport()
ステートメントをNode.jsモジュールローダーにルーティングできます。