最近我实在是忍受不了win10的便签,又丑又不太好用,因此就自己用Electron+Create-react-app实现了一个桌面应用 Delay-Task
由于我是第一次使用 Electron 做桌面应用,因此还是踩了很多坑的,这里就给大家说一下我遇到的坑。
开始
如果你也想结合 Electron 和 React 进行桌面应用开发,建议你看下这篇文章:Getting Started with Electron by Creating a React App,这里面详细的说明了各种工具的安装步骤,我觉得还是不错的。
主进程和渲染进程
大家在进行 Electron 的时候一定要搞清楚主线程和渲染线程两个概念。
主进程
每个 Electron 应用都会有一个主进程,主进程是在 Node 环境下运行的,这意味着如果你要使用一些 Node 模块比如 fs
操作文件,那你就必须在主进程中做这些事情。
此外,对于App窗口的操作和生命周期这些也都是要写在主进程中的。
渲染进程
渲染进程当然就是负责渲染我们的前端内容,渲染进程中可以操作页面上的 dom 而主进程中不可以。同时,在渲染进程中无法访问 Node API。
本地数据保存工具 electron-store
假如我需要在前端触发事件时需要进行本地数据保存的操作,那么推荐使用 electron-store 作为保存工具。相比于 localStorage
,electron-store
的数据存取更加方便,而不是像 localStorage
一样在保存前必须将数据转化为字符串。
ipcRenderer & ipcMain
如果我想实现一个点击保存按钮将数据保存到本地的功能,我就可以使用 electron-store
。由于 electron-store
需要操作 fs
模块,因此必须放在主进程中。但是对于按钮点击事件的监听又必须放在渲染进程中,因此我们需要一个桥梁来建立两个进程之间的通信。
Electron 的 ipcRenderer 和 ipcMain 模块可以实现,它们基于 Node 的 EventEmitter 实现。
首先在主线程中引入 ipcMain
监听数据保存事件:
const { ipcMain } = require('electron')
const Store = require('electron-store')
const store = new Store()
// 注册了一个save-data事件
ipcMain.on('save-data', (_, key, val) => {
store.set(key, val)
})
然后在渲染进程中引入 ipcRenderer
监听到点击事件触发:
const { ipcRenderer } = require('electron')
const elBtn = document.getElementById('saveBtn')
// 监听button的click事件,这里只是写个例子
window.addEventListener('click', (key, val)=>{
ipcRenderer.send('save-data', key, val)
})
当我们点击按钮,渲染现成的 ipcRenderer
用 send
把信息发给 ipcMain
。ipcMain
接收到之后就会执行对应事件的代码。
假如我们要从本地文件中获取信息,也就是说需要 ipcMain
发信息给 ipcRenderer
,可以这样写:
// 渲染进程
// sendSync 方法表示发送同步信息
const data = ipcRenderer.sendSync('get-data', key)
// 主进程
// event.returnValue用来设置返回值返回给ipcRenderer
ipcMain.on('get-data', (event, key) => {
event.returnValue = store.get(key)
})
构建
构建的时候我使用的是 electron-builder,使用这个工具需要在 package.json
中进行配置。详细配置请看官方文档。
配置打包时的选项需要在 package.json
中配置 builder
字段,我是这样配置的:
"build": {
"productName": "delayTask", // 应用名
"appId": "com.electron.delayTask",
"copyright": "Copyright © 2020 power by Longgererer",
"win": {
"icon": "static/icons/logo.ico" // 图标路径
},
"files": [ // 构建时需要一起打包的文件
"node_modules/**/*",
"build/**/*",
"public/electron.js"
],
"directories": {
"buildResources": "static",
"output": "build" // 输出文件夹
},
"extends": null
},
然后在 package.json
里 scripts
字段中加上如下几个命令:
"postinstall": "electron-builder install-app-deps",
"pack-react": "yarn build",
"pack-electron": "electron-builder build"
打包的时候按顺序从上到下执行就好了。
坑1:fs.existsSync is not a function
我在渲染线程中引入 ipcRenderer
时报了这个错。翻了许多 github issues 后找到了合适的解决办法:
在主线程 BrowserWindow
配置中写上 nodeIntegration: true
:
const viewport = new BrowserWindow({
webPreferences: {
nodeIntegration: true
},
})
在根目录创建 .webpack.config.js
文件,写入:
// define child rescript
module.exports = config => {
config.target = 'electron-renderer'
return config
}
再创建一个 .rescriptsrc.js
文件,写入:
module.exports = [require.resolve('./.webpack.config.js')]
然后执行 yarn add @rescripts/cli @rescripts/rescript-env --dev
命令安装Rescripts。
将 package.json
里 scripts
字段中的:
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
改成:
"start": "rescripts start",
"build": "rescripts build",
"test": "rescripts test",
这样使用 fs
模块的时候就不会报错啦。
坑2:windows 系统下不显示 Notification
需要在 package.json
中加上一个 appId
字段:
"appId": "com.electron.delayTask",
然后在主线程中写上:
app.on('ready', () => {
if (process.platform === 'win32') {
app.setAppUserModelId('com.electron.delayTask')
}
})
注意 appId
的值一定是相对应的,不要打错。
坑3:Uncaught ReferenceError: require is not defined
在主线程 BrowserWindow
配置中写上 contextIsolation: false
:
const viewport = new BrowserWindow({
webPreferences: {
contextIsolation: false
},
})
坑4:Not Allow to load local ressource
一般是由于html文件引入路径错误导致。
在入口文件中配置:
const viewport = new BrowserWindow({})
if (!isDev) {
viewport.loadURL(`file://${path.join(__dirname, "../build/index.html")}`)
}
由于我打包的时候生成文件夹名是 build
,所以写的是 ../build/index.html
。
坑5:Application entry file does not exist
打包时报错:Application entry file "build/electron.js" in the "<path>/dist/mac/<app-name>/Contents/Resources/app.asar" does not exist.
这是由于 react-script
这个包的内置配置与 electron-builder
的配置发生了冲突,需要在 package.json
里的 build
配置中配上 "extends": null
来避免使用 react-script
的配置。
坑6:打包完成后页面js文件加载失败路径错误
在 package.json
中配置项目主页的url:"homepage": "./"
。
这样打包出来的文件路径就是正确的了。
以后如果遇到了其他坑我还会继续整合在这篇文章中。