Electron 中的 ES 模組 (ESM)
簡介
ECMAScript 模組 (ESM) 格式是載入 JavaScript 套件的標準方式。
Chromium 和 Node.js 有各自的 ESM 規範實作,而 Electron 則會根據內容選擇要使用的模組載入器。
本文旨在概述 Electron 中 ESM 的限制,以及 Electron 中的 ESM 與 Node.js 和 Chromium 中 ESM 之間的差異。
此功能已在 electron@28.0.0
中加入。
摘要:ESM 支援矩陣
此表格概述了 ESM 在哪些地方受支援以及使用哪個 ESM 載入器。
處理程序 | ESM 載入器 | 預載中的 ESM 載入器 | 適用要求 |
---|---|---|---|
主處理程序 | Node.js | 不適用 | |
渲染器 (沙盒化) | 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
,請務必 await
您需要在 ready
事件之前執行的每個 Promise。否則,您的應用程式可能會在您的程式碼執行之前就進入 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 套件載入到渲染器處理程序中,我們建議您使用 bundler (例如 webpack 或 Vite) 編譯程式碼,以供用戶端使用。
預載腳本
渲染器的預載腳本會在可用時使用 Node.js ESM 載入器。ESM 的可用性取決於其渲染器的 sandbox
和 contextIsolation
偏好設定值,並且由於 ESM 載入的非同步特性而有一些其他注意事項。
注意事項
ESM 預載腳本必須具有 .mjs
副檔名
預載腳本會忽略 "type": "module"
欄位,因此您必須在 ESM 預載腳本中使用 .mjs
檔案副檔名。
沙盒化的預載腳本無法使用 ESM 匯入
沙盒化的預載腳本會以純 JavaScript 執行,而沒有 ESM 環境。如果您需要使用外部模組,建議您為預載程式碼使用 bundler。載入 electron
API 仍然是透過 require('electron')
完成。
如需有關沙盒化的詳細資訊,請參閱處理程序沙盒化文件。
未沙盒化的 ESM 預載腳本會在頁面載入後,於沒有內容的頁面上執行
如果渲染器載入頁面的回應主體完全為空 (即 Content-Length: 0
),則其預載腳本不會封鎖頁面載入,這可能會導致競爭情況。
如果這會影響您,請將您的回應主體變更為包含某些內容 (例如空的 html
標籤 (<html></html>
)),或換回使用 CommonJS 預載腳本 (.js
或 .cjs
),這將會封鎖頁面載入。
ESM 預載腳本必須隔離內容才能使用動態 Node.js 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 模組載入器。