要基于 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:// 协议加载文件。有两个设置很重要:
- 相对资源路径。 使用 Vite 时,设置
base: './'以便构建后的index.html引用./assets/...而不是/assets/.... - 路由。 类似
/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 对比.