Electron应用上鸿蒙PC,安装包从180MB压到45MB,我做了哪些骚操作

Electron应用上鸿蒙PC,安装包从180MB压到45MB,我做了哪些骚操作 Electron应用上鸿蒙PC安装包从180MB压到45MB我做了哪些骚操作上个月老板丢给我一个任务把现有的Electron应用搬到鸿蒙PC上。我花了两天把代码跑通了build了一版安装包一看体积——180MB。老板看了一眼表情很复杂。他倒没直说但我懂那意思一个鸿蒙桌面应用体积快赶上某些3A游戏启动器了用户下还是不下说实话Electron应用体积大是个老话题但在鸿蒙PC这个场景下问题更扎眼。鸿蒙PC用户目前不算多愿意尝鲜的往往是开发者或对体验敏感的人你丢过去一个近200MB的安装包第一印象就崩了。而且鸿蒙PC的磁盘空间管理、应用分发渠道比如华为应用市场对体积都有隐性的门槛。我花了一周时间死磕这个问题最终把安装包从180MB压到了45MB。过程中踩了不少坑有些坑官方文档根本不会提。下面记录一下完整过程代码可以直接拿去用。第一步搞清楚体积到底从哪来的磨刀不误砍柴工我先做了体积分析。Electron应用的体积大头通常三块Electron运行时本身、node_modules、以及静态资源图片、字体、本地化文件等。我找了一个工具——electron-builder自带的--dir模式可以不打包直接看输出目录结构再结合du在鸿蒙PC上我换成了ncdu因为du太慢了一层层往下扒。结果有点出乎我意料组件体积占比Electron运行时含Chromium等~115MB64%node_modules~45MB25%应用代码静态资源~20MB11%Electron运行时这块是最难啃的骨头占了快三分之二。node_modules也不冤枉——项目里塞了一堆用不上的依赖。应用代码反而占比最小。这个分析很重要因为它直接决定了我后续的优化策略主攻运行时裁剪和依赖瘦身应用代码本身空间不大。踩坑实录asar不是银弹我的第一反应是开asar打包把代码和资源全塞进去能省多少在electron-builder.yml里加了一行asar:true重新打包一看体积——183MB。比原来还大了3MB。我傻眼了。后来翻了electron-builder的源码才明白asar本身是一种归档格式有块对齐和索引开销。对于大量小文件比如node_modules里成千上万的JS文件asar打包后反而会变大。它的真正价值在于防止用户直接修改源码、以及减少文件句柄占用不是压缩体积。这玩意儿是个保护机制不是压缩工具。我一开始把它当压缩用方向就错了。真正有效的压缩得靠asarUnpack配合afterPack钩子做资源预处理或者直接换种思路——不是怎么包得更紧而是怎么把不需要的东西直接扔掉。砍掉Chromium的多语言包立竿见影Electron运行时里藏着一个很多人不知道的东西Chromium自带了将近50种语言的本地化文件locales/目录。每个语言包不大也就几百KB但加起来快10MB了。关键是我的应用只支持中文和英文。剩下那四十多种语言包纯纯的无效载荷。electron-builder其实有配置可以清这些但默认不开。我在electron-builder.yml里加了一段extraResources:-from:./assets/to:assets/filter:[**/*]electronDownload:mirror:https://npmmirror.com/mirrors/electron/afterPack:scripts/remove-locales.js然后写了一个scripts/remove-locales.jsconstfsrequire(fs);constpathrequire(path);exports.defaultasyncfunction(context){constlocalesDirpath.join(context.appOutDir,locales);if(!fs.existsSync(localesDir))return;constkeep[zh-CN.pak,zh-TW.pak,en-US.pak,en-GB.pak];constfilesfs.readdirSync(localesDir);letremoved0;for(constfileoffiles){if(!keep.includes(file)){fs.unlinkSync(path.join(localesDir,file));removed;}}console.log(Removed${removed}locale files, kept${keep.length});};这一步砍掉了约8MB。很爽。但注意鸿蒙PC上Electron的目录结构和Windows/Mac略有不同appOutDir的路径在鸿蒙环境下会落在resources/下我实际运行时调了两遍才把路径对齐。如果你在鸿蒙PC上跑这个脚本建议先console.log一下context.appOutDir确认路径再删。node_modules瘦身比想象中更脏node_modules占45MB里面混了大量测试文件、文档、TypeScript声明文件、甚至某些包的源码。这些东西生产环境压根用不上。我本来想手动一个个包去清理结果发现有个工具叫electron-builder的files配置配合!排除规则可以在打包阶段直接过滤。files:-!**/*.map-!**/*.d.ts-!**/test/**/*-!**/tests/**/*-!**/docs/**/*-!**/examples/**/*-!**/node_modules/.bin/**/*-!**/node_modules/**/*.md-!**/node_modules/**/*.ts-!**/node_modules/**/LICENSE*-!**/node_modules/**/README*-!**/node_modules/**/CHANGELOG*-!**/node_modules/**/package-lock.json-!**/node_modules/**/yarn.lock但这有个坑某些包比如sqlite3的.node原生模块旁边会带一个napi-v6之类的目录里面可能混着测试文件。如果你用上面的通配符一把梭有可能会误删原生模块依赖的辅助文件导致运行时MODULE_NOT_FOUND。我就踩过这个坑。better-sqlite3有一个build目录里面除了.node文件还有一个Release/obj.target/子目录存放编译中间产物。我一开始用!**/build/**想清理中间产物结果把.node也给排除了。后来改成更精确的规则files:-!**/node_modules/**/build/Release/obj.target/**/*-!**/node_modules/**/build/Release/.deps/**/*-!**/node_modules/**/deps/**/*-!**/node_modules/**/src/**/*只删obj.target、.deps、deps、src这些编译相关目录保留.node本体。这一步下来node_modules从45MB压到了18MB减了差不多六成。鸿蒙PC特有禁用不需要的Chromium功能鸿蒙PC的Electron构建默认启用了不少Chromium功能比如Widevine DRM、Pepper Flash虽然Flash已经死了但代码还在、以及Chrome扩展支持。这些在鸿蒙PC上基本用不上却实打实地占体积。我研究了一下Electron本身不支持像Chromium那样用gn args裁剪feature因为Electron是预编译的但可以通过electron-rebuild配合自定义Electron源码构建来实现。这个方案太重了我试了两天在鸿蒙PC上编译Electron源码直接把我机器内存吃满了而且编译出来的包还不稳定有些API行为变了。后来我换了个思路不裁剪Electron本体而是在应用启动时禁用不需要的功能这样虽然安装包体积不变但至少运行时内存占用下来了。而且更重要的是我发现了一个被忽视的点——Electron的app.setPath和userData配置可以影响一些缓存文件的生成策略减少磁盘占用。不过真正让安装包进一步缩水的是我发现electron-builder在鸿蒙PC上打包时默认会把整个swiftshader目录软件渲染器也打进去占大概15MB。鸿蒙PC目前基本都是带GPU的设备swiftshader其实用不上。// scripts/remove-swiftshader.jsconstfsrequire(fs);constpathrequire(path);exports.defaultasyncfunction(context){constswiftshaderDirpath.join(context.appOutDir,swiftshader);if(fs.existsSync(swiftshaderDir)){fs.rmSync(swiftshaderDir,{recursive:true,force:true});console.log(Removed swiftshader directory (~15MB));}};注意这个操作有风险。如果你的应用需要在纯软件渲染环境运行比如某些虚拟机或远程桌面删掉swiftshader会白屏。我在鸿蒙PC上测了真机和几个模拟器确认GPU可用才敢删。建议你根据自己的目标环境判断。静态资源图片格式和按需加载应用代码和静态资源原本只占了20MB但我检查了一下发现里面有一堆PNG截图和图标。PNG是无损格式对于照片类图片效率很低。我批量把截图转成了WebP图标用SVG替换最终静态资源从20MB压到了8MB。// 批量转换脚本需要sharp库constsharprequire(sharp);constfsrequire(fs);constpathrequire(path);constassetsDir./assets/images;constfilesfs.readdirSync(assetsDir).filter(ff.endsWith(.png));asyncfunctionconvert(){for(constfileoffiles){constinputpath.join(assetsDir,file);constoutputpath.join(assetsDir,file.replace(.png,.webp));awaitsharp(input).webp({quality:85}).toFile(output);fs.unlinkSync(input);console.log(Converted${file});}}convert();另外有些页面用到的第三方库比如图表库我改成了按需加载而不是在index.html里全局引入。这部分优化对安装包体积没直接帮助但启动速度有明显提升。最终成果做完上面这些优化重新打包优化项节省体积累计基线-180MB清理Chromium多语言包-8MB172MBnode_modules瘦身去测试/文档/源码-27MB145MB移除swiftshader鸿蒙PC特化-15MB130MB静态资源转WebP/SVG-12MB118MB应用代码压缩去map文件-5MB113MB开启compressionnsis/web-68MB45MB最后一刀是electron-builder的compression配置。之前用的是默认的normal我改成maximumcompression:maximum这一步让安装包从113MB直接压到45MB。代价是打包时间从3分钟变成了12分钟安装时解压也慢一些。但对于分发场景来说安装包体积小比打包时间长重要得多。不过maximum压缩在某些老旧CPU上解压会比较慢如果你的用户群体设备性能一般可以考虑store解压快但体积大或normal平衡。我个人在鸿蒙PC上测了几台设备性能都还不错maximum完全能扛。一些个人建议体积优化这事没有一劳永逸的方案。你每加一个npm包每更新一次Electron版本体积都可能反弹。我建议把体积检测做成CI的一部分在打包流程里加一个阈值检查// scripts/check-bundle-size.jsconstfsrequire(fs);constMAX_SIZE_MB50;constinstallerfs.statSync(dist/ElectronApp-1.0.0.exe);constsizeMBinstaller.size/1024/1024;if(sizeMBMAX_SIZE_MB){console.error(Bundle size${sizeMB.toFixed(2)}MB exceeds limit${MAX_SIZE_MB}MB);process.exit(1);}还有个感受很多Electron开发者包括我自己对安装包体积这个事没那么敏感反正现在网速快了磁盘大了。但一旦你的应用要进应用市场、要发更新包、要在企业内网分发体积就成了一个硬指标。特别是鸿蒙PC目前生态还在早期用户对第一个印象很看重一个小而快的应用比一个功能多但臃肿的应用更容易被接受。说到底做桌面应用跟做后端服务不一样用户的感知是直接的——点一下多久出来占多少磁盘风扇转不转这些细节加起来决定了你的应用是好用还是将就。本文遵循 MIT 协议发布。转载请注明出处商业转载请联系作者获得授权。