使用 electron 有段时间了, 作为跨端应用的最牛 x 的打包工具, 被其简单易用性深深折服. 之前都是开箱的简单配置. 最近项目有更多的需求, 挖了更多的 API, 主要包括:
- 如何区分系统将第三方软件打包并运行于子进程?
- 如何优化 electron 的打包体积?
先上一下项目目录, 便于后面的描述
.
├── app // 生产package.json
│ ├── package.json
│ └── yarn.lock
├── build // 第三方资源
│ ├── macResources
│ └── winResources
├── dist // 打包的应用的储存处
├── electron-builder.yml // 配置文件
├── icon.icns // Mac的 icon
├── icon.ico // Win 的 icon
├── main.js // 主程序
├── package.json // 开发 package.json
├── to-build // 前端资源页面
│ ├── index.bundle.js
│ ├── index.html
├── webpack.config.js // 打包 main.js 的 webpack 配置
└── yarn.lock
分解第一个任务:
- 区分系统
Electron 有一些宏变量可以在文件中直接使用, 请参考 File Macros, 我需要的就是
${os}, 如果这些不能满足你, 可以使用env.ENV_NAME自己定义一个. 需要使用 cross-env 保证跨平台
cross-env CUSTOMER_ENV=anything electron-builder
- 如何打包第三方软件 需要说明 electron 默认会检测除了 build 文件夹下的所有文件, 如果满足定义的 file-pattern , 则会将其打包.需求是根据不同系统打包不同的第三方软件(比如驱动). 我采用的方法是:
- 配合
${os}的值来命名文件夹,macResources/winResources, 可以很轻松的使用宏变量"${os}Resources"来找到对应文件夹 - 将两者都放入 build 文件夹中(这样不会打包任何内容)
- 有以下几种方式可以将其打包进
- 定义
file - 定义
extraResources - 定义
extraFiles(与前者唯一的区别是前者不会被拷贝到content中, 参见 ) 我最终采用的是配置file, 因为file会与main在同一级目录下, 对我后面的命令行的路径查找更便于理解(extraResources会在Resource这个目录, 需要../到上一级)
- 定义
请注意, 定义了 file 字段也就表示默认的就失效了, 需要手动把所有的都包含进去. 而且要为文件夹或者包含通配符(直接使用文件名去尝试包含无效)
还请注意, 如果是可执行文件, 不能采用 asar 方式进行压缩, 需设置: asar: false. 采用 2,3 则没这个限制(毕竟是在父文件夹, 有真实路径, 不会被压缩), 经过比对, 使不使用 asar, 应用体积并没有太大的区别, 而且也没有安全性一说, 很轻松就可以解压出来, 参考. 当然也可以使用 asarUnpack 字段去自定义配置. 可参考
接下来就是如何让第三方软件伴随着应用一起执行了. 在 main.js中使用child-process模块的 spawn. 传入对应路径即可.这个就不是属于 electron 的范围了, 是 nodejs 的 API .
let childSpawn;
const checkMacOS = () => os.platform() === 'darwin';
if (checkMacOS()) {
childSpawn = spawn(path.join(__dirname, 'macResources/xxx'));
} else {
spawn(path.join(__dirname, './winResources/xxx'));
}
使用 spawn 是因为在关闭软件之后需要同时终止掉第三方软件. win 可以自动终止, mac 需要指定 pid
mainWindow.on('closed', function() {
if (checkMacOS()) {
process.kill(childSpawn.pid);
}
mainWindow = null
})
其实这样打包的体积已经比我没有区分平台进行打包的要小很多了,(不区分平台会将两个平台都打包进去)本着一不做二不休的态度, 还能不能再优化呢? 看了打包的内容中 node_module 还占了几十兆, 里面还有没用的 electron 和 electron-builder. 不行, 得想办法删掉.
直接删肯定不行, 因为 main.js 还有不少 require 呢. 所以第一步就是使用 webpack 进行打包, 把依赖连起来, 需要注意的是要将 target 设置为 electron-main
打包完成后如何包含 main.js 有两种方案
- 使用之前的
files字段, 将webpack后的main.js包含进去. 这里可以使用通配符等方法, 不赘述 - 使用双package.json , 这个有点意思, 一个是开发
package.json, 一个是生产package.json而且看起来项目结构更干净, 于是试一波 具体操作为 - 新建一个
app文件夹, 会默认作为打包的根目录, 所以在配置文件中的files是使用的相对路径需要对应着修改 - 在
app中新建一个package.json, 只需要包含name, version, description等简单信息, 以及 如果有非 js 的依赖, 比如调用了C++的库, 那就需要在这里的dependencies中添加.我没有用到, 所以暂时忽略.
以下是 webpack 的配置, 因为本身 electron 就是 chromium 内核, 不需要做兼容性, 所以没有引入 babel
const path = require('path');
module.exports = {
target: 'electron-main',
entry: path.resolve(__dirname, 'main.js'),
output: {
path: path.resolve(__dirname, 'app'),
filename: '[name].js'
},
mode: 'production',
node: {
// 使用绝对路径
__dirname: false,
}
};
以下是 electron 的 yml 配置, 可做参考
directories:
output: ./dist
appId: com.electron.xxx
asar: false
copyright: xxx
productName: xxx
mac:
target:
- dmg
icon: ./icon.icns
dmg:
contents:
- x: 130
'y': 50
type: dir
name: Driver
path: build/macResources/Driver
- x: 130
'y': 210
- x: 400
'y': 210
type: link
path: /Applications
nsis:
allowToChangeInstallationDirectory: true
oneClick: false
menuCategory: true
include: build/installer.nsh
allowElevation: true
perMachine: true
win:
target: nsis
files:
- from: ../build/${os}Resources/
to: ${os}Resources/
- from: ./
to: ./
- from: ../to-build
to: web
compression: normal
electronVersion: 6.0.9
说下结果吧, 因为按需加载第三方软件, 减小了一半的 resource 大小, 因为去掉了 node_modules, 使用 webpack uglify 之后又少了几十兆. 因为我第三方软件比较大, 所以整体下来减小了 400 多 M. 加上配置的 compression: normal. 再进行了压缩, 安装包的体积控制在了 150M 以内, 满足了产品需求.