记一次Electron开发全过程
Closed this issue · 1 comments
记一次Electron开发全过程
[[toc]]
引子
有一个需要开发PC桌面端的需求。日常中使用的网易云音乐,Twitch client等都属于electron的产品。
Electron对于前端开发来说上手非常简单,虽早有耳闻electron的强大,也看过一些文档和基本实现,但是没有完整的搭建、开发和维护过一个electron项目。借这个机会,熟悉一下electron技术栈,也写下本文,做一次技术积累。
Electron
Electron分为Main进程(主进程)和Renderer进程(渲染进程)
主进程为Node.js服务端环境,负责数据库处理,文件处理等
渲染进程为浏览器环境
主进程会将渲染进程的html,创建window,挂载html运行
主进程与渲染进程通过electron包提供的ipcMain和ipcRenderer进行进程通讯。其实就是一个EventEmitter,发布订阅模式。
项目搭建
起步
项目开始基本跟着首页的文档一步步走。
脚手架
直到Boilerplates and CLIs这一章。这一章开头也提到了说Electron只是提供了一种基于chromium渲染的桌面端包装而已。对于如何开发、构建、打包等是"unopinionated"。这一章中提到了很多的boilerplate和CLI的方案。
electron-forge
electron-builder
electron-react-boilerplate
前两者是比较基本的脚手架。不限制renderer部分选型。electron-react-boilerplate
这个从名字看就基本限定死React了。由于时间紧迫,React的水平也确实有限。只能在前两者里挑选。
对于前两个脚手架,我选的是star数较多的electron-builder,时间有限,仅能基于star数选型了。
electron-builder
使用yarn add electron-builder --dev
安装。同时electron-builder本身也提供了一些模板项目供快速开发参考。
这里的我使用vue-cli-plugin-electron-builder进行项目搭建。这个是基于Vue CLI3的插件。关于Vue CLI3的安装和插件的安装在此不做赘述。
vue-cli-plugin-electron-builder插件配置
由于这个插件是Vue CLI的插件,那配置文件理所应当在vue.config.js内。
贴上我目前的项目配置:
module.exports = {
pluginOptions: {
electronBuilder: {
builderOptions: {
"appId": "com.example.app",
"productName": "test-app",
directories: {
buildResources: './build',
output: "./dist"
},
"win":{//win相关配置
"icon":"./build/icon.png",//图标,当前图标在根目录下,注意这里有两个坑
"target": [
{
"target": "nsis",//利用nsis制作安装程序,
"arch": [
"x64",//64位
"ia32"//32位
]
}
],
},
"nsis": {
"allowToChangeInstallationDirectory": true,
oneClick: false,
}
}
}
}
}
项目结构
- build文件夹
存放�打包需要的文件,例如icon等 - public文件夹
- 静态文件夹
- 包含渲染进程favcon.ico,index.html等静态文件
- src文件夹
- 项目代码
- -assets: 渲染进程静态文件夹
- -biz: 主进程业务代码
- -components: Vue.js组件
- -plugins: Vue.js插件
- -router: Vue-router
- -store: Vuex
- -views: Vue.js主视图
- background.js: 主进程入口
- main.js 渲染进程入口
- vue.config.js
- vue-cli3的配置文件
主进程与渲染进程通讯
由于electron是一个unopinioned工具,他仅仅提供了进程通讯的方式,而对于如何组织通讯代码,如何封装API是没有一个最优解的。在这我会放出我对于进程通讯的二次封装。
概念描述
首先定义几个概念
- API
- 应用程序接口。
- 这里的API是给调用方使用的,也就是渲染进程。
- Payload Type
- 荷载类型。
- 定义业务类型(事件类型),其实也就是潜在的定义了业务入参和负责业务的具体描述。
- Routes
- 路由。
- 分发绑定业务句柄和业务类型。
- Services
- 服务。
- 纯净的业务处理,会涉及业务数据处理,db操作等。
路由与业务绑定
先放出Payload Type的定义
const PAYLOAD_TYPES = Object.freeze({
FETCH_FULL_DATA: 'FETCH_FULL_DATA',
FETCH_DROPDOWN_LIST: 'FETCH_DROPDOWN_LIST',
UPDATE_CELL: 'UPDATE_CELL',
SORT_SHEET: 'SORT_SHEET',
VALIDATE_SHEET: 'VALIDATE_SHEET',
CALCULATE_SHEET: 'CALCULATE_SHEET',
FETCH_INITIAL_DATA: 'FETCH_INITIAL_DATA',
DELETE_ROW: 'DELETE_ROW'
})
Routes会基于Payload Type进行类型和句柄的绑定,句柄内调用Services进行业务处理。
import { PAYLOAD_TYPES } from './constants'
import * as service from '../service'
export default {
[PAYLOAD_TYPES.FETCH_FULL_DATA]: function (payload, callback) {
callback(service.fetchFullData(payload))
},
[PAYLOAD_TYPES.FETCH_DROPDOWN_LIST]: function (payload, callback) {
callback(service.fetchDropdownList(payload))
},
[PAYLOAD_TYPES.UPDATE_CELL]: function (payload, callback) {
callback(service.updateCell(payload))
}
绑定路由至主进程
const establishConnection = () => {
Object.keys(routes).forEach((routeName) => {
ipcMain.on(routeName, (e, payload) => {
routes[routeName](payload, (respond) => {
e.sender.send(routeName, respond)
})
})
})
}
API声明
import { ipcRenderer } from 'electron'
import { PAYLOAD_TYPES } from '../channel/constants'
export default function fetchFullData(sheetName) {
return new Promise((resolve, reject) => {
// 定义请求参数
const payload = {
sheetName
}
// 发送请求
ipcRenderer.send(PAYLOAD_TYPES.FETCH_FULL_DATA, payload)
// 接受响应给callback
ipcRenderer.once(PAYLOAD_TYPES.FETCH_FULL_DATA, (e, res) => {
resolve(res)
})
})
}
�以上操作之后,API部分只要处理payload参数,发送到对应payload type的频道和绑定相应类型的响应句柄就可以了。
这样API的入参很清晰,回参就是service的回参。一番映射之后,API和Services的逻辑就相当纯净了,无需考虑ipcMain和ipcRenderer等一系列Electron相关的通讯操作。
新增API也只需要定义payload type,定义api,映射payload type和句柄,调用service就行了。
这套设计的优点在于,基本API, Routes,Service的创建只需要copy过去就能行,改改payload和方法名称,设计一下入参即可。之后的开发就可以完全沉浸在Services这一层里。
这套设计借鉴了Redux和Node.js里很多框架都采用的中心化路由映射的**。
数据存储
数据存储的话由于数据比较简单,目前我遇到的业务需求,基本一个mongodb就能够搞定。但是由于是electron,肯定不希望往使用方电脑装太多东西,其实mongodb也就是个json。那只需要找一个简单、成熟、语法友好的基于json的本地持久化工具即可。
在这里使用lowdb
数据持久化
db文件放在user data里做持久化。
获取user data的方法:app.getPath()
const filePath = path.join(app.getPath('userData'), '/data.json')
lowdb
lowdb是基于lodash API的json持久化工具。前端做久了,Lodash肯定是知道的,语法也比较熟。在这放出一些简单的db操作
初始化
const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')
const filePath = path.join(app.getPath('userData'), '/data.json')
const adapter = new FileSync(filePath)
const db = low(adapter)
db操作-增删改查
增
db.get('posts')
.push({title: 'example'})
.write()
删
db.get('posts')
.remove({title: 'example'})
.write()
改
db.get('posts')
.find({ title: 'example' })
.assign({ title: 'hi!'})
.write()
查
db.get('posts').value()
渲染进程
因为是基于Vue CLI搭的项目,那渲染进程这边肯定是选用Vue.js作为前端选型。到这里基本就跟普通web前端开发一样了。
业务就调用API就行了。
打包
electron-builder提供了很多的配置项。参考https://www.electron.build/configuration/configuration
配置文件也已经在上文中给出。
总结
整个项目从构思到搭建,再到开发流程基本没采坑,主要还是因为文档比较给力。
感觉唯一学到的东西就是终于有一套构建桌面端的解决方案了。。
如果有后续更新会在此补充,感谢阅读。
666