跳到主要內容

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 匯入之前,就已經歷史性地支援 ES 模組語法,方法是將這些呼叫轉換為 CommonJS require 呼叫。

範例:@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 模組載入器。