electron打包前后端
目录
init项目
node
版本v18.16.0
前端初始化
如果你已熟悉前端的开发与生产环境的启动部署, 可以快速跳过本步骤.
使用任意你打算使用的前端框架, create-react-app(CRA)
, vue-cli
等等.
前端变化太快, 之前react官网一直把CRA
当亲儿子, 首推这个, 我搭建的两个electron项目前端框架也都是用它. 没想到如今react官网已然看不到推荐使用CRA
搭建项目了. 现在推荐用nextJS.
于是我这里初始化了一个nextJS玩玩, 版本为13.4.1
, 配置选择如下.
根据官方文档的介绍, 在src/app
目录下添加一个/dashboard
的前端路由页面, 又稍微改动了一下开发端口(3001)与打包参数(静态文件打包).
现在我们启动开发环境是npm run dev
之后的3001
端口, 生产环境是打包出来的build
文件夹下的静态文件.
添加electron和相关依赖
yarn add -D concurrently wait-on electron electron-builder
yarn add electron-is-dev express
名称 | 说明 |
---|---|
electron | 呃, 就是electron |
electron-builder | 打包electron应用 |
express | 生产环境中, 启前端用的 |
concurrently | 开发环境中, 自动启前端再启electron用的 |
wait-on | 开发环境中, 自动启前端再启electron用的 |
electron-is-dev | 判断electron目前处于开发还是生产环境 |
开发环境启动electron
官网介绍的非常清晰简洁了: electron的quick-start
简单来说2个改动:
package.json
添加入口文件和启动脚本.
{
...,
"main": "public/electron.js",
"scripts": {
"dev": "next dev -p 3001",
"build": "next build",
"start-electron": "electron .",
"start-all": "concurrently \"npm run dev\" \"wait-on http://localhost:3001 && npm run start-electron\""
},
...
}
- /public下添加
electron.js
const { app, BrowserWindow } = require('electron')
const isDev = require('electron-is-dev')
let mainWindow
const createWindow = () => {
// 主窗口参数设置
mainWindow = new BrowserWindow({
fullscreenable: true,
fullscreen: true,
webPreferences: {
nodeIntegration: true
},
titleBarStyle: 'default',
autoHideMenuBar: true,
title: 'demo',
maximizable: true,
resizable: true,
show: false
})
try {
if (!isDev) {
const productPort = 8013
mainWindow.loadURL(`http://localhost:${productPort}`)
} else {
mainWindow.loadURL('http://localhost:3001')
}
} catch (error) {
console.log('error happened when start frontend', error)
}
mainWindow.on('ready-to-show', () => {
mainWindow.show()
})
}
app.whenReady().then(createWindow)
app.on('window-all-closed', () => {
console.log('close app')
if (process.platform !== 'darwin') {
console.log('app quit')
app.quit()
}
process?.exit()
})
改动完毕, 尝试启动. 上面第1个改动中, 看到我添加了一个
"start-all": "concurrently \"npm run dev\" \"wait-on http://localhost:3001 && npm run start-electron\""
就是把启动前端和启动electorn的两个命令合并了, 完全可npm run dev
跑完了, 再跑npm run start-electron
.
如果对electron
的启动还不是很熟悉, 又或者有问题与疑问, 建议分开跑, 每次只重新跑一下npm run start-electron
即可, 而且在electron.js里可以打log看看执行.
上面的启动electron的代码
mainWindow.loadURL('http://localhost:3001')
如果看明白了, 实际就能理解, electorn
就是壳, localhost:3001
是肉.
剥离electron
的包裹, 剩下的就是我们传统的web前端开发.
生产环境启动
如何启动前端
传统的web生产环境部署, 用nginx
, serve
(CRA推荐)或者express
, pm2
等等方式很多.
因为electron自带node环境, 我这里直接用express
了.
在public/electron.js
新增部分代码
const path = require('path')
const express = require('express')
const appEx = express()
let mainWindow
const createWindow = () => {
...
try {
if (!isDev) {
const productPort = 8013
// 用express启生产环境前端
const reactBuildPath = path.join(__dirname, '../build')
appEx.use(express.static(reactBuildPath))
appEx.get('/*', (_, res) => {
res.sendFile(path.join(__dirname, '../build/index.html'))
})
appEx.listen(productPort)
console.log(`server is running at ${productPort} port`)
mainWindow.loadURL(`http://localhost:${productPort}`)
} else {
mainWindow.loadURL('http://localhost:3001')
}
} catch (error) {
console.log('error happened when start frontend', error)
}
...
}
就是用express
启动生产环境, 然后electron
再包裹载入生产环境的端口.
具体的启动生产环境的代码, 可能因不同的前端框架, 打包出里的结构有出入, 需要做一些调整.
至于如何调试启动开发环境, 简单的办法就是在build
文件下建一个test.js
文件, 把启动要用的js代码复制进去, 直接node test.js
, 模拟测试看看项目成功启动与否.
打包electron
用electron-builder启动, 详细的参数了解请看官方文档.
我这里说我修改的一些很基础的地方, package.json内添加一些脚本与设置
"main": "build/electron.js", // 这里生产环境打包进去后, 目录变为build/elctron.js
"scripts": {
...,
"start-all": "concurrently \"npm run dev\" \"wait-on http://localhost:3001 && npm run start-electron\"",
"build-electron": "electron-builder --dir", // 这里负责打免安装版的electron
"build-all": "npm run build && npm run build-electron"
},
"build": {
"appId": "demo",
"npmRebuild": true,
"asar": false,
"mac": {
"category": "tools"
},
"files": [
"build/**/*",
"package.json"
]
}
跑npm run build-all
即可.
生产环境启动后端
设计思路:
我们的前端是用node
环境, 跑express
启的, 后端同理, 也可以在electron.js
内启动.
同时为了保证前后端一起关闭, 利用node的childProcess, 关联前端主进程即可.
开发环境测试后端接入
我这里在项目下建立一个backend文件夹, 简单写个接口模拟一下后端, 调用locaclhost:3003
就会返回个当前时间
cd ./backend
node index.js
后端起来后, 在我们的前端dashboard页面加入一个接口请求
"use client"
import { useEffect, useState } from 'react'
import styles from './style.module.css'
export default function Dashboard() {
const [state, setState] = useState('')
useEffect(() => {
getTimeFromRequest()
}, [])
const getTimeFromRequest = async () => {
const response = await fetch('http://localhost:3003')
const time = await response.text()
setState(time)
}
return (
<div className={styles.red}>
dashboard page
<div>{`current Time: ${state}`}</div>
</div>
)
}
到'localhost:3001/dashboard'就能看到请求是成功的
打包进生产环境
// 引入child_process
const exec = require('child_process')
let childProcess
// 启动前端
...
mainWindow.loadURL(`http://localhost:${productPort}`)
// 用node启后端环境
const backendPath = path.join(__dirname, './backend')
childProcess = exec.spawn(
'node',
['index.js'],
{ cwd: backendPath }
)
childProcess.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
childProcess.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
childProcess.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
...
...
app.on('window-all-closed', () => {
console.log('close app')
// 结束时增加kill子进程指令
if (childProcess) {
childProcess.kill()
console.log('close backend childProcess')
}
if (process.platform !== 'darwin') {
console.log('app quit')
app.quit()
}
process?.exit()
})
package, 新增打包命令脚本, 在前端打包完毕后, 将后端代码复制到build文件夹下
"build": "next build",
"cp-backend": "cp -r ./backend ./build",
"build-electron": "electron-builder --dir",
"build-all": "npm run build && npm run cp-backend && npm run build-electron"
跑一下 npm run build-all
, 就好啦.
启动康康:
java或其他启动的后端
我上面的例子是node启后端, electron有node环境, 所以不需要再安装其他. 像我们公司的项目, 大部分后端用clojure开发, 要启jar包. 那就需要把jdk也打进去(除非你的安装机器已有java环境).
贴部分代码供参考一下:
// 子进程启动java
const javaStartCmd = ['-jar', 'device-sensor.jar']
const javaBuildPath = path.join(__dirname, '../build/backend')
// java路径为
const java17Path = path.join(__dirname, '../build/jdk-17.0.1/bin')
childProcess = exec.spawn(
`${java17Path}/java`,
javaStartCmd,
{
cwd: javaBuildPath,
env: {
detached: false,
LANG: 'zh_CN.UTF-8'
}
}
)
上面代码表示我的build文件夹内是有jdk-17.0.1
的java环境的, 所以在复制backend
也别忘了把jdk
拷贝进去.
这里拓展链接一下之前java环境变量的引发的bug
electron打包调试
其中打包electron可能会遇到问题, 我在这里给出几个常见问题与调试建议:
查看打包后的原始文件
asar
在调试中改为false, 这样可以在dist/mac/electron-next-demo.app/Contents/Resources/app
内, 直接看到你打包进去的文件, 也许可以解决包括以下并不限于的问题:
- 某些文件打包打丢了
- 打包完的路径和预想的不一样, 有时也会有绝对路径, 相对路径的问题
比如我的打包electron内, 就把package.json的
public/electorn.js
->build/electron.js
, 因为打包进Resources/app
后没有public文件夹了
命令行启动查看log
用命令行启动你的免安装包软件, 这样可以看到electron.js
内的log. 只要你的log够详细, 很快就可以定位到是哪几行代码导致的启动electron出错, 如下图
利用个熟悉的浏览器调试窗口
electron窗口起来后, 可以调出熟悉的调试窗口, 看看前端报错. 很多人遇到过白屏的错误, 如果是静态资源加载的路径不对, 这里也是能看出端倪的.
避免前端路由带来的生产环境启动问题
生产环境的前端跳转有问题. 这里就要搞清楚你的项目有没有用到前端路由, 以及启生产环境是怎么启动的. 像我这里的/*
和/
的差别, 就是解决前端路由的启动.
appEx.get('/*', (_, res) => {
res.sendFile(path.join(__dirname, '../build/index.html'))
})