主进程的启动速度直接决定了用户从双击图标到看见窗口的时间。一个臃肿的主进程会阻塞 app.whenReady() 的触发,导致窗口迟迟无法创建,用户只能盯着空白屏幕或跳动的小图标发呆。优化主进程启动,本质上就是砍掉一切不是首屏必需的加载和初始化。
19.2.1 延迟加载(Lazy Loading)
Node.js 的 require 是同步且会缓存的,在文件顶部一口气引入所有依赖是常见的习惯,但这也意味着即使某个模块在启动阶段根本用不到,它依然会被解析、编译并占用内存。延迟加载的核心就是把那些“未来才可能用到”的模块推迟到真正使用时才引入。
哪些模块适合延迟加载?
- 自动更新功能(
electron-updater):用户安装后不会立刻触发检查更新。 - 数据库驱动(
better-sqlite3、nedb):在首次数据读写时才初始化。 - 日志库、复杂的配置解析、第三方分析服务 SDK。
- 窗口创建代码:除主窗口外,设置窗口、关于窗口等完全可以在用户点击菜单时才加载对应的构造脚本。
具体做法非常简单:用函数或 getter 将 require 包裹起来。
// ❌ 启动时加载,即使暂时用不到
const { autoUpdater } = require('electron-updater');
// ✅ 按需时再加载
function getAutoUpdater() {
const { autoUpdater } = require('electron-updater');
return autoUpdater;
}
更进一步,可以把窗口创建逻辑本身做成懒加载。例如,只在菜单点击事件里 require 对应窗口的模块并创建,而不是在应用启动时就把所有窗口代码全都加载进内存。在大型 Electron 应用中,这种拆分能让启动耗时明显下降。
19.2.2 按需初始化
延迟加载解决的是“代码加载”的问题,而按需初始化更关注“资源分配”和“服务启动”的时机。主进程中有不少资源是启动完成后才逐步使用的,比如托盘图标、全局快捷键、本地服务端口监听、文件监视器等。
一个常见的错误是在 app.whenReady() 中一口气初始化所有服务:
app.whenReady().then(() => {
createMainWindow();
setupTray(); // 托盘图标
registerShortcuts(); // 全局快捷键
startLocalServer(); // 本地 HTTP 服务
initFileWatcher(); // 文件监听
});
如果你的应用启动时主窗口才是用户最迫切看到的东西,那么后四行完全可以推迟到主窗口渲染完成之后再执行,或者干脆延迟到用户首次触发相关功能时再初始化。可以借助 setImmediate、setTimeout 或窗口的 'ready-to-show' 事件来实现:
win.once('ready-to-show', () => {
// 延迟初始化非关键模块
setTimeout(() => {
setupTray();
registerShortcuts();
}, 200);
});
对于需要前置条件的模块,比如本地服务器只在打开特定功能时才需要,那么就在该功能对应的 IPC 处理函数里按需启动,而不是一上来就建好连接池或端口监听。
19.2.3 精简依赖
主进程中 node_modules 的体量也会影响启动性能。Node.js 在加载模块时需要解析文件路径、读取 package.json 等操作,依赖树越庞大、文件数量越多,启动阶段花费在模块加载上的时间就越长。因此,定期精简依赖是保持主进程轻快的好习惯。
实用的精简手段包括:
- 用小而美的库替代重型库:比如用
dayjs(2KB)替代moment(数百 KB),用原生fs.promises替代fs-extra(如果不需要递归复制等额外功能)。 - 移除未使用的依赖:引入
depcheck工具扫描出那些在package.json中声明但实际未require的包,果断卸载。 - 善用 Node.js 内置模块:许多功能实际上 Node.js 自身已经提供,例如
crypto生成 UUID、URL解析路径、util.promisify转换回调风格,不必额外安装第三方包。 - 检查重复或可合并的依赖:运行
npm ls或yarn list查看依赖树,有时同一个库的多个版本被间接依赖,可以通过resolutions(Yarn)或overrides(npm)强制统一版本。 - 避免在主进程引入前端专用库:像图表库、UI 组件库只属于渲染进程,绝不要在主进程里
require,即使只是偶然被间接引用也会拖慢启动。
此外,利用 Webpack、Vite 等工具的打包能力,将主进程代码与渲染进程代码分别处理,还能对主进程进行 tree-shaking 和代码压缩,进一步减少加载负担。
优化主进程启动是一场“做减法”的工作。每次引入新依赖或添加启动初始化逻辑时,都问自己一句:“这个模块/操作必须在窗口出现之前完成吗?” 通过系统性地延迟加载、按需初始化和精简依赖,你可以让应用像原生程序一样秒出主界面,在用户体验上迈出一大步。