electron 的实战总结--减小包体积

15,574 阅读1分钟

使用 electron 有段时间了, 作为跨端应用的最牛 x 的打包工具, 被其简单易用性深深折服. 之前都是开箱的简单配置. 最近项目有更多的需求, 挖了更多的 API, 主要包括:

  1. 如何区分系统将第三方软件打包并运行于子进程?
  2. 如何优化 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

分解第一个任务:

  1. 区分系统 Electron 有一些宏变量可以在文件中直接使用, 请参考 File Macros, 我需要的就是${os}, 如果这些不能满足你, 可以使用 env.ENV_NAME 自己定义一个. 需要使用 cross-env 保证跨平台
cross-env CUSTOMER_ENV=anything electron-builder
  1. 如何打包第三方软件 需要说明 electron 默认会检测除了 build 文件夹下的所有文件, 如果满足定义的 file-pattern , 则会将其打包.需求是根据不同系统打包不同的第三方软件(比如驱动). 我采用的方法是:
  2. 配合${os}的值来命名文件夹, macResources/winResources, 可以很轻松的使用宏变量"${os}Resources"来找到对应文件夹
  3. 将两者都放入 build 文件夹中(这样不会打包任何内容)
  4. 有以下几种方式可以将其打包进
    1. 定义 file
    2. 定义 extraResources
    3. 定义 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 还占了几十兆, 里面还有没用的 electronelectron-builder. 不行, 得想办法删掉.

直接删肯定不行, 因为 main.js 还有不少 require 呢. 所以第一步就是使用 webpack 进行打包, 把依赖连起来, 需要注意的是要将 target 设置为 electron-main

打包完成后如何包含 main.js 有两种方案

  1. 使用之前的 files 字段, 将 webpack 后的 main.js 包含进去. 这里可以使用通配符等方法, 不赘述
  2. 使用双package.json , 这个有点意思, 一个是开发 package.json, 一个是生产 package.json 而且看起来项目结构更干净, 于是试一波 具体操作为
  3. 新建一个 app 文件夹, 会默认作为打包的根目录, 所以在配置文件中的 files 是使用的相对路径需要对应着修改
  4. 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 以内, 满足了产品需求.