背景痛点:网页版在 Windows 上的“水土不服”
很多开发者第一次用 ChatGPT 网页版时,都会遇到“三高一低”的尴尬:
- 高网络依赖:每次刷新都要重新拉取 3 MB 以上的 JS 资源包,弱网环境直接白屏。
- 高内存占用:Chrome 单标签就能吃掉 400 MB,再开几个插件直接飙到 1 GB。
- 高权限要求:公司电脑没管理员权限,无法安装浏览器扩展,体验打折。
- 低系统集成:Win + Shift + S 截图后无法直接粘贴到对话框,窗口大小也记不住。
于是“能不能把网页版包成一个真正的 .exe?”成了群里天天有人问的刚需。
技术选型:PWA vs Electron vs Tauri 谁更适合你
把网页“桌面化”主流方案有三条路线,我踩完坑后画了一张打分表:
| 维度 | PWA | Electron | Tauri |
|---|---|---|---|
| 安装包体积 | 最小(≈200 KB) | 大(≈100 MB) | 中(≈15 MB) |
| 内存占用 | 与 Edge 共享 | 独立进程 300 MB 起 | 系统 WebView 100 MB 左右 |
| Node API | (Rust 侧) | ||
| 代码复用 | 100 % | 100 % | 100 % |
| 签名/更新链 | 商店托管 | Squirrel.Windows 自托管 | 内置 updater |
| 企业网络白名单 | 常被拦截 | 易放行 | 易放行 |
结论:
- 想“零成本”尝鲜,PWA 足够;
- 要离线、要加密、要自定义标题栏,Electron 最省事;
- 想极致瘦身、愿意写 Rust,Tauri 是未来。
下文以 Electron 为例,给出可直接复制的工程级源码。
核心实现:Electron Builder 打包与权限通信
1. 打包配置(electron-builder.yml)
appId: com.example.chatgpt productName: ChatGPT Desktop directories: output: dist buildResources: resources asar: true asarUnpack: - 'node_modules/sqlite3/lib/binding/**/*' win: target: nsis icon: assets/icon.ico requestedExecutionLevel: requireAdministrator # 需管理员时自动提权 nsis: oneClick: false allowToChangeInstallationDirectory: true differentialPackage: true # 差分更新必备 publish: provider: generic url: https://your-cdn.com/releases关键点:
asarUnpack把原生模块拆出来,避免 Windows Defender 把 ASAR 当压缩炸弹。requestedExecutionLevel只在需要管理员时提权,而不是每次启动都 UAC,用户体验好很多。
2. 主进程 / 渲染进程 IPC 权限检测(TypeScript)
渲染进程侧:
// preload.ts const { contextBridge ipcRenderer } = require('electron'); export const checkAdmin = (): Promise<boolean> => ipcRenderer.invoke('is-user-admin'); // 在 React 组件里 const [isAdmin, setIsAdmin] = useState(false); useEffect(() => { checkAdmin().then(setIsAdmin); }, []);主进程侧:
// main.ts import { app, ipcMain } from 'electron'; import * as child from 'child_process'; ipcMain.handle('is-user-admin', async () => { try { // 利用 Windows net session 指令检测 await child.execSync('net session', { stdio: 'ignore' }); return true; } catch { return false; } });避坑指南:Windows 专属“惊喜”
Windows Defender 误报
把 electron-builder 生成的.exe上传到 Microsoft Partner Center 做免费签名扫描,拿到干净报告后,在 NSIS 脚本里加:ExecWait 'reg add "HKLM\Software\Microsoft\Windows Defender\Exclusions\Paths" /v "$INSTDIR" /t REG_DWORD /d 0 /f'可让用户一键加白,避免刚装完就被隔离。
离线环境 Chromium 依赖
首次启动 Electron 会尝试拉取 Widevine 与拼写字典,失败就白屏。
解决:在package.json里锁版本,把node_modules\electron\dist整个拷进内网,再用ELECTRON_CACHE环境变量指向本地目录。多显示器窗口“漂移”
拔掉副屏后,窗口可能落在不可见区域。主进程启动时校正:const { screen } = require('electron'); const bounds = store.get('winBounds', { width: 1200, height: 800 }); const display = screen.getDisplayMatching(bounds); if (!display) { bounds.x = 0; bounds.y = 0; } mainWindow.setBounds(bounds);
安全考量:本地存储与自动更新
本地缓存加密
网页版把对话历史丢 Indexed 数据库,桌面版得加密落盘:import { createCipheriv, createDecipheriv, randomBytes } from 'crypto'; const ALG = 'aes-256-gcm'; const key = Buffer.from(process.env.ENC_KEY!); // 32 字节,启动时注入 export const encrypt = (plain: string): Buffer => { const iv = randomBytes(12); const cipher = createCipher(ALG, key, iv); const enc = Buffer.concat([cipher.update(plain, 'utf8'), cipher.final()]); const tag = cipher.getAuthTag(); return Buffer.concat([iv, tag, enc]); };把返回的 Buffer 写进
app.getPath('userData')/chat.db,即便电脑被拷硬盘,没有环境变量也解不开。自动更新签名验证
Squirrel.Windows 会拉RELEASES文件与 nupkg,但默认不验签。
在main.ts里加钩子:autoUpdater.on('update-downloaded', (info) => { const cert = info.signatureCertificate; if (!cert || cert.subject.indexOf('O=Example Corp') === -1) { console.error('签名不匹配,放弃安装'); return; } autoUpdater.quitAndInstall(); });这样即便 CDN 被劫持,没有公司证书也无法推送恶意包。
性能优化:让“套壳浏览器”不再臃肿
V8 快照快照(Snapshot)
把启动时就要解析的 3 MB JS 提前拍成快照,主进程启动从 900 ms 降到 400 ms:
在webpack.config.js里加:const SnapshotPlugin = require('electron-snapshot-plugin'); plugins: [new SnapshotPlugin({ entry: './dist/snapshot.js', output: 'snapshot.bin' })]主进程加载:
require('v8').startupSnapshot = require('fs').readFileSync('snapshot.bin');内存泄漏巡检
渲染进程每 30 s 采样:setInterval(() => { const { used, total } = performance.memory; if (used / total > 0.9) { console.warn('内存占用超 90 %,强制 GC'); (window as any).gc(); // 启动需加 --js-flags="--expose-gc" } }, 30000);主进程用
process.memoryUsage().rss做同样阈值报警,超了就弹托盘提示用户重启。
小结与思考题
走完上面七步,你就拥有了一个“可离线、可加密、可更新”的 ChatGPT Windows 桌面端,体积压到 80 MB,启动 2 秒内,内存稳定在 300 MB 左右,公司 maiden 电脑也能装。
下一步,不妨思考:
“如何实现跨平台的模型差分更新,让同一套更新逻辑在 Windows、macOS、Linux 上都能按二进制补丁粒度下发,而不用全量拉 4 GB 的大模型?”
如果你也想从 0 到 1 体验把 AI 装进本地,不妨看看这个动手实验——从0打造个人豆包实时通话AI,步骤很细,连申请火山引擎 token 的截图都给了,小白也能跟着跑通。我亲测一下午就搞定,把语音对话搬进自己写的 exe 里,那一刻的成就感,比刷网页版爽多了。