参考文章:⚡️ From React to an Electron app ready for production,原文较为久远了,所以本文对部分代码进行了修改
使用React构建Electron应用时,官网推荐使用electron-react-boilerplate
。但是这个项目有些重,于是想使用比较熟悉的create-react-app
进行构建,记录一下踩坑过程。
个人比较喜欢TypeScript,所以使用create-react-app的TypeScript模板进行构建
npx create-react-app electron-cra --template typescript
不过后面都跟TypeScript无关了,所以习惯js的可以省略--template typescript
然后安装一些依赖
yarn add electron electron-builder cross-env wait-on concurrently --dev
yarn add electron-is-dev
这时运行yarn start
可以启动网页。很常规的CRA流程。
接下来将项目改造成为Electron应用。
按照Electron官网的教程,向根目录下添加main.js
const { app, BrowserWindow } = require('electron')
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
})
// 这里对官网代码进行了修改,因为cra是启动了web服务而非静态文件
// win.loadFile('index.html');
win.loadURL('http://localhost:3000/');
win.webContents.openDevTools();
}
app.whenReady().then(createWindow)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
然后在package.json
中添加"main": "main.js"
这时候启动两个终端,一个运行yarn start
,一个运行yarn electron ./
,就能看到Electron运行起来了
但这只是开发环境,部署使用electron-builder
显然不能使用这种方式,因此要使用create-react-app
构建后的结果。
运行yarn build
,生成build
目录,里面就是create-react-app
编译之后的文件。
打开build
目录下的index.html
文件,发现是一片空白,F12一下,发现是资源路径不对。解决方案是在package.json
中添加"homepage": "./"
。添加之后再进行build,打开index.html
,就能看到正常页面了。
直接运行yarn electron-builder
进行打包,报错
Application entry file "build\electron.js" in the "......\electron-cra\dist\win-unpacked\resources\app.asar" does not exist.
Seems like a wrong configuration.
原来在打包时需要build\electron.js
文件,这个文件的作用和main.js是一样的。在build
目录下直接建一个electron.js
显然不太合理,那怎么办呢?
解决方法是将electron.js
放在public
目录里,因为编译时public
目录内的内容是会被复制到build
目录下的;然后将public/electron.js
指定为Electron的入口。
在public
目录下新建electron.js
,内容和main.js
基本一致,除了将
win.loadURL('http://localhost:3000/');
修改为
win.loadURL(`file://${__dirname}/index.html`);
然后将package.json
中的"main"
项修改为"public/electron.js"
,并增加"build"
项:
"build": {
"appId": "com.example.electron-cra",
"files": [
"build/**/*",
"node_modules/**/*"
],
"directories":{
"buildResources": "assets"
},
"extraMetadata":{
"main":"build/electron.js"
}
}
(根据原文的说法,files
和directories
项是为了避免electron-builder和CRA的冲突,但是我没看懂……总之就先留着吧。extraMetadata
项是从原文中一个错误命令build --em.main=build/electron.js
修改来的)
现在,执行
yarn build
yarn electron-builder
就能在dist
目录下看到打包好的程序了。为了将两个步骤结合起来,可以在package.json
的script
节中增加命令:
"electron-pack": "electron-builder",
"preelectron-pack": "yarn build"
这样,执行electron-pack
,就能同时执行编译和打包。
经过以上改造,打包程序的工作完成了,但又有了新问题:publlic/electron.js
中指定了
win.loadURL(`file://${__dirname}/index.html`);
这样我们就不能在开发环境执行Electron,也不能使用CRA的动态加载修改功能了。解决方法就是在publlic/electron.js
中增加开发环境的判断,在开头增加引用
const isDev = require('electron-is-dev');
然后修改前面的那行代码为
win.loadURL(isDev ? 'http://localhost:3000' : `file://${__dirname}/index.html`);
这样Electron就能识别当前环境,选择合适的渲染来源了。
最后一个问题,现在要在开发环境下运行,还是需要启动两个终端,分别运行Web服务器和Electron。可以使用concurrently
将两个命令集成为一个。
在package.json
的script
节中增加命令:
"electron-dev": "concurrently \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && electron .\""
就可以通过执行yarn electron-dev
,同时开启web服务和Electron了。
最后放上最终版的publlic/electron.js
和package.json
//publlic/electron.js
const { app, BrowserWindow } = require('electron')
const isDev = require('electron-is-dev');
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
})
win.webContents.openDevTools();
win.loadURL(isDev ? 'http://localhost:3000' : `file://${__dirname}/index.html`);
}
app.whenReady().then(createWindow)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
//package.json
{
"name": "electron-cra",
"version": "0.1.0",
"private": true,
"homepage": "./",
"main": "public/electron.js",
"build": {
"appId": "electron.antd",
"files": [
"build/**/*",
"node_modules/**/*"
],
"directories": {
"buildResources": "assets"
},
"extraMetadata": {
"main": "build/electron.js"
}
},
"dependencies": {
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"@types/jest": "^26.0.15",
"@types/node": "^12.0.0",
"@types/react": "^16.9.53",
"@types/react-dom": "^16.9.8",
"electron-is-dev": "^1.2.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-scripts": "4.0.1",
"typescript": "^4.0.3",
"web-vitals": "^0.2.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"electron-dev": "concurrently \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && electron .\"",
"electron-pack": "electron-builder",
"preelectron-pack": "yarn build"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"concurrently": "^5.3.0",
"cross-env": "^7.0.3",
"electron": "^11.1.1",
"electron-builder": "^22.9.1",
"wait-on": "^5.2.0"
}
}