首屏加载速度慢怎么优化 Electron 资源打包体积

文章导读
如果首屏加载明显偏慢,先别急着把原因归结到打包体积上。Electron 启动流程里,主进程初始化、渲染进程资源读取、网络请求都有可能拖慢首屏。体积优化只对本地包体加载和解析环节有效,如果页面是在等接口返回,换打包工具收益不大。下面的排查顺序,适合已经确认本地包体较大、启动阶段卡在读取文件或解析模块的场景。
📋 目录
  1. A 先看体积构成,别让 node_modules 背锅
  2. B 动态 import 拆包,路径必须写死
  3. C asar 不是为媒体文件准备的
  4. D 原生模块单独盯一下 ABI
  5. E 依赖审查和 tree-shaking 的边界
  6. F 改完后看这几个信号
A A

如果首屏加载明显偏慢,先别急着把原因归结到打包体积上。Electron 启动流程里,主进程初始化、渲染进程资源读取、网络请求都有可能拖慢首屏。体积优化只对本地包体加载和解析环节有效,如果页面是在等接口返回,换打包工具收益不大。下面的排查顺序,适合已经确认本地包体较大、启动阶段卡在读取文件或解析模块的场景。

先看体积构成,别让 node_modules 背锅

排查 Electron 打包体积时,先别急着改配置。用 electron-builder 的 --dir 模式生成未压缩目录,再用 du 或 ncdu 按目录大小排序,通常会发现 node_modules 占用过半。重点检查是否有大型依赖被整体打入,比如把整个 pdfjs-dist 或 sharp 都塞进了主进程。此时先区分哪些是运行时必需,哪些只是开发依赖,再决定是替换还是按需加载。

做体积构成排查的目标是找到大头,而不是单纯删代码。把开发依赖和运行时依赖分开后,可以看大型依赖是否真的要在主进程使用。像 pdfjs-dist 如果只用在渲染进程的某个页面,可以考虑用动态加载或其他库代替。但替换依赖要评估功能差异和 API 兼容性,不能只看体积数字。

动态 import 拆包,路径必须写死

对于体积较大的功能模块,可以改用动态 import 并在渲染进程或主进程按需引用。以数据库驱动或 OCR 引擎为例,只在用户触发对应操作时才 require,打包时这些模块会通过 webpack 或 esbuild 的代码分割拆成独立 chunk。注意动态加载路径必须显式写全,避免使用变量拼接,否则打包工具会找不到模块,运行时直接报错。

动态 import 的函数形式是让打包器能静态解析的关键。下面这种写法在 electron-vite 或 webpack 里都能让模块变成独立 chunk:

// 正确:路径完全可解析
const db = await import('./db/sqlite-driver');

// 错误:变量拼接会让打包器放弃搜索
const name = './db/sqlite-driver';
const db = await import(name);

拆出的 chunk 会出现在资源目录里,首屏只加载主进程和初始页面需要的模块,后续触发操作时才读对应文件。验证时可以在 DevTools 的 Network 面板确认首屏没有加载这些 chunk,再打包后看 dist 目录是否多出按功能命名的 js 文件。要注意动态加载会带来一点额外延迟,适合低频操作,如果某个功能几乎每次启动都会用,拆包的收益就不明显。

asar 不是为媒体文件准备的

图片和字体资源要压缩,但别盲目把所有文件都塞进 asar。asar 压缩率有限,对于已经压缩过的 png、jpg 和 mp4 文件,放入 asar 不会显著减小体积,反而会占内存。正确做法是:将配置文件、JSON、未压缩的 SVG 和 woff2 字体留在 asar 内,把大体积媒体文件放到 extraResources,并在运行时从 process.resourcesPath 读取。注意 extraResource 路径在打包后需要额外处理,尤其是 macOS 的 asar 路径与资源路径的拼接方式不同。

首屏加载速度慢怎么优化 Electron 资源打包体积

electron-builder 的 files 和 extraResources 是两套复制逻辑。配置文件、JSON、未压缩字体可以通过 files 留在 asar 里,而 mp4、png 这类媒体文件用 extraResources 直接放到 resources 目录。例如:

files:
  - "dist/**"
  - "package.json"
extraResources:
  - from: "assets/videos"
    to: "media"
    filter: ["**/*.mp4"]
  - from: "assets/images"
    to: "images"

代码里不要硬编码相对路径,统一用 process.resourcesPath 拼接。macOS 下 app.asar 本身是一个文件,路径和 Windows、Linux 不完全一样,需要先确认 process.resourcesPath 指向的是 Contents/Resources 而不是 asar 内部。如果图片或视频是从渲染进程读取,还需要在打包后检查 file:// 路径的拼接。

原生模块单独盯一下 ABI

项目里如果使用了 better-sqlite3、bcrypt 这类原生模块,打包体积会明显增加,并且存在 ABI 不匹配的风险。操作流程通常是:先运行 electron-rebuild 重编模块,再在 electron-builder 配置里填入 npmRebuild: true,让每次打包都重新编译。验证模块版本是否匹配,可以在主进程打印 process.versions.modules,和模块目录里 binding.gyp 或 node-gyp 给出的版本对照。

原生模块无法 tree-shaking,一旦依赖就会整包打进去。如果模块体积过大,可以考虑用纯 JS 替代方案,但一定要测试运算性能和兼容性,例如 bcrypt 换成 bcryptjs,虽然体积小很多,但同步性能差异明显,需要结合场景决定。

依赖审查和 tree-shaking 的边界

用 webpack-bundle-analyzer 或 electron-bundle-analyzer 看模块依赖树,能快速发现重复打包的库。例如不同依赖可能各自带了一份 lodash 或 moment,通过 import 映射统一到同一版本可以减少重复。另一些包在 package.json 的 main 字段指向体积很大的 CommonJS 入口,改用 module 字段引入 ES 版本后,打包器可以更彻底地摇树。但并不是所有库都提供了 module 入口,强行把 import 指向替代版可能改动运行时行为,升级依赖版本前要跑一遍相关功能用例。

改完后看这几个信号

打包体积优化是否真正有效,不能只看总大小。建议在改动前后做同一环境的启动录制,观察主进程启动到渲染进程 ready 的耗时,以及首屏资源请求的瀑布图。如果体积减少了但首屏耗时没变化,说明瓶颈在别处。另外,每一次调整 dependencies 或 extraResources 之后,都要完整走一遍安装包安装流程,注意 asar 内和外文件是否存在,原生模块是否能在目标系统启动。如果出现启动崩溃,先回滚到上一个可用配置,再用最小复现方式隔离是哪个依赖或资源路径出了问题。