返回博客

Electron app.getVersion() 开发模式静默返回框架版本号

某天,我在某个桌面客户端 App 的「检查更新」页面里看到了一个版本号——v42.2.0。这个数字看起来相当可疑:我们的 App 才发布到 0.x,哪来的 42 版?打开 node_modules/electron/package.json 一看,"version": "42.2.0"。完全一致。

更诡异的是:同一份代码打包成 dmg 安装后,版本号显示完全正常,是预期的 0.1.4。只有 npm run dev 跑起来的开发环境会错。

现象

  • 代码里写着 ipcMain.handle('app:version', () => app.getVersion())
  • 开发模式启动命令:electron dist/src/main.js
  • UI 上版本号:v42.2.0(与 node_modules/electron/package.json 里的版本完全吻合)
  • 打包后的 dmg 安装运行:版本号 v0.1.4,正确

整个过程没有任何报错、警告或日志——它就这么静默地给了你一个错误的数字。

根因

Electron 文档里有这样一句话(一句,仅一句):

Returns string — The version of the loaded application. If no version is found in the application's package.json file, the version of the current bundle or executable is returned.

"找不到应用的 package.json version 字段时,回退到 Electron 自身版本"——这个 fallback 是有文档记载的行为,但它完全静默,不打日志,不抛异常。

Electron 从入口脚本所在目录开始向上找 package.json。问题在于路径:

  • 入口是 dist/src/main.js,向上走经过 dist/src/dist/
  • 如果构建工具(esbuild、tsc)在 dist/ 输出了一个不带 version 字段的 package.json,Electron 就会在这里停止,判定"找不到版本",触发 fallback
  • 或者项目是 monorepo / 嵌套子包结构,中间层有多个 package.json,解析到了错误的那一个

打包(packaged)模式下,App 被打进 app.asar 归档,package.json 总在 asar 根部,路径无歧义,所以打包后一直正常。

坑:app.getVersion() 在开发模式下可能静默返回 Electron 框架版本

当 Electron 通过 electron path/to/main.js 形式启动、且路径解析链上存在不含 version 字段的中间 package.json(常见于 dist/ 输出目录)时,app.getVersion() 会静默回退到 Electron 自身版本,不打任何警告。

快速验证:UI 显示的版本号若与 node_modules/electron/package.json 里的版本完全吻合,即中招。

正解

不要信任 app.getVersion(),改用 app.getAppPath() 显式读取 package.json

// main.ts
import { readFileSync } from 'node:fs'
import path from 'node:path'
import { app, ipcMain } from 'electron'
 
function getAppVersion(): string {
  try {
    const appPath = app.getAppPath()
    const pkg = JSON.parse(readFileSync(path.join(appPath, 'package.json'), 'utf8'))
    return String(pkg.version ?? 'unknown')
  } catch {
    // 极端 fallback:asar 异常 / JSON 损坏时不让 IPC throw
    return app.getVersion()
  }
}
 
ipcMain.handle('app:version', () => getAppVersion())

app.getAppPath() 的行为在两种模式下均可靠:

  • 开发模式:返回入口脚本所在的项目根目录(含真实 package.json
  • 打包模式:返回 app.asar 挂载点(含打包时写入的 package.json

两种模式都能找到正确的 package.json,再用 Node 原生 readFileSync 读取,完全绕开 Electron 的 fallback 路径。

正解:用 app.getAppPath() 显式读取 package.json

app.getAppPath() 在开发和打包两种模式下都能正确解析到应用根目录,是唯一在两种环境下都可靠的路径来源。不要用 process.env.npm_package_version(仅 npm 脚本下有值)或 require('../package.json')(ASAR 打包后路径语义变化)。

几个常见的错误修法

  1. electron . 替代 electron path/to/main.js:能改善路径解析,但脆弱——任何人改了 dev 脚本就复现,且强制 package.jsonmain 字段耦合 dev/prod 路径约定。
  2. process.env.npm_package_version:只在 npm run 启动时有值,直接调 Electron 可执行文件时为 undefined
  3. require('../package.json').version:开发下可用,ASAR 打包后相对路径语义改变,可能找到错误文件或报错。

检测存量问题

# 找主进程里所有直接调用点
git grep -n 'app\.getVersion()' -- '*.ts' '*.js' '*.mjs'
 
# 找暴露给 renderer 的 IPC channel(可能传递版本)
git grep -in 'getVersion\|app:version\|app:get-version' -- '*.ts' '*.tsx'

对每一处 app.getVersion() 问自己:如果它返回了 Electron 框架版本号,会不会有用户察觉或出错(关于页、遥测、更新检查)?有则替换。

一句话外卖

Electron app.getVersion() 的 fallback 是静默的——开发模式路径歧义导致找不到 package.json 时,它会把 Electron 框架版本当应用版本吐出来,不打任何日志;唯一可靠的替代是 app.getAppPath() + readFileSync