Electron 中的 ES 模块(ESM)¶
简介¶
ECMAScript 模块(ESM)格式是加载 JavaScript 包的标准方式。
Chromium 和 Node.js 各自拥有 ESM 规范的实现,Electron 会根据上下文选择使用哪个模块加载器。
本文档旨在概述 Electron 中 ESM 的限制,以及 Electron 中的 ESM 与 Node.js 和 Chromium 中的 ESM 之间的差异。
:::info
此功能在 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,请确保对每个需要在 ready 事件之前执行的 Promise 都使用 await。否则,你的应用可能会在代码执行之前就已 ready。
对于动态 ESM import 语句,这一点尤其需要牢记(静态导入不受影响)。
例如,如果 index.mjs 在顶层调用 import('./set-up-paths.mjs'),那么当该动态导入解析时,应用很可能已经处于 ready 状态。
``js @ts-expect-error=[2] title='index.mjs (Main Process)'
// add an await call here to guarantee that path setup will finish beforeready`
import('./set-up-paths.mjs')
app.whenReady().then(() => { console.log('This code may execute before the above import') })
:::caution 转译器转换
JavaScript 转译器(例如 Babel、TypeScript)在 Node.js 支持 ESM 导入之前,历史上通过将 ESM 语法转换为 CommonJS
`require` 调用来支持 ES Module 语法。
<details markdown="1">
<summary markdown="span">示例:@babel/plugin-transform-modules-commonjs</summary>
`@babel/plugin-transform-modules-commonjs` 插件会将
ESM 导入转换为 `require` 调用。具体语法取决于
[`importInterop` 设置](https://babeljs.io/docs/babel-plugin-transform-modules-commonjs#importinterop)。
<!-- eslint-skip -->
```js @nolint @ts-nocheck title='@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 包
如果你希望直接通过 npm 将 JavaScript 包加载到渲染进程中,我们建议 使用 webpack 或 Vite 等打包工具,将代码编译为客户端可消费的形式。
预加载脚本¶
渲染进程的预加载脚本会在_可用时_使用 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 或 .cjs),这会阻塞页面加载。
ESM 预加载脚本必须启用上下文隔离才能使用动态 Node.js ESM 导入¶
如果你的未沙箱化渲染器进程未启用 contextIsolation 标志,
则无法通过 Node 的 ESM 加载器动态 import() 文件。
js @ts-nocheck title='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 模块加载器。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 el/electron