electron 开发经验之谈系列-使用 electron-builder 打包

本文最后更新于:2021年2月19日 晚上

前言

基本项目搭建 中,我们已经能够启动一个 electron 应用程序了(开发环境),现在来看如何将之打包为二进制程序便于分发给最终用户。

依赖

渲染层打包

直接使用 cra 的打包工具即可,没什么大不了的,但确实存在一些注意事项

  • 因为 electron 在生产环境会从文件系统中加载静态资源,所以打包出来的静态资源必须支持相对路径,下面是常见的两个设置。

主进程打包

electron-builder 打包需要以下几个步骤

更新 package.json 的一些配置

  1. 使用 package.json 中的 build 字段作为配置项,参考: https://www.electron.build/
  2. electron-builder 使用 main 字段作为启动脚本文件
  3. electron-builder 要求必须使用固定的版本号,意味着 electron 依赖需要指定为 "electron": "10.2.0"

下面是一个基本的配置示例

{
  "main": "dist/main.js",
  "scripts": {
    // 一个非常基本的打包脚本
    "pkg": "electron-builder"
  },
  "devDependencies": {
    "electron": "10.2.0",
    "electron-builder": "^22.9.1",
    // electron 主进程实际上是 nodejs 环境,所以为了更好的开发体验,安装 nodejs 的类型定义
    "@types/node": "^12.19.12"
  },
  "build": {
    // 程序的唯一标识符
    "appId": "com.rxliuli.electron_example",
    // 打包出来的 exe 名字
    "productName": "electron 示例应用",
    // 打包的目录
    "directories": {
      "output": "release"
    },
    "win": {
      // 打包目标,参考: https://www.electron.build/
      "target": ["nsis"]
    }
  }
}

复制静态资源

现在,我们需要打包静态资源并复制到主进程模块里面

  1. cd apps/renderer 目录
  2. yarn build 打包静态资源
  3. 将静态资源复制到 build/dist 目录下

修改主进程入口文件 main.ts

还需要修改 src/main.ts 代码,主要修改 BrowserWindow 对象载入的 url 地址

import { app, BrowserWindow } from "electron";
import path = require("path");
import { URL } from "url";

async function createMainWindow() {
  // 创建新的 electron 窗口
  const mainWindow = new BrowserWindow();
  // 载入生产环境的 url
  await mainWindow.loadURL(
    new URL(path.join(__dirname, "./build/index.html")).href
  );
}

// 其他代码...

注意,这个 url 路径是相对于打包后的 dist/main.js 而言的,因为最终打包的程序运行时的相对路径也是这样。

打包主进程的 exe 程序

因为 electron-builder 需要下载基本的 electron 程序,所以请提前设置好透明代理,如果不知道它是什么,参考: 透明网关Proxifier

  1. 使用 yarn compile 编译 ts 代码
  2. 使用 yarn pkg 打包 electron 应用

现在,我们应该可以在 apps/main/release/win-unpacked 下看到 exe 程序,双击它即可看到之前在开发环境的首页了。

参考: https://github.com/rxliuli/electron_example/tree/85d398fc2c6ba6c918ad9641dbb5d8bae2d4216b/apps/main

优化打包

虽然打包已经实现,但确实还存在一些问题

  • 打包脚本仍然不是一键的
  • 不能兼容开发、生产环境

下面我们来解决这两个问题

实现一键打包二进制程序

使用 gulp 复制渲染层的静态资源

1、添加 gulp 相关依赖 yarn add -D gulp ts-node @types/gulp fs-extra @types/fs-extra

  • gulp @types/gulp: gulp 核心依赖
  • ts-node: 使用 ts 编写 gulp 脚本必须的依赖
  • fs-extra @types/fs-extra: fs 的扩展增强,使用 Promise 包装异步 api

2、添加 gulp 脚本文件

import { copy, remove } from "fs-extra";
import * as path from "path";

async function copyByMap(copyMap: [string, string][]) {
  await Promise.all(
    copyMap.map(async ([src, destDir]) => {
      const srcPath = path.resolve(__dirname, src);
      const destPath = path.resolve(__dirname, destDir, path.basename(srcPath));
      await copy(srcPath, destPath);
    })
  );
}

/**
 * 清理最终生成目录
 */
export async function clean() {
  await remove(path.resolve(__dirname, "dist"));
  await remove(path.resolve(__dirname, "release"));
}

/**
 * 复制一些资源到 dist 目录中
 */
export async function copyStatic() {
  await copyByMap([["../renderer/build", "dist/"]]);
}

3、添加 npm script

注: lerna 的好处之一就是可以运行其它模块的 npm script。

{
  "scripts": {
    // 打包渲染层的静态资源
    "build:web": "lerna run --scope renderer build",
    // 打包渲染层的静态资源之后复制然后使用 electron-builder 打包 exe 程序
    "pkg": "gulp clean && yarn compile && yarn build:web && gulp copyStatic && electron-builder"
  }
}

4、修改 tsconfig.json

此时在 apps/main 模块根目录下也有 ts 文件了,所以 tsc 翻译代码会将它们也包含进去,但实际上不需要。

{
  "include": ["src"]
}

现在,我们可以使用一个命令打包 exe 程序了。

参考: https://github.com/rxliuli/electron_example/blob/3dacff5dc0/apps/main/package.json

使用环境变量来兼容开发、生产环境

解决方案简单来说就一句话:使用环境变量指定开发环境的 URL。
这里使用 env-cmd 来跨平台写入环境变量(不使用 dotenv 的原因在于自定义环境使用起来有点麻烦,不像 env-cmd 那么直观),而另一个 cross-env 并未提供管理环境变量的解决方案。下面说一下使用 env-cmd 的步骤

  1. 安装依赖 yarn add -D env-cmd

  2. 添加配置文件 .env-cmdrc.json
    基本上是一个键值映射文件,键是环境,值对象是环境变量

    {
      "dev": {
        "ELECTRON_START_URL": "http://localhost:3000/"
      }
    }
  3. dev:electron 命令之前设定环境变量 env-cmd -e dev electron ./dist/main.js

  4. 修改 src/main.ts 读取环境变量

    await mainWindow.loadURL(
      process.env.ELECTRON_START_URL ||
        path.join(__dirname, "./build/index.html")
    );

    现在,像 基本项目搭建更新 package.json 添加几个 npm script 说的那样启动开发环境就会显示开发环境的页面,打包后显示的则是打包后的静态资源。

效果

参考: https://github.com/rxliuli/electron_example/blob/f8b4f94435/apps/main/.env-cmdrc.json

总结

虽然 electron-builder 已经足够好用了,但它仍然不能解决 electron 项目工程上的问题,所以这里结合了 lerna/gulp/env-cmd 打包。