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 | N/A | |
| 渲染进程(沙盒化) | Chromium | 不支持 | |
| 渲染进程(非沙盒化 & 上下文隔离) | Chromium | Node.js |
|
| 渲染进程(非沙盒化 & 非上下文隔离) | Chromium | Node.js |
主进程
Electron 的主进程在 Node.js 环境中运行并使用其 ESM 加载器。 使用方法参考 Node's ESM 文档。 若要在主进程中的文件中启用 ESM ,必须满足以下条件:
- 文件以
.mjs扩展名结尾 - 最近的 package.json 设置了
"type": "module"
更多详情请参阅 Node 文档 模块系统
注意事项
您必须在 app 的 ready 事件之前使用 await
ES 模块的加载是异步的 。 这意味着只有主进程入口的导入会在 ready 事件之前执行副作用。
这很重要,因为某些 Electron API(例如 app.setPath)需要在 app 的 ready 事件之前调用。
在 Node.js ESM 中使用顶层 await,请确保所有 Promise 在 app 的 ready 事件之前完成。 否则,您的应用可能会在代码执行之前 ready。
这对于动态导入语句(静态导入不受影响)来说尤为重要。
例如,如果 index.mjs 在顶层执行 import('./set-up-paths.mjs'), 应用会在动态导入解析完成后触发 ready 事件。
// 在 app `ready` 之前 set-up-paths 完成
import('./set-up-paths.mjs')
app.whenReady().then(() => {
console.log('This code may execute before the above import')
})
在 Node.js 支持 ESM 导入之前,JavaScript 转译器(如Babel,TypeScript)支持 ESM 模块语法是通过将这些导入转换为 CommonJS require 调用来实现的。示例:@babel/plugin-transform-modules-commonjs
@babel/plugin-transform-modules-commonjs插件将把
ESM 导入转换为 CommonJS require 调用。 确切的语法将取决于
importInterop 设置。import foo from "foo";
import { bar } from "bar";
foo;
bar;
// 配置 "importInterop: node", 编译为 ...
"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' // ❌ 行不通!
</script>
如果您想要通过 npm 直接将 JavaScript 包加载到渲染进程中, 我们建议使用诸如 Webpack 或 Vite 之类的打包工具来编译您的代码以供客户端使用。
Preload 脚本
如果可用,渲染进程的预加载脚本将使用 Node.js ESM 加载器。
ESM 的可用性将取决于渲染器 sandbox 和 contextIsolation
配置项的值, 并且由于 ESM 加载的异步性质,还有其他一些注意事项。
注意事项
ESM 预加载脚本必须有 .mjs 扩展
预加载脚本将忽略 "type": "module" 字段,所以你 必须 在你的 ESM 预加载脚本中使用 .mjs 文件扩展。
沙盒化的预加载脚本不能使用 ESM 导入
沙盒化的预加载脚本将作为普通 JavaScript 脚本运行,没有 ESM 上下文。 如果您需要使用外部模块,我们建议使用打包工具打包预加载代码。 加载 electron API 仍然通过 require('electron') 完成。
关于沙盒的更多信息,参阅进程沙盒化文档。
非沙箱化的 ESM 预加载脚本将在没有内容的页面加载后运行
如果渲染器加载的页面的响应体 完全 空白(比如 Content-Length:0),其预加载脚本不会阻止页面加载,这可能导致竞态条件。
如果这对您有影响,修改你的响应体,至少让它有 一些东西(例如,一个空的 html 标签(<html></html>))或者切换回使用 CommonJS 的预加载脚本(. js or . cjs),这将会阻塞页面加载。
ESM 预加载脚本必须是上下文隔离才能使用动态 Node.js ESM 导入。
如果您的非沙盒化渲染进程没有启用 contextIsolation 标志,您不能通过 Node 的 ESM 加载器动态地 import()文件。
// ❌ 非上下文隔离无法使用
const fs = await import('node:fs')
await import('./foo')
这是因为 Chromium 的动态 ESM import() 函数通常在渲染进程中具有优先权,没有上下文隔离,您是无法知道 Node.js 在动态导入语句中是否可用。 如果您启用了上下文隔离,渲染器的隔离预加载上下文中的 import() 语句就能导向 Node.js 模块加载器。