Electron+Create-react-app构建桌面应用时遇到的坑

1,243 阅读5分钟

最近我实在是忍受不了win10的便签,又丑又不太好用,因此就自己用Electron+Create-react-app实现了一个桌面应用 Delay-Task

1614658567_1_.png

由于我是第一次使用 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 作为保存工具。相比于 localStorageelectron-store 的数据存取更加方便,而不是像 localStorage 一样在保存前必须将数据转化为字符串。

ipcRenderer & ipcMain

如果我想实现一个点击保存按钮将数据保存到本地的功能,我就可以使用 electron-store。由于 electron-store 需要操作 fs 模块,因此必须放在主进程中。但是对于按钮点击事件的监听又必须放在渲染进程中,因此我们需要一个桥梁来建立两个进程之间的通信。

Electron 的 ipcRendereripcMain 模块可以实现,它们基于 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)
})

当我们点击按钮,渲染现成的 ipcRenderersend 把信息发给 ipcMainipcMain 接收到之后就会执行对应事件的代码。

假如我们要从本地文件中获取信息,也就是说需要 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.jsonscripts 字段中加上如下几个命令:

"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.jsonscripts 字段中的:

"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": "./"

这样打包出来的文件路径就是正确的了。

以后如果遇到了其他坑我还会继续整合在这篇文章中。