5.2 启动速度优化:懒加载、预加载与代码分割


5.2 启动速度优化:懒加载、预加载与代码分割

本节摘要:冷启动时间的构成是"主进程初始化 + 窗口创建 + 页面加载与首帧渲染",优化思路是先测量分段耗时,再对症压缩:窗口状态记忆与延迟 show 消除白闪、非必要初始化延迟与并行化、前端代码分割让首屏只载必需模块。本节给出完整的测量方法与三层优化手段,并用一个把冷启动从近五秒压到一秒半的实战案例收束。

时间都花在哪了

先建立测量的习惯,再谈优化。冷启动的分段测量可以这样做:主进程入口先记一个起点时间戳,whenReady、窗口创建、页面完成加载(did-finish-load 事件)各记一个站点,首帧就绪(ready-to-show)收尾,打印出分段账单:

// 主进程:启动分段计时 const T0 = Date.now(); const mark = (label) => console.log(`[boot] ${label}: +${Date.now() - T0}ms`); app.whenReady().then(() => { mark('whenReady'); const win = new BrowserWindow({ width: 1024, height: 720, show: false }); win.webContents.on('did-finish-load', () => mark('页面加载完成')); win.once('ready-to-show', () => { mark('首帧就绪'); win.show(); }); win.loadFile(path.join(__dirname, '../renderer/index.html')); });
典型账单(优化前): [boot] whenReady: 620ms ← 应用框架初始化 + 窗口对象创建 [boot] 页面加载完成: 2900ms ← 前端bundle全量加载与执行 [boot] 首帧就绪: 4700ms ← 首屏渲染与同步初始化

账单会说话:这个例子里大头在页面侧(加载两秒三、渲染一秒八),那么主进程侧再怎么抠也省不出一秒。分段测量决定火力分配,这是启动优化的第一原则。另一条常被忽略的原则:杀毒扫描、磁盘冷热、开发模式的 source map 都会显著影响数字,对比测试要在同环境的正式构建上做。

消除白闪:窗口状态记忆

白闪(窗口先显示空白再冒出内容)即使总耗时不变也会被用户感知为"慢"。组合拳:show:false 加 ready-to-show 已经在 3.3 节给出;再进一步是记住上次的窗口状态,让窗口直接出现在用户熟悉的位置与尺寸,减少"窗口出现后又被调整"的二次跳变:

// 窗口状态记忆:读写 userData 下的状态文件(2.2 节同款模式) const stateFile = path.join(app.getPath('userData'), 'window-state.json'); async function loadWindowState() { try { const s = JSON.parse(await fs.readFile(stateFile, 'utf8')); // 校验状态合法性:屏幕可能变了,上次坐标可能已经不在任何屏幕内 const visible = screen.getAllDisplays().some(d => s.x >= d.workArea.x - 50 && s.x < d.workArea.x + d.workArea.width); return visible ? s : {}; } catch { return {}; } } const state = await loadWindowState(); const win = new BrowserWindow({ width: state.width ?? 1024, height: state.height ?? 720, x: state.x, y: state.y, show: false, backgroundColor: '#f7f8fa' });

屏幕校验那几行不是多余的:外接显示器拔掉后,上次的窗口坐标可能落在不存在的区域,窗口"创建成功但永远看不见"——这是多屏用户常见的幽灵故障。

延迟与并行:主进程侧的两招

主进程初始化的常见病灶是"入口文件里什么都干":读配置、连数据库、预热缓存、注册十几个通道,全部串行堵在 whenReady 之前。两招应对:

延迟非必要项。问每个初始化步骤一句:"首帧需要它吗?"配置读取也许必要,拼写检查词典、更新检查、遥测上报统统可以推迟到首帧之后:

app.whenReady().then(async () => { createMainWindow(); // 主线:只做首帧需要的事 await firstPaint(); // 等首帧落地 initUpdateChecker(); // 支线:全部推迟 warmSpellDictionary(); scheduleTelemetry(); });

并行可并行项。确实必要的初始化之间,用 Promise.all 并行取代串行 await——三个各耗三百毫秒的串行步骤,并行后只占最长的那个。

代码分割:让首屏只吃必需的

页面侧的大头优化交给打包器的代码分割。核心思想:首屏只加载首屏要用的模块,路由级组件、重量级编辑器、图表库全部按需加载。配置了分割后,网络面板(开发模式)或构建产物报告能看到首屏 bundle 明显瘦身:

// 路由级懒加载:图表页整体变成按需chunk const Dashboard = () => import('./views/Dashboard.vue'); // 用到才拉取 // 重量级依赖的使用点懒加载 button.addEventListener('click', async () => { const { highlight } = await import('heavy-highlighter'); highlight(codeBlock); // 第一次点击才加载高亮引擎 });

对 Electron 还有一步专属操作:本地资源的加载策略。开发模式走开发服务器,生产模式的页面与静态资源从磁盘 ASAR 包读取(第六章讲打包时展开)——本地读取没有网络往返,但同步解压与磁盘 IO 仍是成本,首屏资源清单越短越好。

案例:从近五秒到一秒半

背景:一个数据面板类应用,正式构建冷启动约四秒八,用户调研里"启动慢"是被提及最多的抱怨。

操作:分段测量定位火力点——页面侧占三秒七。三项手术:图表库与地图组件改路由级懒加载(首屏 bundle 从四兆压到九百 KB);入口的同步初始化拆出六项延迟到首帧后;补上窗口状态记忆与背景色消除白闪观感。主进程侧只做了一件小事:三个串行的配置读取并行化。

结果:冷启动稳定在一秒四到一秒六之间,白闪消失。未做的事项也如实记录:主进程框架初始化的六百毫秒属于框架固有成本,进一步压缩性价比极低,不再投入。

解读:这个案例的要点是优化要有账单和止损线——每一步对着分段数字动刀,数字不再显著改善就收手,把精力留给下一类问题。变式:如果测量显示大头在主进程的 whenReady(比如入口加载了庞大的原生模块),处方就换成原生模块按需加载或子进程化,页面侧的分割反而排在后面。

本节要点回顾

  • 第一原则:分段测量决定火力分配,别在占比小的环节较劲;
  • 白闪三件套:show:false、ready-to-show、窗口状态记忆加屏幕校验;
  • 主进程两招:非必要延迟到首帧后,必要项并行化;
  • 页面侧:路由级分割加重量级依赖按需导入,首屏清单最小化;
  • 止损线:框架固有成本不值得压榨,优化收益要能写进账单。

启动提速完成,下一节管帧率:滚动掉帧、动画卡顿这些"肉眼可见的不流畅"从哪来、怎么治。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U