写在前面
已经不是第一次使用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
}
},
...
复制代码
上面有几个属性比较容易出错,需要根据自己项目具体情况进行设置:
- "main": "main.js", ———— 项目入口文件
- "homepage": "./", ———— 根目录
- "buildResources": "public",———— 打包资源文件夹目录
- "files": [...], ———— react打包时额外需要那些文件
- "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;
复制代码
上面这些代码主要就两块代码要特别说两句:
- 意识urlLocation页面路径这里;
- 本地开发环境和打包正式环境要做区分;
- 打包正式环境需要使用url.format方法,并配置hash路径;
不使用url.format方法会出现打开新窗口空白;窗口刷新后空白问题;
- 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打包文件夹
然后在Finder/文件管理器中双击打开xxx.dmg文件或者mac/xxx.app文件就可以打开项目了。
大家要是在electron打包过程中有什么其他问题,欢迎在评论区留言,我看到会第一时间看看,或许能帮上什么忙?