Electron + React + Antd 初始化配置
# 初始化
# React
初始化 React
npm init vite
✔ Project name: … dove
✔ Select a framework: › React
✔ Select a variant: › JavaScript
Scaffolding project in /.../dove...
Done. Now run:
cd dove
npm install
npm run dev
2
3
4
5
6
7
8
9
10
11
12
安装路由器
npm install --save react-router-dom
# Electron
安装 electron
npm install --save-dev electron
修改 package.json
- 增加或修改 main 并指向到 main.js,以便让 electron 找到正确的 js 入口文件
- 将 type 从 module 改为 commonjs
{
"main": "main.js",
"type": "commonjs",
}
2
3
4
5
安装打包工具,可以自动将 electron 打包和生成为 .app 并打包成 zip。
npm install --save-dev @electron-forge/cli
设置脚手架
npx electron-forge import
# Antd
npm install --save antd @ant-design/icons
在 src/main.jsx 中导入 ant 样式表
import "antd/dist/reset.css"
# 浏览器测试
为了验证 Antd 是否配置完好,我们将 src/App.jsx 中 .card 中的 button 替换为 Antd 导入的 Button
修改前
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
2
3
修改后
import {Button} from "antd";
<Button type="primary" onClick={() => setCount((count) => count + 1)}>
count is {count}
</Button>
2
3
4
5
运行查看
npm run dev
open http://localhost:5173
2
可以看到是来自 Antd 的按钮效果。
# Electron
编写简单的 main.js 以使用 electron 打开初始化好的项目。
const {app, BrowserWindow} = require("electron")
function createMainWindow() {
let w = new BrowserWindow({
width: 1024,
height: 650,
})
w.loadURL("http://localhost:5173/")
}
app.whenReady().then(() => {
createMainWindow()
// MacOS 在没有窗口可用的情况下激活应用时会打开新的窗口
app.on("activate", createMainWindow)
})
app.on("window-all-closed", () => {
// macOS 应用通常即使在没有打开任何窗口的情况下也继续运行
if (process.platform !== "darwin") {
app.quit()
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 预览
得益于我们刚才的工作,启动 electron 程序将变得非常简单
npm run start
# 如何 Electron 判断是否处于生产环境
electron 官方提供了 isPackaged 属性,可以用做判断
if (app.isPackaged) {
w.openDevTools()
}
2
3
# Electron 预加载脚本
Electron 的主进程是一个拥有着完全操作系统访问权限的 Node.js 环境。出于安全原因,渲染进程默认跑在网页页面上,而并非 Node.js里。为了将 Electron 的不同类型的进程桥接在一起,我们需要使用被称为 预加载 的特殊脚本。
并且从 Electron 20 开始,预加载脚本默认 沙盒化 ,不再拥有完整 Node.js 环境的访问权。 实际上,这意味着你只拥有一个 polyfilled 的 require 函数,这个函数只能访问一组有限的 API。
可用的 API | 详细信息 |
---|---|
Electron 模块 | 渲染进程模块 |
Node.js 模块 | events、timers、url |
Polyfilled 的全局模块 | Buffer、process、clearImmediate、setImmediate |
详细的沙盒教程 https://www.electronjs.org/zh/docs/latest/tutorial/sandbox
# 如何使用预加载脚本
创建主窗口的时候使用 preload 加载 js 文件
let w = new BrowserWindow({
width: 1024,
height: 650,
webPreferences: {
preload: path.join(__dirname, "preload.js"),
},
})
2
3
4
5
6
7
如上面所说,需要进行桥接。所以我们要在 preload.js 中导入 {contextBridge}
const {contextBridge} = require("electron")
contextBridge.exposeInMainWorld("electron", {
hello: name => {
console.log("hello", name)
},
})
2
3
4
5
6
7
此时我们向渲染进行 expose 了一个名为 electron 的对像,它具有一个名为 hello 的方法并接受一个 name 参数。我们可以简单的实现调用它向主进程问好。
windows.electron.hello("react")
# 确认渲染进程已经完成DOM加载
这是一个传统的 javascript 事件,可以在 preload 中确认渲染进程的状态。 DOMContentLoaded 事件将会在 HTML 文档完全解析且所有延迟脚本都已下载并执行时被触发。但它不会等待图像、异步脚本等其他内容完成加载。
window.addEventListener("DOMContentLoaded", (event) => {
console.log("DOM fully loaded and parsed")
})
2
3
# 进程间通信
可以使用 Electron 的 ipcMain 模块和 ipcRenderer 模块来进行进程间通信。但这些模块只能在主进程中使用,而不能在 preload.js 中。
# on/send
这是一种传统通信方式,允许多次提供监听,在 send 时所有监听器都会被调用。只要主进程正在运行,渲染器就可以从多次调用的 ipcMain 通道接收数据。
ipMain.on("listen", () => {
console.log("in listen")
})
ipcRenderer.send("listen")
2
3
4
# handle/invoke
这是一组新的通信API,可以代替即将废弃的 remote 模块的部分功能。与 on/send 几乎没有功能差异。
ipMain.handle("listen", () => {
console.log("in listen")
})
ipcRenderer.invoke("listen")
2
3
4
# 繁琐的通信方法
如此一来,进程间通信就变得非常繁琐,渲染进程希望和主进程通信时在形式上要过两道桥。
例如我们试图在渲染进程中调用 electron 使用本地浏览器打开一个链接,就需要这样写。
preload.js
const {contextBridge, ipcRenderer} = require("electron")
contextBridge.exposeInMainWorld("electron", {
openUrlInBrowser: link => {
ipcRenderer.invoke("open/url-in-browser", link)
},
})
2
3
4
5
6
7
main.js
let {shell, ipcMain} = require("electron")
ipcMain.handle("open/url-in-browser", (e, link) => {
shell.openExternal(link)
})
2
3
4
5
index.html
window.electron.openUrlInBrowser("https://github.com")