Overview

基于 Electron + Vite 开发一款日常使用工具软件.
基于 Electron + Webpack + React 开发一款日常使用工具软件

技术方案

脚手架模板

基于 Vite + React 的模板方案,GitHub - electron-vite/electron-vite-react: Electron + Vite + React + Sass boilerplate.
原因是模板内容比较少,没有复杂的设计,方便后期技术迭代,同时也想体验下 Vite

跨线程共享 Redux

使用 electron-redux 来实现应用的 Redux Store 跨线程共享,具体看这里Electron Redux 状态共享.

替换 Vite 使用 Webpack 开发

🎯 基于 electron-react-boilerplate

项目配置

Main 使用 ES Module

electron 依然不支持直接使用 ESM,但是一些依赖包在逐渐放弃 CommonJs 改为使用纯 ESM 包,例如 get-portgoogle/zx 等,为了在主进程中能够使用 ESM,在electron-react-boilerplate样板的基础上针对main.ts添加
webpack 打包,将 ESM 打包为 Commonjs 使用

主进程热重载

electronmon 监听主进程文件变化,对主进程代码进行热更新,类似于 nodemon,使用方式是在package script 替换electron . 来启动应用。

为 main 添加 webpack 打包之后,原来的 electronmon 监听 main.ts修改并热重载应用的方式就不适用了,之前的 main.ts使用的是 ts-node进行构建的,需要修改electronmon的使用方式,改为启动 weboack 构建之后的文件,并监听。

需要注意的是mainpreload一样,需要在单独的进程中去启动监听,否则webpack watch 会阻塞后续任务的执行。

electronmon 在使用时有可能出现 main watch 进程没有杀死的情况,导致多个进程存在,当输出文件变动时,会唤起多个 app 实例,有时候会出现这种情况,可选则替代方案 webpack-electron-reload;

 
"electronmon": {
 
"patterns": [
 
"!**/**",
 
// 监听主进程文件
 
"release/app/dist/mainDev/main.js",
 
// 监听 preload 文件
 
".erb/dll/preload.js"
 
],
 
"logLevel": "quiet"
 
},
 

webpack-electron-reload 方案:
webpack.main.dev.ts 中添加一下代码:

 
const ElectronReloadPlugin = require('webpack-electron-reload')({
 
path: path.join(__dirname, '../../release/app/dist/mainDev/main.js'),
 
});
 
  
 
plugins: [
 
ElectronReloadPlugin(),
 
],
 

preload 文件热更新

通过 webpackwatch 模式监听 preload.ts 文件,当文件发生变化时,watch 模式下的preload.ts 文件会实时更新主进程的 preload.js文件,触发electronmon 更新主进程。

dev script

ts-node ./.erb/scripts/check-port-in-use.js && npm run dev:renderer
执行端口检查脚本,和 dev:renderer

package

  • registry-js: windows 读取注册表信息

  • electron-redux:electron main 和 renderer 共享 redux

  • @reduxjs/toolkit:redux 工具包
    工程类:

  • BundleAnalyzerPlugin 打包分析

  • detect-port 检查端口

  • @pmmmwh/react-refresh-webpack-plugin 用于为 React 组件启用“快速刷新”(也称为热重载)

  • concurrently 同步运行command

  • electron-builder 打包electron应用

  • husky git钩子

项目工程

  • 添加 package-lock.json、yarn-lock.json gitgnore,仅使用 pnpm
  • 梳理 package.json 配置,移除无用的配置
  • 梳理 webpack 配置,移除无用的配置
  • 删除项目自带 Eslint Config,配置自己的规则,package.json 中的相关依赖需要移除
  • electron main进程中通过webpack或者其他方式支持 alias issues
  • 通过tsconfig-paths-webpack-plugintsconfig-paths 插件自动同步tsconfig.json pathswebpack alias link

Xtrem

使用 Xtrem实现终端功能

FAQ

FAQ

如何在主线程和渲染线程中使用 NodeJs/Electron API

正常来说在渲染线程中是没有办法使用 NodeJs API的,因为渲染线程属于浏览器环境,一般的做法是通过 Electron PreloadElectron API 注入到渲染进程在渲染进程中使用 window.xxx 来使用,具体的方法为:

  1. new BrowserWindow() 是配置 webPreferences 参数
new BrowserWindow({
	webPreferences: {
		preload,
		nodeIntegration: false,
		contextIsolation: false
	}
})
  • nodeIntegration: 是否启用Node integration, 如果想要在渲染进程中使用NodeJs/Electorn模块
  • contextIsolation: 是否在独立 JavaScript 环境中运行 Electron API和指定的preload 脚本.默认为 true
  1. preload文件中加入下面代码
// electron/preload.ts
window.ipcRenderer = require('electron').ipcRenderer;

或者

// electron/preload.ts
import { contextBridge, ipcRenderer } from 'electron'
contextBridge.exposeInMainWorld('ipcRenderer', ipcRenderer)

如果使用Typescript的话需要加入 Global 类型:

 
declare global {
	interface Window {
			ipcRenderer: IpcRenderer;
			require: (module: 'electron') => {
			ipcRenderer: IpcRenderer;
		};
	}
}

在渲染进程中使用:

// main.tsx
window.ipcRenderer.send('main-process-message', data)

相关iuess:

如果想直接在渲染进程中使用NodeJs、Electron API,也是可以的,但是需要注意

这样做并不安全。

因为使用的是 electron-vite-react 模板,它提供了更便捷的方式让我们在渲染进程中使用Node,这里需要安装两个个包:

  1. 在主线程 main.ts 中配置
new BrowserWindow({
	webPreferences: {
		preload,
		// 是否启用Node integration, 如果想要在渲染进程中使用NodeJs/Electorn模块
		// https://github.com/electron-vite/electron-vite-react/issues/50
		nodeIntegration: true,
		// 是否在独立 JavaScript 环境中运行 Electron API和指定的preload 脚本.默认为 true
		contextIsolation: false,
	},
});
  1. 配置vite.config.ts
// vite.config.ts
import renderer from 'vite-plugin-electron-renderer';
 
export default defineConfig({
	plugins: [
		electron({
				include: ['electron', 'preload'],
				transformOptions: {
				sourcemap: !!process.env.VSCODE_DEBUG,
			},
		}),
		renderer({
			// 在主进程中开启 Node.js 集成
			nodeIntegration: true,
		}),
	],
})
  1. 在渲染进程中使用
import { ipcRenderer } from 'electron';
 
ipcRenderer.send('message', data);

测试代码:

import { lstat } from 'fs/promises';
import { cwd } from 'process';
import { ipcRenderer } from 'electron';
 
ipcRenderer.on('main-process-message', (_event, ...args) => {
	console.log('[Receive Main-process message]:', ...args);
});
 
lstat(cwd())
	.then(stats => {
		console.log('[fs.lstat]', stats);
	})
	.catch(err => {
		console.error(err);
	});

相关 iuess:

无法在主线程中使用 ESM 包

官方关于 Electron支持ESM的讨论:Support Node’s ES Modules · Issue #21457 · electron/electron · GitHub

在 Electron 中无法直接使用 ESM模块的包,例如get-port这个包,它的package.json 下标明了这个包是一个 esm 包:

type: 'module'

在使用的时候会报错

Error [ERR_REQUIRE_ESM]: require() of ES Module D:\code\github\tools\node_modules\.pnpm\g[email protected]\node_modules\get-port\index.js from D:\code\github\tools\dist-electron\electron\main\ipc\ipc.action.js not supported.
Instead change the require of index.js in D:\code\github\tools\dist-electron\electron\main\ipc\ipc.action.js to a dynamic import() which is available in all CommonJS modules.
    at c._load (node:electron/js2c/asar_bundle:5:13339)
    at Object.<anonymous> (D:\code\github\tools\dist-electron\electron\main\ipc\ipc.action.js:29:31)
    at c._load (node:electron/js2c/asar_bundle:5:13339)
    at Object.<anonymous> (D:\code\github\tools\dist-electron\electron\main\ipc\ipc.listen.js:3:19)
    at c._load (node:electron/js2c/asar_bundle:5:13339)
    at Object.<anonymous> (D:\code\github\tools\dist-electron\electron\main\index.js:25:18)
    at c._load (node:electron/js2c/asar_bundle:5:13339)
    at loadApplicationPackage (D:\code\github\tools\node_modules\.pnpm\e[email protected]\node_modules\electron\dist\resources\default_app.asar\main.js:121:16)
    at Object.<anonymous> (D:\code\github\tools\node_modules\.pnpm\e[email protected]\node_modules\electron\dist\resources\default_app.asar\main.js:233:9)
    at c._load (node:electron/js2c/asar_bundle:5:13339)
    at Object.<anonymous> (node:electron/js2c/browser_init:189:3102)
    at ./lib/browser/init.ts (node:electron/js2c/browser_init:189:3306)
    at __webpack_require__ (node:electron/js2c/browser_init:1:128)
    at node:electron/js2c/browser_init:1:1200
    at node:electron/js2c/browser_init:1:1267

没找到比较好的办法来实现在Electron 中使用 ESM模块,最简单的就是“换个包”或者使用支持CommonJS的老版本。

因为使用的是 vite-electron-react 这个模板,所以找了下发现作者似乎提供了将 ESMCommonJS 模块的功能:

需要下载安装这个包,vite-plugin-esmodule,然后根据文档配置,但是经过测试,发现并没有生效,但是在.vite-plugin-esmodule 文件夹下已经有了转换后的模块。其中 index.js 是 CommonJS模块,render是给渲染进程用的ESM模块,

但是主进程依然使用不了,打包出来的代码,引用的还是 node_modules/get-potr 下的index,并没有引用 .vite-plugin-esmodule 下的 index, 看了下 vite-plugin-esmodule 源码,发现环境判断有点问题
isElectronRendererServe 的值为 true,会设置 replacementrenderid, 也就是 ESM 模块,尝试将 isElectronRendererServe 手动设置为 false,依然会有问题,不知道是不是跟 pnpm 有关系。

目前还没找到比较好的方法,可以看下这个资料:

算鸟,Vite开发Electron坑太多了,还是去用Webpack吧

Node spawn 执行Shell失败问题

使用 node spawn 执行 exe 文件时,有一些程序会直接报错 -4092

const ls = spawn('D:\\5eplay\\5EClient\\5EClient.exe');
const ls = spawn('D:\\aDrive\\aDrive.exe');

上面两行代码,第二行能够正常执行,打开app,第一行运行时会报错,解决办法是给 spawn 方法的 options 参数添加 shell 配置,但是为啥这样写可以运行,没弄明白..

const ls = spawn('D:\\5eplay\\5EClient\\5EClient.exe', [], { shell: true });

shell配置的含义