前言
Electron是一个跨平台创建桌面应用程序的框架,允许我们使用HTML/CSS/JS去创建跨平台桌面应用程序。随着大前端的发展,当我们去开发Web UI时,会习惯性的使用Webpack等构建工具以及React等钱的MVVM框架去辅助开发。在开发Electron时也是同理,因此本文将介绍如何使用Webpack/React去打包构建整个Electron应用,并使用Electron-builder构建出App。其实社区提供了很多Electron Webpack的脚手架和模版,比如electron-forge
、electron-react-boilerplate
等等,但通过自己的摸索和构建(重复造轮子),能对前端打包构建体系有个更深刻的理解。
目录
- Electron简介
- Electron安装
- 结构设计
- 使用webpack打包主进程和渲染进程
- 使用electron-builder构建应用
- C++模块支持
- Redux + React-router集成
- Devtron辅助开发工具集成
- 总结
- 参考
Electron简介
Electron是使用Web前端技术(HTML/CSS/JavaScript/React等)来创建原生跨平台桌面应用程序的框架,它可以认为是Chromium、Node.js、Native APIs的组合。
Chromium由Google开源,相当于Chrome浏览器的精简版,在Electron中负责Web UI的渲染。Chromium可以让开发者在不考虑浏览器兼容性的情况下去编写Web UI代码。
Node.js是一个 JavaScript 运行时,基于事件驱动、非阻塞I/O 模型而得以轻量和高效。在Electron中负责调用系统底层API来操作原生GUI以及主线程JavaScript代码的执行,并且 Node.js中常用的utils、fs等模块在 Electron 中也可以直接使用。
Native APIs是系统提供的GUI功能,比如系统通知、系统菜单、打开系统文件夹对话框等等,Electron通过集成Native APIs来为应用提供操作系统功能支持。
与传统Web网站不同,Electron基于主从进程模型,每个Electron应用程序有且仅有一个主进程(Main Process),和一个或多个渲染进程(Renderer Process),对应多个Web页面。除此之外,还包括GUP进程、扩展进程等其他进程。
主进程负责窗口的创建、进程间通信的协调、事件的注册和分发等。渲染进程负责UI页面的渲染、交互逻辑的实现等。但在这种进程模型下容易产生单点故障问题,即主进程崩溃或者阻塞将会导致整个应用无法响应。
Electron安装
在安装Electron的过程中遇到最大的问题可能就是下载Electron包时出现网络超时(万恶的墙),导致安装不成功。
解决方法自然是使用镜像,这里我们可以打开node_modules/@electron/get/dist/cjs/artifact-utils.js
,找到处理镜像的方法mirrorVar
function mirrorVar(name, options, defaultValue) {
// Convert camelCase to camel_case for env var reading
const lowerName = name.replace(/([a-z])([A-Z])/g, (_, a, b) => `${
a}_${
b}`).toLowerCase();
return (process.env[`NPM_CONFIG_ELECTRON_${
lowerName.toUpperCase()}`] ||
process.env[`npm_config_electron_${
lowerName}`] ||
process.env[`npm_package_config_electron_${
lowerName}`] ||
process.env[`ELECTRON_${
lowerName.toUpperCase()}`] ||
options[name] ||
defaultValue);
}
以及获取下载路径getArtifactRemoteURL
方法
async function getArtifactRemoteURL(details) {
const opts = details.mirrorOptions || {
};
let base = mirrorVar('mirror', opts, BASE_URL); // ELECTRON_MIRROR 环境变量
if (details.version.includes('nightly')) {
const nightlyDeprecated = mirrorVar('nightly_mirror', opts, '');
if (nightlyDeprecated) {
base = nightlyDeprecated;
console.warn(`nightly_mirror is deprecated, please use nightlyMirror`);
}
else {
base = mirrorVar('nightlyMirror', opts, NIGHTLY_BASE_URL);
}
}
const path = mirrorVar('customDir', opts, details.version).replace('{
{ version }}', details.version.replace(/^v/, '')); // ELECTRON_CUSTOM_DIR环境变量,并将{
{version}}替换为当前版本
const file = mirrorVar('customFilename', opts, getArtifactFileName(details));
// Allow customized download URL resolution.
if (opts.resolveAssetURL) {
const url = await opts.resolveAssetURL(details);
return url;
}
return `${
base}${
path}/${
file}`;
}
可以看到可以定义挺多环境变量来指定镜像,比如ELECTRON_MIRROR、ELECTRON_CUSTOM_DIR等等,这其实在官方文档中也有标明
Mirror
You can use environment variables to override the base URL, the path at which to look for Electron binaries, and the binary filename. The URL used by
@electron/get
is composed as follows:url = ELECTRON_MIRROR + ELECTRON_CUSTOM_DIR + '/' + ELECTRON_CUSTOM_FILENAME
For instance, to usethe China CDN mirror:
ELECTRON_MIRROR="https://cdn.npm.taobao.org/dist/electron/" ELECTRON_CUSTOM_DIR="{ { version }}"
因此在下载Electron时只需要添加了两个环境变量即可解决网络超时(墙)的问题
ELECTRON_MIRROR="https://cdn.npm.taobao.org/dist/electron/" ELECTRON_CUSTOM_DIR="{
{ version }}" npm install --save-dev electron
安装完electron后,可以尝试写一个最简单的electron应用,项目结构如下
project
|__index.js # 主进程
|__index.html # 渲染进程
|__package.json #
对应的主进程index.js
部分
const electron = require('electron');
const {
app } = electron;
let window = null;
function createWindow() {
if (window) return;
window = new electron.BrowserWindow({
webPreferences: {
nodeIntegration: true // 允许渲染进程中使用node模块
},
backgroundColor: '#333544',
minWidth: 450,
minHeight: 350,
height: 350,
width: 450
});
window.loadFile('./index.html').catch(console.error);
window.on('close', () => window = null);
window.webContents.on('crashed', () => console.error('crash'));
}
app.on('ready', () => createWindow());
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', createWindow)
对应的渲染进程index.html
部分
<!DOCTYPE>
<html lang="zh">
<head><title></title></head>
<style>
.box {
color: white;font-size: 20px;text-align: center;}
</style>
<body>
<div class="box">Hello world</div>
</body>
</html>
向package.json
中添加运行命令
{
...,
"main": "index.js",
"script": {
"start": "electron ."
},
...
}
npm run start
运行,一个最简单的electron应用开发完成。
项目结构
Electron项目通常由主进程和渲染进程组成,主进程用于实现应用后端,一般会使用C++或rust实现核心功能并以Node插件的形式加载到主进程(比如字节跳动的飞书、飞聊的主进程则是使用rust实现),其中的JavaScript部分像一层胶水,用于连接Electron和第三方插件,渲染进程则是实现Web UI的绘制以及一些UI交互逻辑。主进程和渲染进程是独立开发的,进程间使用IPC进行通信,因此对主进程和渲染进程进行分开打包,也就是两套webpack配置,同时为区分开发环境和生产环境,也需要两套webpack配置。此外在开发electron应用时会有多窗口的需求,因此对渲染进程进行多页面打包,整体结构如下。
project
|__src
|__main # 主进程代码
|__index.ts
|__other
|__renderer # 渲染进程代码
|__index # 一个窗口/页面
|__index.tsx
|__index.scss
|__other
|__dist # webpack打包后产物
|__native # C++代码
|__release