本页目录

运行时版本、兼容迁移与持续维护

理解 Electron 绑定的 Chromium/Node 版本,建立升级差异、数据兼容与候选更新决策。

L2 · 能交付约 16 分钟阅读含示例、练习与验收
本页内容

升级的对象不只有 electron 这个包#

在浏览器项目中,用户通常独立更新 Chrome;Electron 应用把 Chromium、Node 和 Electron 一起交付。因此升级 Electron 同时改变浏览器引擎、主进程运行时和桌面 API 的组合。Vue 代码没有改,也可能受到浏览器行为变化影响;main 没有改,也可能遇到 Node 默认行为或原生模块 ABI 变化。

本章要求是能建立当前运行基线、阅读跨版本变化、安排分层验证并保留失败恢复路径。前置是构建、IPC、数据迁移与本地打包概念。我们不执行升级安装或在线更新,只通过完整离线实验建立比较与决策方法。当前用户要本地交付,升级报告不需要以公网发布作为完成条件。

本轮主线程实际读取的运行时是 Electron 44.3.0、内嵌 Node 24.20.0、Chromium 152.0.7977.78,平台为 win32;执行 npm 和构建的终端 Node 则是 22.22.0。二〇二六年九月十一日查询的独立 Node 最新 LTS 为 24.21.0。这几个数字不同是正常现象:升级终端 Node 不会替换已经打进 Electron 的 Node,二者属于不同运行环境。

版本号、支持周期与已弃用是不同信息#

Electron 采用语义化版本,但主版本升级包含的不只是 Electron API。Chromium 和 Node 版本变化也会成为重要升级因素;即使应用只调用少量 Electron API,也不能跳过底层变化说明。补丁版本通常用于修复,但“补丁”并不等于无需验证,安全修复也可能让依赖旧不安全行为的代码失效。版本策略

deprecated 表示不再推荐并可能未来移除,removed 表示当前已不可使用。还有默认值改变、行为改变和参数结构改变,它们未必让 TypeScript 编译报错。例如某个回调以前总有 webContents,未来可能允许 null;类型更新后可见,但 JavaScript 项目只有运行到路径才暴露。因此读变更说明时要将每条映射到实际调用点,而不是只搜索“编译失败”。

官方破坏性变更文档也可能列出未来版本计划。应确认条目属于当前升级目标还是尚未发布版本,不要看到页面顶部就把未来行为当作当前事实。跨多个主版本升级时,需要逐个检查中间版本变化,而不是只看目标版本最后一页。API 移除往往经历弃用期,长期忽略警告会把未来升级变成一次集中偿债。破坏性变更

实验一:采集运行快照并比较真正变化的层#

建立 runtime-report 目录,保存下面两个文件。安装固定 Electron 44.3.0 后执行 npm run report;此程序不创建窗口,只在 ready 后输出实际内嵌运行时并退出。它仍通过 Electron 执行,不可拿 node main.cjs 替代。本文没有再次运行这个入口,前面的实际版本来自主线程独立完成的运行时检查。

package.json
{
  "name": "runtime-report",
  "version": "1.0.0",
  "private": true,
  "main": "main.cjs",
  "scripts": { "report": "electron ." },
  "devDependencies": { "electron": "44.3.0" }
}
main.cjs
const { app } = require("electron");
app.whenReady().then(() => {
  console.log(JSON.stringify({
    appVersion: app.getVersion(),
    electron: process.versions.electron,
    node: process.versions.node,
    chrome: process.versions.chrome,
    modules: process.versions.modules,
    platform: process.platform,
    arch: process.arch,
    packaged: app.isPackaged
  }, null, 2));
  app.quit();
}).catch(error => { console.error(error); app.exit(1); });

保存输出时应同时记录来源是源码模式还是打包模式、机器平台与架构。只有一串 Electron 版本号不足以诊断原生模块问题,modules 是 Node 模块 ABI 标识,不是 npm 包版本。终端 node --version 与上述输出应分别保存,不能在用户报告里混写为“Node 已升级”。

下面的比较器是独立 Node 实验,保存 compare-runtime.cjs 与 compare-runtime-check.cjs,执行 node compare-runtime-check.cjs。fixture 中的 old/new 值刻意使用虚构编号,不冒充历史 Electron 发行组合;它只验证差异分类,不据此推断兼容性。

compare-runtime.cjs
const fields = ["electron", "node", "chrome", "modules", "platform", "arch"];
function compareRuntime(before, after) {
  for (const value of [before, after]) {
    for (const field of fields) {
      if (typeof value[field] !== "string" || !value[field]) {
        throw new Error("INVALID_RUNTIME:" + field);
      }
    }
  }
  const changes = fields.filter(field => before[field] !== after[field])
    .map(field => ({ field, before: before[field], after: after[field] }));
  return {
    changes,
    nativeReviewRequired: ["modules", "platform", "arch"]
      .some(field => before[field] !== after[field]),
    rendererReviewRequired: before.chrome !== after.chrome,
    mainReviewRequired: before.node !== after.node || before.electron !== after.electron
  };
}
module.exports = { compareRuntime };
compare-runtime-check.cjs
const assert = require("node:assert/strict");
const { compareRuntime } = require("./compare-runtime.cjs");
const before = {
  electron: "fixture-old", node: "fixture-node-a", chrome: "fixture-chrome-a",
  modules: "fixture-abi-a", platform: "win32", arch: "x64"
};
const after = { ...before, electron: "fixture-new", node: "fixture-node-b",
  chrome: "fixture-chrome-b", modules: "fixture-abi-b" };
const result = compareRuntime(before, after);
assert.equal(result.changes.length, 4);
assert.equal(result.nativeReviewRequired, true);
assert.equal(result.rendererReviewRequired, true);
assert.equal(result.mainReviewRequired, true);
assert.equal(compareRuntime(before, { ...before }).changes.length, 0);
assert.throws(() => compareRuntime({}, after), /INVALID_RUNTIME/);
console.log("runtime comparison checks passed");

输出 runtime comparison checks passed。nativeReviewRequired 只是提示需要检查原生依赖,不声称 ABI 相同就一定兼容;驱动还可能依赖系统库或 Electron 特定行为。rendererReviewRequired 也不代表必须重写 UI,而是提醒你用真实新 Chromium 验证关注路径。比较器提供检查方向,最终结论仍来自文档与运行证据。

从调用点建立升级清单#

先收集应用真正使用的 Electron 能力:窗口与导航、IPC、文件对话框、剪贴板、托盘、通知、协议、更新与原生模块。把官方变化映射到这些能力和具体文件。没有使用的 API 变化可以记录不适用;命中的变化则写出旧行为、新行为与替代实现。这样升级计划不会变成一份泛泛的“全部重新测试”。

例如从旧窗口打开事件迁移到 setWindowOpenHandler,不只是换一个方法名;你需要重新确认允许哪些目标、是否产生新窗口以及外部链接策略。迁移弃用的视图 API 时,也要检查尺寸、焦点、销毁和导航生命周期。只把 TypeScript 错误修到消失,可能留下行为不一致。安全默认值变得更严格时,应修正应用依赖,不要长期恢复旧弱配置来维持启动。

依赖升级也需要范围控制。先锁定 Electron 目标和必须配套的构建工具,跑已有基线,再逐步更新其他库。一次同时更新 Vue、路由、数据库驱动、打包器和 Electron,出现问题时很难归因。锁文件保留精确依赖图,升级报告应说明哪些包是主动升级、哪些是传递依赖变化,避免只列 package.json 中几行版本。

实验二:数据格式升级后,为什么不能直接降级应用#

建立 compatibility-lab,保存下面两个文件,执行 node compatibility-check.cjs。这里不处理真实数据库,只模拟读写兼容策略:旧应用只理解第一版数据,新应用能读取第一、二版,但只写第二版。迁移先生成新对象,验证后才替换当前引用;失败保留原值。没有访问外部更新源。

compatibility.cjs
function assertReadable(appPolicy, document) {
  if (!Number.isInteger(document?.schemaVersion) ||
      document.schemaVersion < appPolicy.readMin ||
      document.schemaVersion > appPolicy.readMax) {
    throw new Error("UNSUPPORTED_DATA_SCHEMA");
  }
}
function migrateToV2(document) {
  if (document?.schemaVersion !== 1 || !Array.isArray(document.items) ||
      document.items.some(item => typeof item !== "string")) {
    throw new Error("INVALID_V1_DOCUMENT");
  }
  return {
    schemaVersion: 2,
    items: document.items.map((text, index) => ({ id: "item-" + index, text }))
  };
}
function stageUpgrade(policy, current) {
  assertReadable(policy, current);
  const staged = current.schemaVersion === 1 ? migrateToV2(current) : structuredClone(current);
  if (staged.schemaVersion !== policy.writeVersion) throw new Error("WRITE_VERSION_MISMATCH");
  if (!Array.isArray(staged.items) || staged.items.some(item =>
    !item || typeof item.id !== "string" || typeof item.text !== "string")) {
    throw new Error("INVALID_V2_DOCUMENT");
  }
  return staged; // 返回待提交副本,不在函数内部覆盖 current。
}
module.exports = { assertReadable, stageUpgrade };
compatibility-check.cjs
const assert = require("node:assert/strict");
const { assertReadable, stageUpgrade } = require("./compatibility.cjs");
const oldApp = { readMin: 1, readMax: 1, writeVersion: 1 };
const newApp = { readMin: 1, readMax: 2, writeVersion: 2 };
let current = { schemaVersion: 1, items: ["draft"] };
const snapshot = structuredClone(current);
const staged = stageUpgrade(newApp, current);
assert.deepEqual(current, snapshot);
current = staged; // 模拟验证后的提交点。
assert.equal(current.schemaVersion, 2);
assert.throws(() => assertReadable(oldApp, current), /UNSUPPORTED_DATA_SCHEMA/);
assertReadable(newApp, current);
const broken = { schemaVersion: 1, items: [123] };
const original = JSON.stringify(broken);
assert.throws(() => stageUpgrade(newApp, broken), /INVALID_V1_DOCUMENT/);
assert.equal(JSON.stringify(broken), original);
console.log("compatibility checks passed: migration, downgrade refusal, source preservation");

这个实验让降级风险可见:新应用能读旧数据,不代表旧应用能读新数据。真实恢复可能选择恢复旧快照、保持新程序前向修复,或者在过渡期使用兼容写入。恢复快照会影响之后新增数据,不能简单把旧文件复制回来就宣称无损。应先保护当前现场,再比较哪些记录需要保留或转换。

读版本范围与写版本是显式合同。它们不替代完整 schema 校验,范围内数据仍可能损坏;也不能把未知高版本强行当作当前版解析。对于用户手动复制来的配置,拒绝并说明不兼容,比加载为空后自动保存默认值更安全。升级失败后的恢复入口,应能在主 UI 无法启动时仍提供必要诊断或导出能力。

更新渠道是产品策略,不是字符串排序#

stable、beta 等渠道通常表示不同风险与受众,不能仅因为 beta 的版本号更大就自动给 stable 用户安装。平台、架构、安装类型、系统最低要求和用户选择都参与候选筛选。版本比较要使用明确规则,不能依赖字符串比较;例如 1.10.0 与 1.9.0 的字典序不代表语义版本顺序。

自动更新包含可用性检查、下载、校验、准备安装与重启等阶段,每一步都可能失败。下载失败应该保留当前可运行应用;校验失败应该拒绝候选;用户正在编辑时应允许推迟重启。不要先删除当前版本再下载新版本,也不要把下载完成直接显示成“已升级”。UI 应明确当前运行版本与待安装版本。

本地交付同样需要渠道意识。一个临时验证包可以放在独立目录与独立用户数据范围中,避免它打开正式数据后执行不可逆迁移。产品名称、应用标识和数据路径是否隔离应明确设计,不能只把 ZIP 文件名加上 beta 就认为两套应用互不影响。测试者需要知道自己打开的是哪一份可执行文件。

练习:只选择候选,不下载或安装#

保存 candidate.cjs 与 candidate-check.cjs,执行 node candidate-check.cjs。实现只支持没有预发布后缀的三段数字版本,其他版本明确拒绝;渠道是单独字段。它不是完整 SemVer 库,也不是更新器,返回候选后仍必须交给平台更新系统完成可信校验和安装。

完整参考实现
candidate.cjs
function versionParts(value) {
  if (typeof value !== "string" || !/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value)) {
    throw new Error("UNSUPPORTED_VERSION_FORMAT");
  }
  const parts = value.split(".").map(Number);
  if (parts.some(part => !Number.isSafeInteger(part))) throw new Error("VERSION_TOO_LARGE");
  return parts;
}
function compare(a, b) {
  const left = versionParts(a), right = versionParts(b);
  for (let i = 0; i < 3; i++) if (left[i] !== right[i]) return left[i] < right[i] ? -1 : 1;
  return 0;
}
function selectCandidate(current, releases) {
  versionParts(current.version);
  const allowed = releases.filter(release =>
    release.channel === current.channel && release.platform === current.platform &&
    release.arch === current.arch && compare(release.version, current.version) > 0);
  allowed.sort((a, b) => compare(b.version, a.version));
  return allowed[0] ? { ...allowed[0], nextStep: "verify-with-platform-updater" } : null;
}
module.exports = { selectCandidate };
candidate-check.cjs
const assert = require("node:assert/strict");
const { selectCandidate } = require("./candidate.cjs");
const current = { version: "1.9.0", platform: "win32", arch: "x64", channel: "stable" };
const releases = [
  { ...current, version: "2.0.0", channel: "beta" },
  { ...current, version: "1.10.0" },
  { ...current, version: "3.0.0", platform: "darwin" },
  { ...current, version: "1.8.0" }
];
assert.equal(selectCandidate(current, releases).version, "1.10.0");
assert.equal(selectCandidate({ ...current, version: "1.10.0" }, releases), null);
assert.throws(() => selectCandidate(current, [{ ...current, version: "2.0.0-beta.1" }]),
  /UNSUPPORTED_VERSION_FORMAT/);
console.log("candidate checks passed");

候选选择器不接收 URL,也没有执行文件路径,因此不会把版本判断顺便变成执行任意程序的能力。真实元数据仍需验证来源、结构和签名等平台要求;不要把这里的 plain object 当作已经可信的网络响应。结果中的 nextStep 刻意保留未完成步骤,防止界面把“发现候选”误显示为“更新成功”。

把升级工作拆成可回放的候选实验#

升级前先确认当前版本确实可运行。若基线已经白屏或者数据检查失败,新版本出现同样现象时无法判断是否回归。保存一组小而有代表性的输入:首次启动、已有数据、权限拒绝、取消任务和异常退出后的重新读取。它们与源码修订一起形成可回放基线,而不是依赖开发者记得“上周好像能用”。

创建升级候选后,先只改变必要版本与适配代码。记录锁文件变化,重新安装时使用确定的依赖图;不要在验证过程中反复运行不固定目标的更新命令。候选通过后再决定是否合并或本地交付,失败时保留候选与日志用于分析。回到旧代码并不要求删除所有实验线索。

升级计划也需要定义停止条件。主窗口不能加载、业务数据无法读取、权限校验失效或原生模块崩溃,应阻止交付;个别非关键动画变化可能允许记录后继续。标准应在看到结果前确定,避免为了赶进度临时把失败归为“不重要”。这与评测基线一样,先定义可接受变化,再观察实际结果。

当安全修复与兼容性发生冲突#

新版本拒绝过去允许的导航、权限或模块访问时,首先理解被收紧的边界。直接恢复宽泛访问可能让应用重新运行,却保留了升级试图修复的风险。应找到业务真正需要的能力,通过窄接口、受控来源和正确生命周期实现,而不是将安全开关变成永久兼容层。

对于暂时无法迁移的依赖,记录原因、受影响路径和替代计划。继续使用旧运行时不是自动合理,也不是一句“最新版不稳定”就能解释;需要比较安全支持、业务风险和迁移成本。若问题只在一个未使用功能中,可能有更小的修复范围;若影响核心文件访问,则应优先解决。

版本支持状态会随时间变化,因此教材中的固定版本是可复现实验基线,不是长期承诺“这个版本永远够新”。维护时重新查看官方支持与安全信息,并把更新节奏纳入日常工作。避免等到系统升级或依赖彻底移除旧兼容后,才被迫跨越大量主版本。

失败恢复要覆盖“程序还没进入主界面”#

如果 renderer 崩溃后才提供修复按钮,这个按钮本身可能永远无法显示。关键恢复信息应有主进程日志、命令行诊断或其他受控入口。配置损坏时可以提供导出原文件与恢复默认偏好的选择,但不能默认清空所有用户资料。修复偏好与重置业务数据库必须明确分开。

数据迁移开始前记录源版本和恢复材料,迁移完成后记录新的 schema 与成功标识。突然退出后,下一次启动应能够判断尚未开始、正在迁移、已经提交还是需要人工恢复。仅保存一个“正在升级”布尔值不足以区分这些情况,尤其当实际数据已经提交而标志尚未更新。

这里不要求初学者立即实现复杂更新器,但要能识别哪些保证来自现有平台工具,哪些需要应用自己补齐。安装器可能负责替换二进制,却不知道你的 JSON 配置是否兼容;数据库事务可能保证迁移原子性,却不知道用户是否还有未保存编辑。跨层协作需要明确提交点与责任。

验收平台与架构不能靠名字合并#

win32 是 Node 平台标识,不意味着应用只有三十二位;实际位数与架构要看 arch。macOS 上 x64 与 arm64、Windows 上不同架构、Linux 上系统库组合,都可能影响原生模块与打包。报告“Windows 通过”时最好附具体系统与架构,避免别人把结果扩展到未执行的环境。

显示缩放、系统主题、输入法和多显示器也可能在 Chromium 升级后表现变化。它们不一定适合全部自动化,但应根据产品使用场景选择代表性人工检查。对于以文本编辑为主的工具,输入法组合输入比一个无关动画更值得优先验证;对于托盘工具,退出与后台驻留的生命周期更关键。

自动化结果与人工观察应能对应同一份制品。先跑测试,再重新构建另一份文件交给用户,可能使证据失去关联。用制品摘要、应用版本和构建记录把检查绑定起来,才能说明“通过的是交付的这一份”。这也是为什么运行时快照与文件清单在前两章不是可有可无的装饰。

维护记录应该帮助下一次升级#

记录不需要复述所有命令,但应保存发生变化的合同、解决方式与验证范围。例如某个 preload 模块必须收进单文件产物,或某原生依赖需要目标架构重建,这些结论比“升级已完成”更能指导后续工作。对未使用的变化写明不适用依据,下一次新增功能时也能重新评估。

最后保留一个稳定的升级练习频率。每次小范围更新都练习版本核对、合同比较和分层验收,成本通常比多年后一次性跨越大量变化更可控。目标不是追逐每个新 API,而是让桌面应用持续保持可解释、可恢复和可验证的运行状态。

依赖审核与分平台验收#

依赖审核不只看漏洞数量。要知道包在哪个进程执行、是否带安装脚本、是否包含原生二进制、是否主动访问网络以及维护状态如何。renderer 的 XSS 相关依赖、main 的文件处理依赖和构建阶段运行的脚本,拥有不同权限。npm audit 提供已知漏洞线索,但报告为空不能证明依赖可信,报告有项也需要结合可达路径与修复方案判断。

升级前保留可运行基线、固定输入和预期结果。升级后先跑纯 Node 合同与数据检查,再构建 renderer,再运行真实 Electron,再检验打包产物。Windows、macOS 和 Linux 的菜单、权限、文件路径、签名与原生模块各有差异;一台 Windows 机器通过不能代替其他平台验收。明确列出支持范围,比笼统写“跨平台”可靠。

对于崩溃或启动失败,先比较实际运行快照与制品,而不是只看源码 package.json。用户可能仍在运行旧快捷方式指向的安装目录,或者测试包使用了不同数据路径。恢复后记录失败发生在下载、安装、启动、迁移还是业务使用阶段,才能决定下一次改进应该落在更新器、构建还是应用本身。

验收应提交一份含基线、目标、命中变化、执行检查和未验证平台的升级记录。本文实际运行比较器、兼容策略与候选选择器;没有升级已安装应用、执行在线更新、打开 GUI 或做跨平台安装。自测一:升级系统 Node 会更新 Electron 内置 Node 吗?不会。自测二:新版本能读旧数据就能安全降级吗?不能推出。自测三:候选版本最大就应安装吗?还需渠道、平台、可信校验与用户任务状态等条件。

本章验证记录#

Node 22.22.0 已运行 compare-runtime-check.cjs、compatibility-check.cjs 与 candidate-check.cjs。Electron 运行快照入口只做语法检查;文中的真实 Electron 44.3.0 内嵌版本引用本轮主线程另行读取的结果,不代表这里启动了 GUI 或做了升级安装。没有访问更新服务或修改已安装应用。

原有课程整理于 2026-09-10;Node / Electron 扩充于 2026-09-11。示例环境与验证范围以正文为准。
原创中文学习手册,阅读结构参考 Vue 文档;非 Vue 官方教材。
下载本章 Markdown

支持中文和英文全文搜索 · ↑ ↓ 选择 · Enter 打开 · Esc 关闭