这一篇就主要讲一讲第一次制作electron桌面应用的具体过程以及踩坑。
初次制作主要用的是electron+react的路线,react负责渲染,如果使用vue的话思路大同小异,毕竟框架仅负责渲染进程。
首先我用一个已经完成的react项目进行测试,在react项目的package.json中添加“homepage”:“/.”,然后进行build,之后在build文件夹中,安装electron,打包构建应用,启动桌面应用,react项目正常显示。这只是个小测试,实际用途不大。第一点是,在package.json中添加“homepage”:“/.”之后,build中的index.html可以直接访问并显示项目,引入electron仅仅只是给他套了一个桌面应用的壳;第二点是,构建的应用的所有功能实际上是使用react在前端进行实现的,它与本地资源、环境没有任何关联与操作,所以只能算作一次类似helloworld的操作。
我的第一个electron应用是个数据提取工具,我做了如下的规划:
1、主进程,使用node语言,负责本地资源的调用以及后端逻辑的处理;
2、渲染进程,使用react框架,仅负责前端的渲染;
3、数据资源使用sqlite,本地调用快捷方便,同时保证数据安全。
第一步首先用creat-react-app构建react项目,然后在react项目中加入electron的主文件main.js。
构建窗口,在这里,我就不展示main.js的构建窗口的代码了,官方文档写的很清楚,网上也可以查询到。不过在这里说一下第一个小坑,在构建mainWindows的时候,webPreferences中的配置参数,nodeIntegration与 preload两个参数实际上是冲突的,一旦你配置了预加载preload.js文件之后,nodeIntegration:true的配置就会失效。如果你只是简单的使用html页面来负责渲染,可以开启nodeIntegration,会比较方便,如果使用react或者Vue框架的话,建议使用preload配置。此外,还有一个建议,将electron 的main.js文件以及预加载preload.js文件都放在react项目的public文件夹中,这样在构建 react项目时,两个文件就会被直接引入到build文件中,package.json 中添加"main": "public/main.js",便于开发过程中的测试。
//关于node模式和预加载文件的设置
const mainWindow = new BrowserWindow({
width: 800,
height: 1000,
webPreferences: {
nodeIntegration: true,
preload: path.join(__dirname, 'preload.js')
接下来,就是本次制作最大的难点,主线程与渲染线程之间的沟通。
根据官方文档的说明,主线程与渲染线程的沟通主要是通过ipcMain与ipcRenderer进行信息传递的:
//以下代码是写在main.js中
const { BrowserWindow,ipcMain } = require('electron')
function createWindow(
const mainWindow = new BrowserWindow()
//主进程发送信号“message”,传递参数a,a可以是string,array,object,下同
mainWindow.webContents.send('message',a)
//主进程监听到信号“message”,终端打印传递来的参数arg
ipcMain.on('message', (event, arg) => {
console.log(arg)})
//以下代码是在渲染部分
//渲染进程发送信号“message”,传递参数a
ipcRenderer.send('message',a);
//渲染进程监听到信号“message”,终端打印传递来的参数arg
ipcRenderer.on('message',(event,arg)=>{
console.log(arg)})
以上就在主进程与渲染进程形成闭环,进行信息的传递以及函数的调用。主进程的监听与发送相对简单,按照官方文档写在main.js中即可,但是想在react中调用ipcRenderer,实现的过程颇为复杂。
我首先在react中尝试使用
const {ipcRenderer } = require('electron');
react报错。
然后尝试改为import的方式:
import {ipcRenderer } from 'electron'
依旧react报错。
只能继续寻找解决方案。我在网上搜索到了一个方法,然后进行尝试。
const electron = window.require('electron');
const {ipcRenderer} = electron;
依旧报错。但是这个方法给了我灵感,调用window,或许有想不到的收获。
查找官方文档,然后研究了github中electron-react-boilerplate模板,在官网上有这么一段代码引起了我的注意(如下所示,在electron-react-boilerplate上也有类似的代码),我就在preload.js加入了该段代码进行测试,而后在react中加入console.log(window)的语句。
preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electron', {
setTitle: (title) => ipcRenderer.send('set-title', title)
npm start启动react,但结果依然不尽如人意,并没有什么特别的发现。就在这时,我突然意识到一个问题,单单调用react,electron实际并未介入其中。只能将react打包,然后在build文件中测试electron,查看后台,我在输出window中看到了electron和下一层的setTitle,豁然开朗。然后我就在react的代码中加入了如下代码,再次打包,测试electron,在主进程可以监听“set-title”可以获取到a参数,至此,主进程与react负责的渲染进程的通信打通了。
window.electron.setTitle(a)// a为传递的参数
而后又遇到一个问题,就是在开发的时候打包进行测试,有点麻烦了。于是我参考了网上的一个方法,将主进程中的mainWindow.loadFile("index.html") 改为
mainWindow.loadURL("127.0.0.1:3000") //127.0.0.1:3000 react的测试地址
然后先用npm start启动react ,注意,由于react框架中引入了electron模块的内容,所以在单独启用react项目时,是不会显示任何东西的,会报错“ Cannot read properties of undefined (reading '函数名')”,在preload中定义的函数由于没有启动electron,会显示未定义。然后在用electron . 启动electron的测试,在应用中页面呈现,功能加载,并且修改代码之后保存,页面也会重新加载,便于开发维护。
初次开发的整体思路大致如此,最后,再写一个小坑,解决了之后可以让代码更方便简洁。关于ipcRenderer函数的写法,如ipcRenderer.send()函数有两个参数,第一个是信号的名称,第二个是传递的参数,preload.js作如下修改:
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electron', {
setTitle: (message,obj) => ipcRenderer.send(message, obj)
recieveTitle:(message,obj) => ipcRenderer.on(message, obj)
这样在react中使用ipcRenderer的时候,只需要调用setTitle()和recieveTitle()两个参数就可以了,通过不同的信号message实现不同的传参调用。
软件制作的难点就是以上这些了,解决了这些问题,其他的就是常规的前后端开发。最后再上一次软件截图:
欢迎来到Electron的第二期入门教程,上一期我们从零开始编写并发布了第一个简单的windows桌面应用程序,对electron的项目结构等也有了基本了解。这一期开始,会继续深入地学习Electron的其他知识点,这一节概念性的东西会比较多,虽然后面也有实操的点,但重在理解。上一节写preload.js的时候,就提到过进程相关的概念,但是并没有详细地去学习。Electron继承了Chromium的多进程架构,这使得该框架架构与现代web浏览器非常相似。
✧ 为什么不是单个进程?
Web浏览器是非常复杂.
//国内设置淘宝npm的mirror,否则可能因为无法获取资源而失败
npm config set ELECTRON_MIRROR http://npm.taobao.org/mirrors/electron/
// windows上如果要安装x86这里就设置ia32, 否则默认x64
npm i electron@9.1.2 --arch=ia32 --save-dev
二.创建工程:
// 安装脚手架
npm install create-electron-app -g
Uncaught ReferenceError: process is not defined
Uncaught ReferenceError: require is not defined
Uncaught ReferenceError: module is not defined
ERROR TypeError: Cannot read properties of undefined (reading 'NormalModule')
TypeError: Cannot read properties of undefined (reading 'NormalModule')
at VueLoaderPlugin.apply (E:\aqy-app\node_modules\vue-loader-v16\dist\pluginWebpack5.js:44:47)
然后去网上找了一些文章看了看貌似是版本的问题,我用的electron是@v16.0.4, 而remote在electron12的时候废弃了remote模块,所以需要我们自己安装remote包。
1.首先在项目根目录下安装@electron/remote包:
我们来看一下如何自定义一个更加有(gao)意(da)思(shang)的标题栏,例如网易云音乐这种
首先我们要把默认的标题栏删掉,找到主进程中创建窗体部分,new BrowserWindow时添加参数frame: false即可
mainWindow = new BrowserWindow...
// 在 macOS 上,当应用处于激活状态时,点击 Dock 图标会重新创建一个窗口
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
然后,在你的项目文件夹中创建一个名为 index.html 的文件,输入以下代码:
```html
<!DOCTYPE html>
<meta charset="UTF-8">
<title>Hello World!</title>
</head>
<h1>Hello World!</h1>
</body>
</html>
最后,在终端中输入以下命令启动应用:
npm start
这样,你就可以在浏览器窗口中看到一个简单的“Hello World!”页面了。