跳转至正文

使用 Capacitor 和 Electron 构建桌面应用 (2026)

使用 Electron 将您的 Capacitor 应用转换为桌面应用:安全 Shell 配置、插件策略、macOS、Windows、Linux 打包以及 Live Update。

Capgo 横幅:用 Electron 从 Capacitor 项目构建桌面应用指南

要基于 Capacitor 应用构建桌面应用,只需把同一个 Web 构建产物(webDir)放进 Electron 外壳中:一个主进程负责打开 BrowserWindow,一个暴露小型桌面 API 的 preload 脚本,以及用于为 macOS、Windows 和 Linux 生成安装程序的 electron-builder。你的 UI 和业务逻辑仍与 iOS 和 Android 共享,桌面专属功能则位于 Electron main process 中。

本指南介绍 2026 年 Electron 上 Capacitor 的现状、兼容 Capacitor 8 的安全方案、插件的工作方式、打包与签名,以及如何在不发布新安装包的情况下发布 Web 更新。

2026 年 Electron 上 Capacitor 的现状

多年来,默认答案一直是 @capacitor-community/electron,它将 Electron 作为平台接入了 Capacitor CLI。该项目现在已在其 README 中标记为不再维护。其最后一个 npm 版本 5.0.1 于 2023 年 9 月发布,要求 Capacitor 5.4 或更高版本,并且是基于 Electron 26 构建的。此后 Electron 已发布多个大版本,而只有最新的三个大版本会获得安全修复。

你仍可以在旧项目中使用它,但对于 Capacitor 8 应用,对比情况如下:

方案 优点 缺点
你自己的 Electron shell(本指南) 当前 Electron,完全掌控,无额外抽象层 你大约需要编写 60 行 main 和 preload code
@capacitor-community/electron 熟悉 cap 命令 无人维护,锁定在旧版 Capacitor 和 Electron

Shell 方案比听起来更省事,因为 Capacitor 已经能生成静态 web 构建。Electron 只需加载它即可。

插件在 Electron 中的运行机制

Electron 的渲染器是 Chromium,因此你的 Capacitor 应用在那里运行得和在浏览器中一样。Capacitor 的核心检测不到原生桥接,会回退到各插件的 web 实现。

插件类型 在 Electron 中可用? 处理方式
具有 Web 实现的插件(Preferences、Share、Clipboard 以及许多 Capgo 插件) 是的,使用 Web 版本 测试 Web 行为
仅限 Native 的插件(无 Web 实现) 否,会以未实现为由拒绝 在 preload API 后面编写等效的 Electron 实现
纯 JS 库 是 无

Capacitor.getPlatform() 返回 'web' 在此配置中,以及 Capacitor.isNativePlatform() 返回 false. 我们会从 preload 脚本暴露一个显式的 desktop 标志,让你的 code 能干净地分支。

前提条件

  • 一个 Capacitor 应用,其 bun run build 输出一个静态文件夹(即 webDir 中的 capacitor.config.ts,通常 dist 或 www).
  • Bun 以及当前 LTS 版本的 Node.js,因为 Electron 工具链仍运行在 Node 上。
  • macOS 分发需要 Apple Developer ID 证书。Windows 需要 code 签名证书。

第 1 步:安装 Electron 和 packager

bun add -d electron electron-builder

添加一个 electron/ 项目根目录下的文件夹:

my-app/
  capacitor.config.ts
  dist/              # web build, shared with iOS and Android
  electron/
    main.ts
    preload.ts
  electron-builder.yml

第 2 步:让 Web build 从磁盘加载

Electron 在生产环境中使用 file:// 协议加载文件。有两个设置很重要:

  1. 相对资源路径。 使用 Vite 时,设置 base: './' 以便构建后的 index.html 引用 ./assets/... 而不是 /assets/....
  2. 路由。 类似 /settings 并不以文件形式存在。桌面端构建请使用 hash 路由,或确保路由仅在加载完成后进行客户端导航 index.html.
// vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
  base: './',
});

相对路径在 iOS 和 Android 上同样有效,因此无需单独构建。

步骤 3:编写主进程

// electron/main.ts
import { app, BrowserWindow, ipcMain, shell } from 'electron';
import path from 'node:path';

const isDev = !app.isPackaged;

async function createWindow() {
  const win = new BrowserWindow({
    width: 1280,
    height: 800,
    minWidth: 800,
    minHeight: 600,
    show: false,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false,
      sandbox: true,
    },
  });

  win.once('ready-to-show', () => win.show());

  // Open external links in the default browser, never inside the app.
  // Only allow https: so a script cannot launch file: or other protocol handlers.
  win.webContents.setWindowOpenHandler(({ url }) => {
    try {
      if (new URL(url).protocol === 'https:') void shell.openExternal(url).catch(() => {});
    } catch {
      // invalid URL: ignore
    }
    return { action: 'deny' };
  });

  if (isDev) {
    await win.loadURL('http://localhost:5173');
    win.webContents.openDevTools({ mode: 'detach' });
  } else {
    await win.loadFile(path.join(__dirname, '..', '..', 'dist', 'index.html'));
  }
}

ipcMain.handle('desktop:get-version', () => app.getVersion());

app.whenReady().then(createWindow);

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit();
});

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) createWindow();
});

保留 contextIsolation: true, nodeIntegration: false,以及 sandbox: true。你的 Web 应用可能会加载第三方脚本,它们都不应获得 Node.js 访问权限。

步骤 4:从 preload 暴露一个小型桌面 API

// electron/preload.ts
import { contextBridge, ipcRenderer } from 'electron';

contextBridge.exposeInMainWorld('desktop', {
  isElectron: true,
  platform: process.platform,
  getVersion: () => ipcRenderer.invoke('desktop:get-version') as Promise<string>,
});

在你的 Web code 中编写,并像使用插件一样使用它:

// src/desktop.ts
declare global {
  interface Window {
    desktop?: {
      isElectron: boolean;
      platform: string;
      getVersion: () => Promise<string>;
    };
  }
}

export const isDesktop = () => Boolean(window.desktop?.isElectron);

你在桌面端需要的每个原生功能都对应一个 ipcMain.handle 在主进程中,以及 preload 中的一个函数。请在主进程中验证参数,因为 renderer 是不可信的。

步骤 5:构建并运行

使用 Bun 编译 Electron 文件。preload 在沙箱中运行,因此请将其打包为单个 CommonJS 文件:

{
  "main": "electron/dist/main.js",
  "scripts": {
    "build": "vite build",
    "electron:compile": "bun build electron/main.ts --outdir electron/dist --target node --format cjs --external electron && bun build electron/preload.ts --outdir electron/dist --target node --format cjs --external electron",
    "electron:dev": "bun run electron:compile && electron .",
    "electron:pack": "bun run build && bun run electron:compile && electron-builder"
  }
}

开发时,在一个终端中启动开发服务器(bun run dev) 以及 bun run electron:dev 在另一个终端中运行。桌面窗口里就能热重载。

步骤 6:使用 electron-builder 打包安装程序

# electron-builder.yml
appId: com.example.myapp
productName: My App
directories:
  output: release
files:
  - dist/**/*
  - electron/dist/**/*
  - package.json
mac:
  target: [dmg, zip]
  category: public.app-category.productivity
  hardenedRuntime: true
win:
  target: [nsis]
linux:
  target: [AppImage, deb]
  category: Utility

运行 bun run electron:pack. electron-builder 会为运行它所在的操作系统构建。在 CI 中使用 macOS、Windows 和 Linux runner 矩阵来生成全部三个平台版本。

操作系统 常见目标平台 签名
macOS .dmg, .zip 需要 Developer ID 证书及公证,否则会被 Gatekeeper 拦截
Windows NSIS .exe、MSI Code 签名证书以避免 SmartScreen 警告
Linux AppImage、 .deb, .rpm 可选

Electron 应用比移动端构建更大,因为包含 Chromium 和 Node.js。根据目标平台不同,安装包大约在 80 到 150 MB 之间。

步骤 7:无需新安装程序即可发布 Web 更新

Capacitor 应用中的大多数改动都是 Web 改动。在移动端,你可以通过 Capgo 实时更新来发布这些改动。在桌面端, @capgo/electron-updater 以相同的模式完成同样的工作:通道、内置 bundle、已下载的 bundle,以及自动回滚,如果新 bundle 未调用 notifyAppReady().

bun add @capgo/electron-updater

更新主进程,让更新器选择要加载的 bundle:

// electron/main.ts (production branch)
import { ElectronUpdater, setupIPCHandlers, setupEventForwarding } from '@capgo/electron-updater';

const updater = new ElectronUpdater({
  appId: 'com.example.myapp',
  autoUpdate: true,
});

// inside createWindow(), instead of loading dist/index.html directly
const builtinPath = path.join(__dirname, '..', '..', 'dist', 'index.html');
await updater.initialize(win, builtinPath);
setupIPCHandlers(updater);
setupEventForwarding(updater, win);
await win.loadFile(updater.getCurrentBundlePath());

在同一个 preload 文件中添加更新器桥接:

// electron/preload.ts (next to the desktop API)
import { exposeUpdaterAPI } from '@capgo/electron-updater/preload';

exposeUpdaterAPI();

并确认 Web 应用在桌面端正常启动:

import { isDesktop } from './desktop';

if (isDesktop()) {
  const { requireUpdater } = await import('@capgo/electron-updater/renderer');
  await requireUpdater().notifyAppReady();
}

使用与移动端相同的 CLI 上传新的 bundle:

bun run build
bunx @capgo/cli@latest bundle upload --channel=production

Web 更新无法更改 Electron 本身、主进程或 preload。对于这些,请使用二进制自动更新器发布新的安装程序。我们的指南 Electron 自动更新 涵盖了该路径,而 electron-updater 插件页面 以及 文档 则介绍了所有选项。

故障排除

打包后的应用显示空白窗口。 资源使用了绝对路径。请设置 base: './' 并重新构建。在打包后的应用中打开 DevTools,确认 file:///assets/....

重新加载后路由显示空白页面。 路由器尝试从磁盘加载 /route 。桌面端请切换为 hash 路由。

require is not defined 或 preload 错误。 沙箱化的 preload 无法在运行时加载任意包。请将其打包为单个文件,如上述编译脚本所示。

插件调用被拒绝,提示“not implemented on web”。 该插件没有 Web 实现。请在主进程中添加 IPC 处理器,并通过 window.desktop.

macOS 提示应用已损坏或来自未识别的开发者。 应用未进行签名和公证。请在 electron-builder 中配置 Developer ID 证书和公证。

更新在安装后立即回滚。 notifyAppReady() 未在超时时间内(默认 10 秒)调用。请在应用启动早期调用它。

总结

你无需专门的 Capacitor 平台包即可支持桌面端。一个简短的 Electron 主进程、一个暴露类型化 API 的 preload 脚本、相对资源路径以及 electron-builder,就能从同一个 Web 构建生成所有三大操作系统的安装程序。添加 @capgo/electron-updater 后,桌面应用将获得与 iOS 和 Android 构建相同的 live update 工作流。如果你还在选择桌面运行时,请阅读我们的 Electron 与 Tauri 对比.

为 Capacitor 应用提供 Live Updates

当 Web 层出现线上 Bug 时,通过 Capgo 发布修复,而不用等 App Store 审核好几天。用户会在后台收到更新,原生变更则仍走正常审核流程。

博客最新动态

Capgo 为你提供打造真正专业的移动应用所需的最有价值的洞察。