返回

create-electron-vite 四个 build 坑与统一修复

2026年9月27日 · 技术

姊妹篇:《create-electron-vite 上手:从建项目到 AI 编程》——项目怎么建、AI 怎么接,看那篇;这篇专治 build。

先说结论:create-electron-vite 的四个 build 问题,全部与框架选择无关——React、Vue、Vanilla 三个变体一个都躲不掉,因为它们同源:这个脚手架本质上是「Vite 官方 Web 模板 + 一层很薄的 Electron 补丁」,补丁自己还带病。好消息是,四个问题存在一套对所有变体统一的修复动作,文末有可以直接粘给 AI 的清单。

四个问题一句话预览:

  • TS6133:模板自带的 require 声明没人用,首次 build 必红;
  • electron 下载失败:两套下载时机 + 一套只认平铺文件的缓存互不相通,GitHub 直连不稳就死在打包段;
  • asar 体积虚胖:dependencies 的整棵 node_modules 被塞进安装包,实测 282.8 MB → 34.7 MB;
  • 幽灵文件:dist-electron/ 没人清理,旧构建产物永久残留并进包。

验证方式:React 与 Vue 两个变体分别用这个脚手架新建并完整构建,另有一个 React 系真实项目(electron 35.7.5)做对照;文中每个修复都在真实构建里验证过。环境:Windows 11、Node v24.15.0、create-electron-vite 0.7.1、electron-builder ^24.13.3。

理解 npm run build 实际执行的三段,后面的「本质」才好讲:

tsc(或 vue-tsc)  →  vite build  →  electron-builder
   类型检查            前端打包           桌面安装包打包
                      产出 dist/(渲染层)  引用 dist-electron/(主进程)
                                        + release/ 下的安装包

electron-builder 干的事:把 dist/、dist-electron/、package.json、以及 package.json 里 dependencies 的整个 node_modules 生产树,连同 electron 运行时本体,组装成 app.asar 和安装包。记住「dependencies 会被整个塞进包里」这句,后面要用。

打个比方,发布一个桌面应用就是搬一次家:tsc 是验房师,拿着图纸逐项挑毛病;vite build 是装修队,把毛坯房(源码)装成精装房(dist/),家具直接焊死在墙上;electron-builder 是搬家公司,把整套房连同车钥匙(electron 运行时)一起装进集装箱(安装包)。接下来的四个问题,全出在搬家的路上。

问题 1:新建完第一次 build 就报 TS6133(React 也不幸免)

现象:npm run build 第一步就死,React 变体实测日志:

> tsc && vite build && electron-builder

electron/main.ts(6,7): error TS6133: 'require' is declared but its value is never read.

本质:模板自带的 electron/main.ts 里有一行 const require = createRequire(import.meta.url),从头到尾没被用过;而脚手架会给所有变体的 tsconfig include 补上 "electron"(源码里无条件执行),且模板开着 noUnusedLocals。「未使用 + 强制检查」相撞,三种模板开箱即红。我们用 React 变体验证过:electron/ 目录的类型检查是真实生效的(在 main.ts 里埋一个类型错误,tsc 立刻报 TS2322)——所以别想着把 electron 从 include 里摘掉来绕,那等于放弃主进程的类型保护。

还是搬家的说法:装修队交房时在墙里留了根废线头,而验房师的规矩偏偏是「见到没用的东西就拒收」。房子结构没问题,是遗留物和严格的标准在相撞——所以修法不是放宽标准,而是拔掉那根线头。

修复:删掉 electron/main.ts 顶部这两行(对所有变体都这么做):

- import { createRequire } from 'node:module'
  import { fileURLToPath } from 'node:url'
  ...
- const require = createRequire(import.meta.url)
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
diff

问题 2:build 卡在「下载 electron」,报 ERR_ELECTRON_BUILDER_CANNOT_EXECUTE

现象:前两段都过了,electron-builder 阶段报:

⨯ Get "https://github.com/electron/electron/releases/download/v30.5.1/electron-v30.5.1-win32-x64.zip": ...
⨯ app-builder.exe process failed ERR_ELECTRON_BUILDER_CANNOT_EXECUTE

本质:Electron 二进制有两套下载时机和一套真正的缓存,它们互不相通。

  1. npm install 时,electron 包的安装脚本把运行时解压到 node_modules/electron/dist——这一步很多人以为是「下载好了」;
  2. 打包时,electron-builder 不碰 node_modules/electron/dist,它按 package.json 里的 electron 版本号,去找一个 zip 文件:%LOCALAPPDATA%\electron\Cache\electron-v<版本>-win32-x64.zip(实测 24.13.3 只认这个目录下的平铺同名文件,不递归子目录);
  3. 找不到 → 当场从 GitHub Releases 下载。GitHub 直连不稳(超时 / EOF),build 就死在这里。

继续搬家:装修队已经把一套同款大理石台面搬进了你家(node_modules/electron/dist),但搬家公司不进你家验收,只按型号去仓库提「原厂原包装」的货;而仓库管理员找货只认货架上贴着标准标签的那一层,塞在无名纸箱里的同款他视而不见。「家里明明有」和「仓库提得出」是两本账。

两个最容易踩的认知坑:

  • 「npm install 都成功了,怎么打包还要下载?」——因为两步用的是两份东西。npm install 的下载(新版 @electron/get)会把 zip 存进 %LOCALAPPDATA%\electron\Cache\<sha256 哈希目录>\ 里;而 electron-builder 只认平铺文件名,哈希子目录里的 zip 它看不见。这就是「我明明有缓存还是失败」的原因。
  • 镜像下载完会不会存下来? 会。配了镜像后第一次下载,zip 会落到上面那个平铺位置,之后的 build 离线也能过(实测确认)。

修复(推荐,写进项目配置一劳永逸)——electron-builder.json5:

{
  // GitHub 直连不稳,二进制走 npmmirror
  "electronDownload": {
    "mirror": "https://npmmirror.com/mirrors/electron/"
  },
  "directories": { "output": "release/${version}" },
  // ...
}
json5

离线兜底:手动把 zip 按 electron-v<版本>-win32-x64.zip 的名字平铺放进 %LOCALAPPDATA%\electron\Cache\(比如从哈希子目录里拷出来),下次 build 直接命中。

问题 3:安装包体积离谱——asar 里塞了整个 node_modules

现象:一个几乎没写代码的项目,asar 有几 MB 到十几 MB;真实项目能到几百 MB。

本质:dependencies 的真实含义不是「项目用到的包」,而是「electron-builder 会打进 asar 的包」。而 Vite 打包渲染层时,已经把 React/Vue 和所有前端库编译进了 dist/——node_modules 里那些原件是第二份,纯死重。

dependencies 不是你的购物清单,而是搬家公司的发货清单——装修队明明已经把家具焊死在精装房里了,搬家公司仍按清单把仓库里的原材料原样再发一份,全塞进你的集装箱。

实测(前 = 模板原样,后 = 依赖分区调整后):

项目asar 总体积其中 node_modules
React 模板新项目4.9 MB4.7 MB(react 树 5 个包)
React 模板新项目调整后0.1 MB0(node_modules 整个消失)
Vue 模板新项目14.1 MB14.0 MB(vue 全家桶 12 个包)
Vue 模板新项目调整后0.1 MB0
真实项目(React 系,含图表库)282.8 MB269.2 MB(mermaid 一家 122MB)
真实项目调整后34.7 MB23.6 MB

安装包同步瘦身:139 MB → 91.7 MB。

修复:package.json 里做依赖分区——

  • 留在 dependencies:主进程运行时真正需要的第三方包。判断方法:electron/main.ts(及主进程其他文件)import 的包;如果配置了 vite.config.ts 的 external 清单,就以它为准;
  • 移到 devDependencies:一切渲染进程专用的库(react / react-dom / vue 等框架本体、recharts 等图表库、lucide 等图标库、UI 组件库)。devDependencies 照常安装、Vite 照常打包,只是不进 asar。

一条判断口诀:「删掉这个包,打包出来的应用还能不能跑?」能跑 → devDependencies。

问题 4:asar 里混进旧构建的「幽灵文件」

现象:dist-electron/ 里出现没被任何代码引用的 .js 文件(带内容哈希的旧 chunk),并且被原样打进安装包。真实项目里曾积到 7 个文件、约 2 MB,其中包括两份 950 KB 的完整旧版主进程 bundle。

本质:vite build 只清理自己的输出目录 dist/;dist-electron/ 是 vite-plugin-electron 写的,没人清。只要某次改动让输出文件名变化(重命名模块、动态 import 增删、chunk 拆分),旧文件就永久残留。React 变体实测复现:主进程加一个动态 import 模块 extra.ts(产出 extra-DPF_M5d5.js),把它改名 extra2.ts 再 build——旧的 extra-DPF_M5d5.js 仍然躺在 dist-electron/ 里并被打进 asar。

这套房里只有一间屋有人打扫(dist/,装修队每次自己清);dist-electron/ 那间换了家具也没人扔旧的,而搬家公司打包时是「整间房连灰尘一起装箱」——旧东西越多,集装箱越沉。

修复:build 脚本前置清理(package.json):

"build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true});require('fs').rmSync('dist-electron',{recursive:true,force:true})\" && tsc && vite build && electron-builder"
json

顺手要修的小问题

  • .gitignore 缺口(脚手架的 bug):脚手架其实带了往 .gitignore 补 dist-electron 和 release 的逻辑,但因为它用「整行精确匹配」找插入点,而模板文件是 CRLF 行尾,匹配永远失败——补丁静默失效,所有变体的 .gitignore 都缺这两行,构建产物会被提交进 git。手动补上即可;
  • 占位符元信息:electron-builder.json5 里 appId: "YourAppID"、productName: "YourAppName" 是脚手架的占位符,直接决定安装包文件名(实测产物叫 YourAppName-Windows-0.0.0-Setup.exe)和安装目录,改成真实值;
  • 元数据警告:description is missed / author is missed——在 package.json 补这两个字段即可,不影响构建;
  • 图标:default Electron icon is used——把 build/icon.png(≥256×256)放进项目根的 build/ 目录,electron-builder 会自动识别。

统一修复清单(复制给 AI 一次做完)

以上修复对三种模板完全一致,可以整段粘给 AI 一次执行(也可以自己动手,10 分钟的事):

这个项目由 create-electron-vite 0.7.1 生成,按顺序完成以下修复,每步做完跑 npx tsc --noEmit 验证:

1. 删除 electron/main.ts 中未使用的 require 声明:
   "import { createRequire } from 'node:module'" 和 "const require = createRequire(import.meta.url)" 两行
2. 依赖分区:渲染进程专用依赖(react、react-dom,Vue 项目则是 vue)从 dependencies
   移到 devDependencies;dependencies 只留主进程运行时需要的包
3. build 脚本最前面加产物清理:
   node -e "require('fs').rmSync('dist',{recursive:true,force:true});require('fs').rmSync('dist-electron',{recursive:true,force:true})" &&
4. electron-builder.json5 增加:
   "electronDownload": { "mirror": "https://npmmirror.com/mirrors/electron/" }
5. .gitignore 补两行:dist-electron/ 和 release/(脚手架的自动补丁因 CRLF 静默失效了)
6. electron-builder.json5 里 YourAppID / YourAppName 占位符改成真实应用名

全部完成后跑一次 npm run build 确认通过,报告 asar 体积。

修复后怎么验收

  1. npm run build 全程无下载日志(或有 npmmirror 下载且成功);
  2. ls dist-electron/ 文件数符合预期(模板项目就是 main.js + preload.mjs + 你自己的 chunk),没有名字眼生的孤儿文件;
  3. asar 体积合理:模板级项目 < 1 MB,纯模板甚至没有 node_modules 目录;
  4. 冒烟:直接运行 release/<版本>/win-unpacked/<应用名>.exe,窗口能起来即主进程依赖齐全。

如果你在 Electron 构建上被卡住的不止这四个问题,或者想让人直接帮你把项目捋顺——欢迎聊聊。

相关文章

这篇文章帮到你了吗?

有疑问、发现错误,或想聊聊你的实践。 每一条反馈我们都会认真读。

想聊聊你的项目?

文中遇到的问题,我们大多亲手踩过、修过。安许科技帮中小企业做 Web 系统、桌面软件与 AI 辅助交付,远程协作、按项目报价。