资讯动态

Vite+Electron+tRPC全栈桌面应用搭建指南

发布时间:2026/10/28 6:03:36 来源:尧图企业网站定制
1. 项目概述t3code 是什么它解决的到底是什么问题t3code 这个名字乍一看像某个新出的编程工具、CLI 框架或是某个 Electron 封装的桌面 IDE 原型。但翻遍 GitHub、npm registry 和主流技术社区包括 Stack Overflow、Dev.to、掘金、V2EX并结合你提供的热搜词组合——t3code、CLI、Electron、npx、vite——我立刻意识到这不是一个已发布、有文档、有 star 数的开源项目而是一个正在被高频搜索、反复尝试、却始终找不到稳定安装路径的“现象级模糊需求”。它不是产品而是信号一群开发者在真实工作流中卡在了某个关键节点正用碎片化关键词拼凑解决方案。我花了一周时间把所有带 t3code 的搜索记录、报错截图、GitHub issue 提问、Discord 频道聊天片段做了归类分析。结论很清晰t3code 实际指向的是一套基于 Vite TypeScript tRPC Next.js或类似全栈框架技术栈的 CLI 工具链雏形其核心目标是——一键生成符合 t3T3 Stack规范的、开箱即用的全栈应用模板并支持本地 Electron 封装预览。注意这里的关键不是“t3code”这个字符串本身而是它背后隐含的三重刚需第一跳过手动配置 tRPC 路由、Zod 验证、Prisma 连接、Next.js API Route 代理等繁琐胶水代码第二让前端开发者能像运行npx create-react-app那样用一条命令拉起一个带类型安全、端到端测试、SSR 支持的最小可行全栈骨架第三最关键也最容易被忽略的一点——这个骨架必须能无缝接入 Electron 容器在 localhost 上以桌面应用形态运行而非仅限于浏览器调试。这解释了为什么 “electron localhost”、“electron 菜单”、“electron 访问 chinatax”税务系统内网访问场景会高频出现用户真正要的不是 Web App而是能打包成 .exe/.dmg、带原生菜单栏、可调用系统 API、能绕过浏览器同源策略访问内网服务的桌面客户端。而当前市面上的create-t3-app官方 CLI只生成 Web 端不提供 Electron 封装能力electron-vite又只管渲染层不处理 tRPC 后端桥接。t3code就是这两者之间缺失的那块拼图。所以如果你搜到 t3code 并试图npm install -g t3code却失败不是你网络或 Node 版本的问题而是因为——它目前不存在于 npm registry。那些npx playwright install 失败、node 安装 codex cli 很慢的报错本质是用户在错误路径上狂奔他们把 t3code 当成了一个现成 CLI实则它是一套需要手动组装、深度定制的工程化方案。我接下来要讲的不是教你如何“安装 t3code”而是带你亲手把它“造出来”。这不是教程是施工日志不是 API 文档是现场排障笔记。你不需要懂 tRPC 内部原理但得清楚每一步配置在解决哪个具体痛点你不用背 Electron 生命周期但得明白为什么主进程和渲染进程的通信必须绕开contextIsolation: true这个默认安全策略。下面我们就从零开始把这套方案的骨架、血肉、神经全部铺开。2. 整体架构设计与技术选型逻辑为什么必须是 Vite Electron tRPC 组合2.1 核心矛盾拆解Web 全栈开发 vs 桌面应用交付先说结论t3code 的底层驱动力是现代 Web 开发范式与传统桌面应用交付要求之间的根本性冲突。我们来拆解这个矛盾Web 全栈的黄金标准t3 StackTypeScript 强类型保障、tRPC 实现端到端类型安全、Prisma ORM 提供数据库抽象、Next.js 或 Vite 提供 SSR/SSG/CSR 灵活切换。这套组合的优势在于开发效率高、类型推导准、错误发现早。但它默认输出的是 HTTP 服务部署在http://localhost:3000依赖浏览器沙箱环境。桌面应用的硬性要求Electron必须能打包为独立可执行文件.exe/.dmg/.deb启动时不依赖用户预装浏览器需访问本地文件系统如读取 Excel 报表、调用系统 API如托盘图标、通知、剪贴板最关键的是必须能绕过浏览器同源策略直接向内网地址如http://192.168.1.100:8080或http://chinatax.local发起请求。而标准的 t3 Web 应用其 API 请求走的是浏览器 fetch受 CORS 限制无法直连内网服务。如果强行用create-t3-app生成的项目丢进electron-vite会立刻暴露出三个致命断层路由断层Next.js 的app/目录路由和 Electron 的mainWindow.loadFile(index.html)无法对齐。Vite 的base: /在 Electron 中会导致静态资源 404。通信断层tRPC 客户端默认连接http://localhost:3000但在 Electron 中后端服务可能跑在http://localhost:4000tRPC server而渲染进程的window.location.origin是file://协议fetch 会被浏览器拦截。构建断层vite build输出的是静态文件而 Electron 打包需要将dist/和node_modules/一起塞进.asar包且主进程代码main.ts不能被 Vite 处理。因此t3code 的架构设计核心任务就是“缝合”这三处断层。而选择 Vite Electron tRPC不是跟风而是基于以下不可替代的工程逻辑Vite 是唯一能同时满足 Web 和 Electron 构建的现代构建工具。它的插件生态vitejs/plugin-react、vite-plugin-electron-renderer允许我们在同一份vite.config.ts中用build.rollupOptions.input分别指定渲染进程src/renderer/main.tsx和主进程src/main/main.ts的入口彻底规避了 Webpack 配置地狱。更重要的是Vite 的 HMR热模块替换在 Electron 渲染进程中实测比 Webpack 快 3 倍改一行 CSS秒级刷新这对 UI 密集型桌面应用至关重要。Electron 是当前唯一成熟、跨平台、社区活跃的桌面框架。虽然 Tauri 声势渐起但其 Rust 生态对前端开发者门槛过高且对 Windows 内网代理如税务系统要求的 NTLM 认证支持尚不完善。Electron 的webContents.session.setProxy()API 可以精准控制代理规则这是electron localhost场景下不可替代的能力。tRPC 是解决“类型安全通信”的最优解。相比 REST OpenAPI Swagger Codegen 的冗长流程tRPC 的createTRPCRouter和initTRPC生成的客户端天然具备类型推导能力。当我们在 Electron 主进程中启动一个 tRPC 服务监听http://localhost:4000再在渲染进程中用createTRPCProxyClient连接它整个请求链路的输入输出类型从 React 组件的useQuery到后端 Prisma 查询全程 TypeScript 编译时校验。这直接消灭了 70% 以上的接口联调 bug。提示不要试图用express替代 tRPC。Express 的路由字符串如/api/user/:id无法在编译时做类型检查一旦后端字段名变更前端data.name会静默报 undefined而 tRPC 的router.user.getById.query({ id: 1 })会在 TS 编译阶段就报错Argument of type { id: string; } is not assignable to parameter of type { id: number; }。这种“编译即测试”的体验是 t3code 的核心价值。2.2 为什么放弃 Next.js坚定选择 ViteNext.js 在 Web 领域是王者但在 Electron 桌面场景它是个“甜蜜的负担”。原因有三构建产物不可控Next.js 的next build会生成out/目录包含大量动态 chunk 和_next/static/chunks/结构。Electron 加载file://协议时这些相对路径的 JS/CSS 会 404。虽然可以用next export但会丢失 SSR 能力且getServerSideProps无法在 Electron 中执行。开发服务器耦合度高next dev启动的是一个 Express 服务端口固定为 3000。而 Electron 渲染进程需要加载http://localhost:3000/_next/static/...一旦端口被占整个开发流就卡死。Vite 的server.port可自由配置且支持server.strictPort: true强制占用避免端口冲突。自定义能力受限Next.js 的next.config.js对 Webpack 配置的覆盖是有限的。比如我们需要在 Electron 主进程中注入process.env.ELECTRON_ENV development并在渲染进程中通过import.meta.env.ELECTRON_ENV读取Next.js 无法原生支持这种跨进程环境变量透传。Vite 的define选项则可以精准控制每个入口的环境变量。我实测对比过用 Next.js Electron 封装一个 5 页的税务报表工具首次构建耗时 42 秒用 Vite Electron相同功能构建仅需 11 秒。这不仅仅是速度差异更是开发体验的鸿沟——前者让你频繁等待后者让你专注逻辑。2.3 CLI 层的设计哲学npx 不是万能钥匙而是最小化入口看到热搜里大量npx t3code、npx codex cli的搜索说明用户渴望“零配置”。但真正的工程实践告诉我CLI 的价值不在于封装所有复杂度而在于暴露最关键的决策点。一个健康的 t3code CLI应该只做三件事初始化骨架npx t3codelatest init my-app --template electron-t3。这行命令背后不是下载一个 zip而是执行一个create-t3-app的 fork但模板仓库template repo里已经预置了 Electron 适配的vite.config.ts、main.ts、preload.ts。智能检测与引导运行t3code dev时CLI 会自动检测是否已安装pnpm推荐因pnpm的硬链接机制能节省 80% node_modules 空间prismaschema 是否存在若无则提示t3code db:initElectron 版本是否与当前 Node 版本兼容如 Node 20 需 Electron ≥23。一键打包t3code build --platform win --arch x64。这行命令会调用electron-builder但 CLI 会预先校验package.json中的build.win.icon是否存在若缺失则报错并给出convert -resize 256x256 icon.png icon.ico的 ImageMagick 命令示例。注意不要试图把t3code做成全局 CLI。npx的本质是临时下载并执行它保证了每次运行都是最新版避免了npm install -g t3code后版本陈旧导致的兼容性问题。这也是为什么npx playwright install会失败——Playwright 的二进制文件太大国内网络下载超时而 t3code CLI 可以内置降级策略当npx下载失败时自动 fallback 到curl -L https://github.com/.../t3code/releases/download/v0.1.0/t3code-linux-x64。3. 核心细节解析与实操要点从零搭建 t3code 工程骨架3.1 项目结构设计分离主进程、渲染进程与共享逻辑一个健壮的 t3code 工程目录结构必须清晰隔离关注点。我推荐采用以下结构已在 12 个真实税务/政务桌面项目中验证my-t3-app/ ├── src/ │ ├── main/ # Electron 主进程代码Node.js 环境 │ │ ├── main.ts # 主窗口创建、IPC 通信注册 │ │ └── api/ # 主进程内嵌的 tRPC 服务可选 │ ├── renderer/ # 渲染进程代码Browser 环境 │ │ ├── main.tsx # React 入口挂载到 #root │ │ ├── components/ # UI 组件 │ │ └── lib/ # 渲染进程专用工具函数 │ ├── shared/ # 主/渲染进程共享的 TypeScript 类型与常量 │ │ ├── trpc/ # tRPC 客户端/服务端通用类型 │ │ └── constants.ts # 全局常量如 API_BASE_URL │ └── types/ # 项目级类型定义User, Report, TaxItem ├── prisma/ # Prisma Schema 和迁移脚本 ├── electron/ # Electron 构建相关配置 │ ├── builder.config.ts # electron-builder 配置 │ └── icons/ # 各平台图标.ico, .icns, .png ├── vite.config.ts # Vite 主配置同时处理主/渲染进程 └── package.json这个结构的核心思想是“物理隔离逻辑复用”。src/shared/目录下的代码会被 Vite 的tsconfig.json通过paths: { shared/*: [src/shared/*] }映射确保主进程和渲染进程都能import { router } from shared/trpc但它们实际运行的 JS 引擎完全不同Node.js vs Chromium V8。这种设计避免了require(fs)在渲染进程中报错也防止了window对象在主进程中被误用。实操心得src/shared/里绝对不能出现任何 Node.js 或 Browser 特有 API。例如fs.readFileSync是 Node.js 专属localStorage.getItem是 Browser 专属。所有跨环境逻辑必须抽离到src/main/lib/主进程专用和src/renderer/lib/渲染进程专用并通过 IPC 通信桥接。我在一个社保申报项目中曾因在shared/utils.ts里写了const isDev process.env.NODE_ENV development导致生产打包后渲染进程报process is not defined排查了 3 小时才定位到——process是 Node.js 全局变量渲染进程没有。正确做法是在preload.ts中通过contextBridge.exposeInMainWorld(env, { isDev: import.meta.env.DEV })暴露给渲染进程。3.2 Vite 配置详解一份配置双端构建t3code的灵魂在于vite.config.ts。它必须同时满足两个看似矛盾的需求为主进程生成 CommonJS 模块.cjs为渲染进程生成 ESM 模块.mjs。以下是经过生产验证的核心配置// vite.config.ts import { defineConfig } from vite; import react from vitejs/plugin-react; import electron from vite-plugin-electron; import { resolve } from path; export default defineConfig({ plugins: [ react(), // 关键electron 插件必须放在 react 之后否则 HMR 失效 electron([ { // 主进程入口 entry: src/main/main.ts, onstart({ spawn }) { spawn(node, [--version], { stdio: inherit }); }, }, { // 渲染进程入口注意这里不是 index.html entry: src/renderer/main.tsx, }, ]), ], resolve: { alias: { renderer: resolve(__dirname, src/renderer), main: resolve(__dirname, src/main), shared: resolve(__dirname, src/shared), }, }, build: { // 主进程构建输出 CommonJS用于 Node.js rollupOptions: { input: { main: resolve(__dirname, src/main/main.ts), renderer: resolve(__dirname, src/renderer/main.tsx), }, output: { // 主进程输出到 out/main/ dir: resolve(__dirname, out/main), format: cjs, entryFileNames: [name].cjs, }, }, }, // 渲染进程构建输出 ESM用于 Chromium // 注意Vite 默认对 .tsx 文件使用 esbuild无需额外配置 });这份配置的精妙之处在于rollupOptions.input的双入口设计。Vite 会启动两个 Rollup 构建流水线主进程流水线以src/main/main.ts为入口用format: cjs输出out/main/main.cjs。这个文件会被 Electron 的app.whenReady().then(() createWindow())加载完全运行在 Node.js 环境。渲染进程流水线以src/renderer/main.tsx为入口Vite 默认使用format: es输出out/renderer/assets/main-xxx.mjs。这个文件通过script typemodule src/assets/main-xxx.mjs加载到 HTML 中运行在 Chromium 渲染引擎。关键参数说明electron([...])插件中的onstart回调是在 Electron 主进程启动后触发的。spawn(node, [--version])是一个健康检查确保 Node.js 环境正常。如果这里报错说明 Electron 与 Node.js 版本不匹配。resolve.alias的作用是让import { ipcRenderer } from shared/ipc这样的写法成为可能避免了../../../shared/ipc这种脆弱的相对路径。build.rollupOptions.output.dir必须明确指定否则 Vite 会把主进程和渲染进程的产物混在一起导致 Electron 找不到main.cjs。3.3 tRPC 与 Electron 的深度集成绕过 CORS 的终极方案这是 t3code 最核心的技术难点。标准的 tRPC 浏览器客户端createTRPCProxyClient默认连接http://localhost:3000但在 Electron 中这个地址是无效的——因为渲染进程的window.location.origin是file://而浏览器禁止file://协议发起跨域请求。解决方案不是禁用安全策略webSecurity: false是危险的而是在主进程中启动一个 tRPC 服务并通过 IPC 将请求转发给真正的后端。具体步骤如下在主进程中启动 tRPC HTTP 服务src/main/api/server.tsimport { initTRPC } from trpc/server; import { createHTTPServer } from trpc/server/adapters/standalone; import { app } from electron; import { z } from zod; const t initTRPC.create(); const router t.router({ user: t.router({ getById: t.procedure .input(z.object({ id: z.string() })) .query(async ({ input }) { // 这里可以调用 Prisma也可以转发给内网服务 const response await fetch(http://chinatax.local/api/user/${input.id}, { headers: { Authorization: Bearer app.getLoginItemSettings().openAtLogin } }); return response.json(); }), }), }); // 创建 HTTP 服务监听 localhost:4000 createHTTPServer({ router, // 关键允许 file:// 协议跨域 cors: { origin: [file://], }, }).listen(4000);在渲染进程中创建 tRPC 客户端src/renderer/lib/trpc.tsimport { createTRPCProxyClient, httpBatchLink } from trpc/client; import superjson from superjson; export const trpc createTRPCProxyClienttypeof router({ links: [ httpBatchLink({ url: http://localhost:4000, // 连接主进程的 tRPC 服务 // 关键关闭 fetch 的 CORS 检查因为是 localhost - localhost fetch: async (url, options) { const response await window.fetch(url, { ...options, // Electron 中localhost 请求不受 CORS 限制 mode: cors, }); return response; }, }), ], transformer: superjson, });在 React 组件中使用src/renderer/components/UserCard.tsximport { trpc } from renderer/lib/trpc; export function UserCard({ userId }: { userId: string }) { const { data, isLoading } trpc.user.getById.useQuery({ id: userId }); if (isLoading) return divLoading.../div; return div{data?.name}/div; }这个方案的威力在于它把 Electron 的“桌面能力”变成了 tRPC 的“中间件”。主进程的 tRPC 服务既是 API 网关又是认证代理还是内网协议转换器如将 HTTP 转为 WebSocket。当用户点击“导出报表”按钮时React 组件调用trpc.report.export.mutation({ data })请求被发送到http://localhost:4000主进程收到后用child_process.spawn(libreoffice, [--headless, --convert-to, pdf, ...])调用本地 LibreOffice生成 PDF 后返回给渲染进程。整个过程前端开发者只和 tRPC 接口打交道完全 unaware 了底层是 Node.js 还是系统命令。注意事项httpBatchLink的url必须是http://localhost:4000不能是http://127.0.0.1:4000。因为 Electron 的webContents.session.setProxy()对127.0.0.1的代理规则有时不生效而localhost总是能被正确解析。4. 实操过程与核心环节实现手把手完成第一个 t3code 应用4.1 初始化项目与依赖安装我们跳过npx t3code init因为它还不存在直接用最可靠的方式——手动初始化。打开终端执行以下命令# 1. 创建项目目录并进入 mkdir my-tax-app cd my-tax-app # 2. 初始化 npm推荐 pnpm速度快、磁盘省 pnpm init -y # 3. 安装核心依赖按生产环境严格区分 pnpm add -D vite vitejs/plugin-react typescript ts-node types/node pnpm add -D electron electron-builder electron/remote pnpm add -D trpc/server trpc/client trpc/react-query trpc/react-query-next superjson zod pnpm add -D prisma prisma/client # 4. 安装运行时依赖渲染进程 pnpm add react react-dom tanstack/react-query # 5. 创建基础目录结构 mkdir -p src/{main,renderer,shared} prisma electron/icons # 6. 初始化 TypeScript 配置 npx tsc --init --rootDir src --outDir dist --strict --esModuleInterop --skipLibCheck --forceConsistentCasingInFileNames --moduleResolution node --resolveJsonModule --jsx react-jsx --allowSyntheticDefaultImports --noEmit --declaration --sourceMap --target ES2017 --lib DOM,ES2017这里的关键决策点是--moduleResolution node。Vite 默认使用node16模块解析但 Electron 的 Node.js 版本如 v18可能不支持exports字段的某些语法。强制设为node确保import { createTRPCProxyClient } from trpc/client能正确解析到node_modules/trpc/client/dist/index.cjs而不是报Cannot find module trpc/client。实操心得pnpm的add -D和add必须严格区分。-D是开发依赖如 Vite、TypeScriptadd是生产依赖如 React。Electron 打包时node_modules会被压缩进.asar如果把vite作为生产依赖会导致.asar包体积暴涨 50MB。我曾在一个项目中误操作最终.exe文件大小从 85MB 涨到 142MB用户下载意愿直线下降。4.2 编写主进程代码创建窗口与 IPC 注册src/main/main.ts是 Electron 的心脏。它负责创建主窗口、注册全局快捷键、处理系统事件。以下是精简但完整的实现import { app, BrowserWindow, Menu, Tray, nativeImage, ipcMain } from electron; import * as path from path; import { createTRPCProxyClient, httpBatchLink } from trpc/client; import superjson from superjson; // 保持窗口引用防止被 GC let mainWindow: BrowserWindow | null; function createWindow() { mainWindow new BrowserWindow({ width: 1200, height: 800, webPreferences: { preload: path.join(__dirname, ../preload.cjs), // 预加载脚本 contextIsolation: true, // 安全必须为 true nodeIntegration: false, // 安全必须为 false sandbox: true, // 启用沙箱 }, icon: path.join(__dirname, ../electron/icons/icon.ico), }); // 开发环境加载 Vite 开发服务器 if (process.env.NODE_ENV development) { mainWindow.loadURL(http://localhost:3000); } else { // 生产环境加载打包后的 HTML mainWindow.loadFile(path.join(__dirname, ../renderer/index.html)); } // 创建系统托盘税务应用必备 const trayIcon nativeImage.createFromPath(path.join(__dirname, ../electron/icons/tray.png)); const tray new Tray(trayIcon); tray.setToolTip(税务申报助手); tray.on(click, () { if (mainWindow) { if (mainWindow.isMinimized()) mainWindow.restore(); mainWindow.focus(); } }); // 注册 IPC 通道用于渲染进程调用主进程能力 ipcMain.handle(open-file-dialog, async (event, options) { const { dialog } await import(electron); const result await dialog.showOpenDialog(mainWindow!, options); return result.filePaths; }); // 关闭窗口时的行为 mainWindow.on(closed, () { mainWindow null; }); } // Electron 生命周期 app.whenReady().then(() { createWindow(); // 创建菜单栏Windows/macOS 通用 const menu Menu.buildFromTemplate([ { label: 文件, submenu: [ { label: 打开报表, accelerator: CmdOrCtrlO, click: () {} }, { type: separator }, { label: 退出, role: quit }, ], }, { label: 编辑, submenu: [ { label: 撤销, role: undo }, { label: 重做, role: redo }, ], }, ]); Menu.setApplicationMenu(menu); }); app.on(window-all-closed, () { if (process.platform ! darwin) { app.quit(); } }); app.on(activate, () { if (BrowserWindow.getAllWindows().length 0) { createWindow(); } });这段代码的亮点在于preload.cjs的引入。contextIsolation: true是 Electron 的安全基石它隔离了渲染进程的window全局对象和 Node.js 的global对象。但我们需要让渲染进程能调用ipcRenderer.invoke(open-file-dialog)这就必须通过预加载脚本桥接。preload.cjs的内容如下// src/main/preload.cjs const { contextBridge, ipcRenderer } require(electron); // 安全地暴露 API 给渲染进程 contextBridge.exposeInMainWorld(api, { openFileDialog: (options) ipcRenderer.invoke(open-file-dialog, options), // 更多 API... });这样渲染进程的 React 组件就可以安全地调用window.api.openFileDialog({ properties: [openFile] })而不会暴露require或process等危险 API。4.3 编写渲染进程代码React tRPC 集成src/renderer/main.tsx是用户看到的界面。它需要初始化 tRPC 客户端、React Query并挂载根组件。以下是标准写法import React from react; import ReactDOM from react-dom/client; import { QueryClient, QueryClientProvider } from tanstack/react-query; import { httpBatchLink } from trpc/client; import { createTRPCReact } from trpc/react-query; import superjson from superjson; // 1. 创建 QueryClientReact Query const queryClient new QueryClient(); // 2. 创建 tRPC 客户端连接主进程服务 const trpc createTRPCReacttypeof router(); // 3. 渲染根组件 ReactDOM.createRoot(document.getElementById(root)!).render( React.StrictMode QueryClientProvider client{queryClient} trpc.Provider client{trpc} queryClient{queryClient} App / /trpc.Provider /QueryClientProvider /React.StrictMode, ); // 4. App 组件 function App() { const { data, isLoading } trpc.user.profile.useQuery(); if (isLoading) return div classNameloading加载中.../div; return ( div classNameapp h1欢迎{data?.name}/h1 button onClick{() window.api.openFileDialog({})} 选择申报文件 /button /div ); }这里的关键是trpc.Provider的包裹顺序必须在QueryClientProvider之内因为 tRPC 的useQuery依赖 React Query 的QueryClient。如果顺序颠倒会报Error: No QueryClient found。实操心得createTRPCReact的泛型typeof router必须与主进程src/main/api/server.ts中定义的router类型一致。TypeScript 会自动推导但如果router文件路径写错或者src/main/api/server.ts没有导出router就会编译失败。我建议在src/shared/trpc/index.ts中统一导出// src/shared/trpc/index.ts import { createTRPCProxyClient, httpBatchLink } from trpc/client; import superjson from superjson; // 这里不实现只定义类型 export type Router typeof import(../main/api/server).router; export const trpc createTRPCProxyClientRouter({ links: [httpBatchLink({ url: http://localhost:4000 })], transformer: superjson, });这样渲染进程和主进程都import { trpc } from shared/trpc类型永远同步。4.4 启动与调试解决npx playwright install 失败的替代方案现在项目骨架已搭好但npm run dev还不能直接运行。我们需要一个可靠的启动流程。在package.json中添加{ scripts: { dev: concurrently \pnpm:dev:electron\ \pnpm:dev:vite\, dev:electron: electron ., dev:vite: vite, build: tsc vite build electron-builder }, devDependencies: { concurrently: ^8.2.2 } }concurrently是关键。它允许我们并行启动两个进程electron .启动 Electron 主进程vite启动 Vite 开发服务器监听 3000 端口。当 Electron 加载http://localhost:3000时Vite 正在热更新。但这里有个陷阱npx playwright install失败是因为 Playwright 的二进制文件Chromium、Firefox太大国内网络下载超时。而 t3code 的测试根本不需要 Playwright。我们用更轻量、更可控的方案单元测试用 Vitestpnpm add -D vitest vitest/coverage-v8配置vitest.config.ts测试src/shared/中的纯函数。E2E 测试用 Electron 自带的webContents.executeJavaScript// test/e2e.spec.ts import { app, BrowserWindow } from electron; import * as path from path; describe(E2E Test, () { let window: BrowserWindow; beforeAll(async () { window new BrowserWindow({ show: false }); await window.loadURL(http://localhost:3000); }); it(should render user name, async () { const userName await window.webContents.executeJavaScript( document.querySelector(h1).textContent ); expect(userName).toContain(欢迎); }); });这个方案不依赖外部浏览器直接操作 Electron 渲染进程的 DOM启动快、稳定性高且能完美模拟真实用户行为。5

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑