跳转到主内容

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.jsN/A
渲染进程(沙盒化)Chromium不支持
渲染进程(非沙盒化 & 上下文隔离)ChromiumNode.js
  • [非沙盒化 ESM 预加载脚本将在没有内容的页面加载后运行](#unsandboxed-esm-preload-scripts-will -run-dun-page-load--with-no-content)
  • [ESM 预加载脚本必须有 . mjs扩展](#esm-preload-scripts-must have-the-mjs-extension)
渲染进程(非沙盒化 & 非上下文隔离)ChromiumNode.js
  • [非沙盒化 ESM 预加载脚本将在没有内容的页面加载后运行](#unsandboxed-esm-preload-scripts-will -run-dun-page-load--with-no-content)
  • ESM 预加载脚本必须有 . mjs 扩展名
  • [ESM 预加载 脚本必须是隔离上下文中才能使用动态Node.js ESM 导入](#esm-preload-scripts-must be separated-context to use-dynamic-nodejs-esm-imports)

主进程

Electron的主进程在 Node.js 环境中运行并使用其ESM 加载器。 使用方法参考Node's ESM 文档。 若要在主进程中的文件中启用 ESM ,必须满足以下条件:

  • 文件以.mjs扩展名结尾
  • 最近的 package.json 有"type": "module"" 设置

更多详情请参阅 Node 文档 模块系统

注意事项

您必须在应用程序的 ready 事件之前使用 await

ES模块加载是 异步 的 。 这意味着只有主进程入口的导入会在 "准备" 事件之前执行副效果。

这很重要,因为某些Electron API(例如[app.setPath](latest/api/app.md#appsetpathname-path)) 需要在 app 的 ready` 事件 之前 被调用 。

在 Node.js ESM 中使用顶层 await,请确保所有 Promise 在 app 的 ready 事件之前完成。 否则,您的应用可能会在代码执行之前`ready'。

这对于动态导入语句(静态导入不受影响) 来说尤为重要。 例如,如果 index.mjs 在顶层执行 import('./set-up-paths.mjs'), 应用会在动态导入解析完成后触发 ready 事件。

index.mjs (Main Process)
// 在 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 设置

@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 软件包
<script type="module">
import { exists } from 'node:fs' // ❌ 无效
</script>

如果您想要通过 npm 直接将JavaScript 包加载到渲染进程中, 我们建议生成客户端时使用诸如webpack 或 Vite 之类的打包工具来编译您的代码。

Preload 脚本

渲染进程的预加载脚本将使用 Node.js ESM loader ,如果可用。 ESM 的可用性将取决于渲染器的 sandboxcontextIsolation 配置项的值, 并且由于ESM加载的异步性质,还有其他一些注意事项。

注意事项

ESM 预加载脚本必须有 .mjs 扩展

预加载脚本将忽略 "type": "module"" 字段,所以你_必须_ 在你的 ESM 预加载脚本中使用 .mjs` 文件扩展。

沙盒化的预加载脚本不能使用 ESM 导入

沙盒化的预加载脚本作为普通JavaScript运行,没有ESM 环境。 如果您需要使用外部模块,我们建议使用打包工具打包预加载代码。 加载electron API 仍然通过 require('electron')完成。

关于沙盒的更多信息,见进程沙盒化 文档。

非沙箱化的 ESM 预加载脚本将在没有内容的页面加载后运行

如果渲染器加载页面的响应正文为空(例如:Content-Leng:0),其预加载脚本不会阻止页面加载,这可能导致竞态条件。

如果这影响到你,改变你的响应,让它包含一些东西 (例如,一个空的‘html’签(<html></html>))或者切换回使用 CommonJS 预加载脚本(. jsor. cjs) ,它将阻塞页面加载。

ESM 预加载脚本必须是上下文隔离才能使用动态Node.js ESM 导入。

如果您的非沙盒渲染进程没有启用contextIsolation, 不能通过 Node 的 ESM 加载程序动态地 import()文件。

preload.mjs
// ❌ 非上下文隔离无法使用
const fs = await import('node:fs')
await import('./foo')

这是因为Chromium的动态ESM import() 函数通常优先在渲染进程中,而无需上下文隔离, 无法知道 Node.js 在动态导入语句中是否可用。 如果您启用上下文隔离,则可以将渲染器的上下文隔离中的import()语句找到 Node.js 模块加载器。