跳转至

ASAR 存档

创建 应用分发 后,应用的源代码通常会被打包成一个 ASAR 存档。这是一种为 Electron 应用设计的简单扩展存档格式。通过打包应用,我们可以缓解 Windows 上长路径名带来的问题,加快 require 的速度,并避免源代码被粗略检查发现。

打包后的应用运行在虚拟文件系统中,大多数 API 都可以正常工作,但在某些情况下,由于一些注意事项,你可能需要显式地处理 ASAR 存档。

使用 ASAR 存档

在 Electron 中有两组 API:由 Node.js 提供的 Node API 和由 Chromium 提供的 Web API。这两组 API 都支持从 ASAR 存档中读取文件。

Node API

由于 Electron 中的特殊补丁,fs.readFile 和 require 等 Node API 会将 ASAR 存档视为虚拟目录,并将其中的文件视为文件系统中的普通文件。

例如,假设我们在 /path/to 下有一个 example.asar 存档:

$ asar list /path/to/example.asar
/app.js
/file.txt
/dir/module.js
/static/index.html
/static/main.css
/static/jquery.min.js

读取 ASAR 存档中的文件:

const fs = require('node:fs')

fs.readFileSync('/path/to/example.asar/file.txt')

列出存档根目录下的所有文件:

const fs = require('node:fs')

fs.readdirSync('/path/to/example.asar')

使用存档中的模块:

```js @ts-nocheck require('./path/to/example.asar/dir/module.js')

存档中的目录也可以使用 `fs.opendir` 进行迭代,存档中存储的符号链接可以使用 `fs.readlink` 检查,它会返回相对于链接本身的链接目标,就像真实文件系统一样。

你也可以使用 `BrowserWindow` 显示 ASAR 存档中的网页:

```js
const { BrowserWindow } = require('electron')

const win = new BrowserWindow()

win.loadURL('file:///path/to/example.asar/static/index.html')

Web API

在网页中,可以使用 file: 协议请求存档中的文件。与 Node API 一样,ASAR 存档会被视为目录。

例如,使用 $.get 获取文件:

<script>
let $ = require('./jquery.min.js')
$.get('file:///path/to/example.asar/file.txt', (data) => {
  console.log(data)
})
</script>

将 ASAR 存档视为普通文件

在某些情况下(例如验证 ASAR 存档的校验和),我们需要将 ASAR 存档的内容作为文件读取。为此,你可以使用内置的 original-fs 模块,它提供不带 asar 支持的原始 fs API:

const originalFs = require('original-fs')

originalFs.readFileSync('/path/to/example.asar')

你也可以将 process.noAsar 设置为 true,以禁用 fs 模块对 asar 的支持:

const fs = require('node:fs')

process.noAsar = true
fs.readFileSync('/path/to/example.asar')

Node API 的限制

尽管我们努力让 Node API 中的 ASAR 存档尽可能像目录一样工作,但由于 Node API 的底层特性,仍然存在一些限制。

存档是只读的

存档不能被修改,因此所有可以修改文件的 Node API 都无法用于 ASAR 存档。

无法将工作目录设置为存档中的目录

尽管 ASAR 存档被视为目录,但文件系统中并不存在实际的目录,因此你永远无法将工作目录设置为 ASAR 存档中的目录。将它们作为某些 API 的 cwd 选项传入也会导致错误。

存档中文件的文件描述符

fs.open、fs.openSync 和 fs.promises.open 会为 ASAR 存档中的文件返回真实的文件描述符(以及 FileHandle),这些描述符由存档本身提供支持。因此,基于它们构建的 API——fs.read、fs.readv、fs.fstat、fs.readFile(fd)、fs.createReadStream、FileHandle#readFile、FileHandle#createReadStream 等——会直接从存档中读取,而不需要任何临时副本。同样,fs.copyFile、fs.cp 及其同步和 Promise 变体会直接从存档复制到目标位置。

由于存档是只读的,使用任何允许写入的标志(w、a、r+ 等)打开存档内的文件都会以 EACCES 失败,对这样的描述符调用 fs.fchmod、fs.fchown 和 fs.futimes 也会以 EACCES 失败。该描述符只是向 Node 的 fs 模块标识条目;它并不由文件内容提供支持,因此将其传递给 fs 之外的代码(例如从原始描述符读取的本地插件、child_process stdio、net.Socket({ fd }) 或 http2stream.respondWithFile())会以 EBADF 失败,并且不受支持。

某些 API 需要额外解包

对于依赖将真实文件路径传递给底层系统调用的 API,Electron 会将所需文件提取到临时文件中,并将临时文件的路径传递给这些 API,使其能够工作。这会给这些 API 增加少量开销。

需要额外解包的 API 包括:

  • child_process.execFile
  • child_process.execFileSync
  • process.dlopen - 用于 require 加载本地模块

fs.stat 的虚假 Stat 信息

fs.stat 及其相关方法对 asar 存档中的文件返回的 Stats 对象是通过猜测生成的,因为这些文件并不存在于文件系统中。因此,除了获取文件大小和检查文件类型之外,你不应该信任该 Stats 对象。

执行 ASAR 存档内的二进制文件

有一些可以执行二进制的 Node API,例如 child_process.exec、child_process.spawn 和 child_process.execFile,但只有 execFile 支持执行 ASAR 存档内的二进制文件。

这是因为 exec 和 spawn 接受的是 command 而不是 file 作为输入,并且 command 会在 shell 下执行。没有可靠的方法来确定某个命令是否使用了 asar 存档中的文件,即使我们确定了,也无法保证替换命令中的路径不会产生副作用。

向 ASAR 存档添加未解包文件

如上所述,某些 Node API 在调用时会将其中的文件解包到文件系统。除了性能问题外,各种防病毒扫描程序也可能因这种行为而被触发。

作为变通方法,你可以使用 --unpack 选项让某些文件保持未打包状态。在以下示例中,原生 Node.js 模块的共享库将不会被打包:

$ asar pack app app.asar --unpack *.node

运行该命令后,你会发现与 app.asar 文件一起创建了一个名为 app.asar.unpacked 的文件夹。它包含已解包的文件,并且应与 app.asar 归档一起分发。

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 el/electron