跳至主要內容

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不支援
渲染器 (非沙盒化且已隔離內容)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,請務必 await 您需要在 ready 事件之前執行的每個 Promise。否則,您的應用程式可能會在您的程式碼執行之前就進入 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 套件載入到渲染器處理程序中,我們建議您使用 bundler (例如 webpack 或 Vite) 編譯程式碼,以供用戶端使用。

預載腳本

渲染器的預載腳本會在可用時使用 Node.js ESM 載入器。ESM 的可用性取決於其渲染器的 sandboxcontextIsolation 偏好設定值,並且由於 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() 檔案。

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 模組載入器。