「为什么」Electron加create-react-app打个包这么费劲?

2,082 阅读3分钟

写在前面

已经不是第一次使用electron打包了,上次也颇花了些时间,我竟不曾想这次又颇花了些。真是颇有些无助了,不得不写点什么以告慰那些被花费的时间了。

背景

打包之前有必要给各位交代一下项目背景,或许对你们也是有些用的。

  • create-react-app搭建的项目
  • electron 11
  • react
  • react-router

方案选择

目前主流的大包有 electron-packager和electron-builder两种方案;

鉴于electron-builder就是有比electron-packager有更丰富的的功能支持更多的平台,同时也支持了自动更新。除了这几点之外,由electron-builder打出的包更为轻量,并且可以打包出不暴露源码的setup安装程序;所以果断选择electron-builder作为首选打包工具。

记得第一次打包使用electron-packager在项目构建目录下打包,配置相对简单,本想继续使用electron-packager打包的,莫名报错,最后放弃。估计是electron版本问题;另一方面也是自己没有记录下打包具体步骤和坑点导致的。

打包配置

首先当然是安装electron-builder了:

npm i electron-builder  --dev

然后是package.json相关配置

  ...
  "main": "main.js",
  "homepage": "./",
  "scripts": {
    "ele": "electron .",
    "onlyEle": "nodemon --watch main.js --watch src/menuTemplate.js --exec \"electron .\"",
    "dev": "concurrently \"wait-on http://localhost:3000 && electron .\" \"cross-env BROWSER=none npm start\"",
    "start": "node scripts/start.js",
    "build": "node scripts/build.js",
    "test": "node scripts/test.js",
    "package": "npm run build && electron-builder build --mac",
  },
 "build": {
    "appId": "top.lixingli",
    "productName": "自治领",
    "copyright": "Copyright © 2022 ${author}",
    "directories": {
      "buildResources": "public"
    },
    "files": [
      "build/**/*",
      "node_modules/**/*",
      "settings/**/*",
      "package.json",
      "main.js",
      "./src/menuTemplate.js",
      "./src/AppWindow.js"
    ],
    "extends": null,  
    "electronDownload": {
      "mirror": "https://npm.taobao.org/mirrors/electron/"
    },
    "mac": {
      "icon":"public/favicon.icns",
      "category": "public.app-category.productivity",
      "artifactName": "${productName}-${version}-${arch}.${ext}"
    },
    "dmg": {
      "background": "public/appdmg.jpg",
      "icon": "public/favicon.icns",
      "iconSize": 100,
      "contents": [
        {
          "x": 380,
          "y": 280,
          "type": "link",
          "path": "/Applications"
        },
        {
          "x": 110,
          "y": 280,
          "type": "file"
        }
      ],
      "window": {
        "width": 500,
        "height": 500
      }
    },
    "nsis": {
      "allowToChangeInstallationDirectory": true,
      "oneClick": false,
      "perMachine": false
    }
  },
  ...

上面有几个属性比较容易出错,需要根据自己项目具体情况进行设置:

  1. "main": "main.js", ———— 项目入口文件
  2. "homepage": "./", ———— 根目录
  3. "buildResources": "public",———— 打包资源文件夹目录
  4. "files": [...], ———— react打包时额外需要那些文件
  5. "electronDownload": { "mirror": "npm.taobao.org/mirrors/ele…" }, ———— 使用淘宝镜像作为electron数据源,不然会卡半天下载electron

其他参数是不同平台的一些配置,晒出来给大家作为参考。重点是把上面这些参数调整好,基本就成功了一半了。

接下来看一下路由相关的配置:

import React from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import './index.css';
import App from './App';
import Setting from './setting';
import Crash from './crash';
import { HashRouter as Router, Switch, Route } from 'react-router-dom'; // 只能用hash路由

ReactDOM.render(
  <Router>
    <Switch>
      <Route exact path="/">
        <App />
      </Route>
      <Route path="/setting">
        <Setting />
      </Route>
      <Route path="/crash">
        <Crash />
      </Route>
    </Switch>
  </Router>,
  document.getElementById('root')
);

路由这一快记住使用hash路由就好了,做前端的应该都知道Browser路由想正常使用,需要后端配置ngix代理之类的,这里就不多说了。

最后来看看在main.js中怎么使用这些路由,实现打开新窗口吧!

const { app, Menu, ipcMain, dialog } = require('electron');
const isDev = require('electron-is-dev');
const menuTemplate = require('./src/menuTemplate');
const AppWindow = require('./src/AppWindow');
const Store = require('electron-store');
const path = require('path');
const url = require('url');

const settingsStore = new Store({ name: 'settings' });
let mainWindow, newWindow;

app.on('ready', () => {
  const mainWindowConfig = {
    width: 1200,
    height: 800,
  };
  const mainUrlLocation = isDev
    ? 'http://localhost:3000/'
    : url.format({
        pathname: path.join(__dirname, './build/index.html'),
        protocol: 'file:',
        slashes: true,
      });
  mainWindow = new AppWindow(mainWindowConfig, mainUrlLocation);
  mainWindow.on('close', () => {
    mainWindow = null;
  });
 
  // main event
  ipcMain.on('open-new-window', (_, urlPath) => {
    if (newWindow) {
      newWindow.close();
    }
    const newWindowConfig = {
      width: 800,
      height: 600,
      parent: mainWindow,
    };
    const settingsUrlLocation = isDev
      ? `http://localhost:3000/#/${urlPath}`
      : url.format({
          pathname: path.join(__dirname, '/build/index.html'),
          protocol: 'file:',
          slashes: true,
          hash: 'setting',
        });
    newWindow = new AppWindow(newWindowConfig, settingsUrlLocation);
    if (urlPath === 'setting') {
      newWindow.removeMenu();
    }
    newWindow.on('close', () => {
      newWindow = null;
    });
  });

// AppWindow.js
const { BrowserWindow, protocol } = require('electron');

class AppWindow extends BrowserWindow {
  constructor(config, urlLocation) {
    const baseConfig = {
      width: 800,
      minWidth: 600,
      height: 600,
      // frame:false,
      titleBarStyle: 'hidden',
      webPreferences: { // 适用于electron 11
        javascript: true,
        plugins: true,
        nodeIntegration: true, // 是否集成 Nodejs
        enableRemoteModule: true,
        webSecurity: false,
        // contextIsolation: false,
      },
      show: false,
      backgroundColor: '#efefef',
    };
    const finalConfig = { ...baseConfig, ...config };
    super(finalConfig);
    this.loadURL(urlLocation);
    this.once('ready-to-show', () => {
      this.show();
    });
   
  }
}

module.exports = AppWindow;
  

上面这些代码主要就两块代码要特别说两句:

  1. loadURL方法传入的url;
  • 本地开发环境和打包正式环境要做区分;
  • 打包正式环境需要使用url.format方法,并配置hash路径;

不使用url.format方法会出现打开新窗口空白;窗口刷新后空白问题;

  1. BrowserWindow的配置根据版本会有不同,上面的配置适用electron 11,自己在使用时要留意版本号, 不过小版本却不应该不大

开始打包

由于已经在script中配置打包命令:

    ...
    "package": "npm run build && electron-builder build --mac",
    ...

所以直接执行打包命令

    npm run package

很奇怪,我每次执行两次这个命令才能打包成功,中间报了下面这个错,所以我后面就先用npm run build打包react,再用electron-builder build --mac打包electron,给大家做个参考吧。

'.../node_modules/digest-header/node_modules/utility'  
stackTrace=Error: ENOENT: no such file or directory

理想情况打包顺利完成,项目目录下新增两个文件夹:

  • build react打包文件夹
  • dist electron打包文件夹

image.png

然后在Finder/文件管理器中双击打开xxx.dmg文件或者mac/xxx.app文件就可以打开项目了。

image.png

大家要是在electron打包过程中有什么其他问题,欢迎在评论区留言,我看到会第一时间看看,或许能帮上什么忙?