第23章 Electron 中的 ES Modules(ESM)
import('./set-up-paths.mjs')
app.whenReady().then(() => {
console.log('This code may execute before the above import')
})
转译器对 ESM 的支持
在 Node.js 原生支持 ESM 之前,JavaScript 转译器(如 Babel、TypeScript)早就支持 ES Module 语法。它们通过将 ESM 导入转换为 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;
// 当 "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 包
如果希望在渲染进程中通过 npm 直接加载 JavaScript 包,我们建议使用 webpack 或 Vite 这类打包工具来编译代码,供客户端使用。
Preload scripts
在可用情况下,渲染进程的 preload script 会使用 Node.js ESM 加载器。ESM 的可用性取决于该渲染进程 sandbox 和 contextIsolation 选项的值,且由于 ESM 加载是异步的,还存在一些其他注意事项。
Caveats
ESM preload scripts 必须使用 .mjs 扩展名
Preload scripts 会忽略 "type": "module" 字段,因此 ESM preload scripts 必须使用 .mjs 文件扩展名。
沙箱化的 preload scripts 无法使用 ESM import
沙箱化的 preload scripts 在没有 ESM 上下文的环境中以纯 JavaScript 方式运行。如果需要引入外部模块,我们建议使用打包工具处理 preload 代码。加载 electron API 仍需通过 require('electron') 完成。
更多沙箱化信息请参阅 Process Sandboxing 文档。
对于没有内容的页面,非沙箱化的 ESM preload scripts 会在页面加载完成后才运行
如果渲染进程所加载页面的响应体完全为空(即 Content-Length: 0),其 preload script 不会阻塞页面加载,这可能导致竞态条件。
如果这对你造成影响,请在响应体中加入一些内容(例如空的 html 标签 <html></html>),或换回使用 CommonJS preload script(.js 或 .cjs),后者会阻塞页面加载。
ESM preload scripts 必须启用上下文隔离才能使用动态 Node.js ESM import
如果你的非沙箱化渲染进程没有启用 contextIsolation 标志,则无法通过 Node.js ESM 加载器动态 import() 文件。
preload.mjs// ❌ 在没有上下文隔离的情况下,以下代码无法正常工作
const fs = await import('node:fs')
await import('./foo')
这是因为在渲染进程中,Chromium 的动态 ESM import() 函数通常具有优先权,且在没有上下文隔离的情况下,无法判断动态 import 语句中 Node.js 是否可用。如果启用上下文隔离,来自渲染进程隔离 preload 上下文的 import() 语句则可以路由到 Node.js 模块加载器。