本文介绍如何构建一个electron应用,适用于有HTML/CSS/JavaScript基础的人阅读,在开始前需要先在电脑上安装Node.js。
Electron介绍
Electron 是由 Github开发的开源框架,它允许开发者使用Web技术来开发跨平台的桌面应用。著名项目包括GitHub的Atom和微软的Visual Studio Code。
Electron架构的核心由三部分组成:
- Chromium: 为electron提供了强大的UI能力,可以不考虑兼容性的情况下,利用强大的Web生态来开发界面
- Node.js:让electron有了底层的操作能力,比如文件的读写,甚至是集成C++等等操作,并可以使用大量开源的 npm 包来完成开发需求
- Native API:Native API让electron有了跨平台和桌面端的原生能力,比如说它有统一的原生界面,窗口、托盘这些
主进程和渲染进程
electron是多进程架构,在开始项目搭建之前,先来了解下electron的两个核心概念:主进程和渲染进程
主进程
主进程负责创建和管理BrowserWindow实例以及各种应用程序事件。它还可以执行诸如注册全局快捷方式,创建系统菜单和对话框,响应自动更新事件等操作。应用程序的入口点将指向将在主进程中执行的JavaScript文件。
一个项目有且只有一个主进程
渲染进程
渲染过程负责运行应用程序的用户界面。
每创建一个窗口都会创建一个渲染进程;并且每个渲染进程都是独立的。
项目工程搭建
接下来开始搭建electron项目工程
使用quick-start创建项目
为了简化步骤,可以使用 quick-start 来搭建electron项目
// 第一步:clone electron-quick-start
git clone https://github.com/electron/electron-quick-start
// 第二步:安装依赖
cd electron-project && npm install
// 运行项目
npm run start
搭建过程中可能会遇到安装依赖失败的问题,具体解决方法可以文末
项目结构介绍
├── index.html
├── main.html
├── package.json
├── preload.js
上图是刚刚创建的项目的目录结构,接下来介绍下electron应用中最主要的三种文件
package.json(元数据)
配置文件。配置应用的相关信息及工程依赖。其中main字段定义了应用的启动入口,在此项目中,入口文件为src/main.js
mian.js (启动文件)
运行在项目主进程中。在此文件中启动应用,创建浏览器,加载页面。
const {app, BrowserWindow} = require('electron')
const path = require('path')
function createWindow () {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
mainWindow.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
app
代表着整个应用,用app.on
监听应用的状态,当达到ready状态后,使用BrowserWindow
(electron提供的模块)创建了一个宽800,高600的窗口,再使用loadFile
,在窗口中加载 index.html
文件。
webPreferences
是BrowserWindow
的属性,用来设置网页功能。preload
是webPreferences
属性的参数,在页面运行其他脚本之前预先加载指定的脚本,无论页面是否集成Node, 此脚本都可以访问所有Node API 脚本路径为文件的绝对路径。
可以在createWindow方法最后添加mainWindow.webContents.openDevTools()
代码,表示打开控制台
index.html
运行在项目渲染进程中。该文件为项目展示的界面,类似于移动端开发的h5界面。
项目开发
主进程和渲染进程的通信
electron 可以使用node.js的api和Native API,但是electron不建议直接在渲染进程(即界面)中直接使用,需要通过两个进程的通信,在主进程中完成操作。
考虑到在网页中直接调用原生的 GUI 容易造成资源溢出,这很危险,开发者不能这么使用。如果开发者想要在网页上执行 GUI 操作,必须要通过渲染器进程和主进程的通信实现。
主进程和渲染进程的通信可以使用ipc模块来实现,以实现以下需求为例,简单介绍如何实现渲染器进程和主进程的通信实现。
页面上有一个按钮和一个输入框,当点击按钮之后,向主进程发送了一个 write-file 的消息,当主进程接收到消息之后,在安装目录下创建一个叫 hello.txt的文件,并写入输入框内的内容。文件生成后发送成功的消息给渲染进程,弹出提示告诉用户已完成。
更改preload.js
在preload.js文件中,通过contextBridge模块,将ipcRenderer模块的api暴露给渲染器
contextBridge Create a safe, bi-directional, synchronous bridge across isolated contexts
// preload.js
const ipcRenderer = require('electron').ipcRenderer
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('ipcRenderer', {
on: (eventName, callback) => {
ipcRenderer.on(eventName, callback)
},
once: (eventName, callback) => {
ipcRenderer.once(eventName, callback)
},
send: ipcRenderer.send,
})
ps:
在低版本的electron中,直接赋值在window上
window.ipcRenderer = require('electron').ipcRenderer
渲染进程 => 主进程
- 在index.html渲染进程中添加一个input输入框和button按钮,在点击按钮时获取输入框的内容,并使用ipcRenderer.send方法发送write-file事件
ipcRenderer 是一个 EventEmitter 的实例。 可以使用它提供的一些方法从渲染进程 (web 页面) 发送同步或异步的消息到主进程。 也可以接收主进程回复的消息。
// index.html
// html部分
<input type="text" id="input">
<button id="button">say hi</button>
// js部分
document.getElementById('button').onclick = function () {
const content = document.getElementById('input').value
window.ipcRenderer.send('write-file', {
content: content,
});
};
- 在main.js主进程中使用ipcMain.on监听write-file事件,接受到信息后使用node的fs模块生成并写入hello.txt文件
ipcMain 是一个 EventEmitter 的实例。 当在主进程中使用时,它处理从渲染器进程(网页)发送出来的异步和同步信息。 从渲染器进程发送的消息将被发送到该模块。
const { app, BrowserWindow, ipcMain } = require('electron');
const fs = require('fs')
ipcMain.on('write-file', (evt, data) => {
fs.writeFileSync('./hello.txt', data.content, 'utf-8')
})
主进程 => 渲染进程
- 在文件生成成功后,使用当前窗口 BrowserWindow 实例的webContents属性的send方法,发送file-complete 事件
webContents是一个EventEmitter. 负责渲染和控制网页, 是 BrowserWindow 对象的一个属性。
+ let mainWindow
const createWindow = () => {
+ mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
});
mainWindow.loadFile(path.join(__dirname, 'index.html'));
mainWindow.webContents.openDevTools();
};
ipcMain.on('write-file', (evt, data) => {
fs.writeFileSync('./hello.txt', data.content, 'utf-8')
+ mainWindow.webContents.send('write-complete', { status: true })
})
- 在index.html渲染进程使用ipcRenderer.on方法监听事件
document.getElementById('button').onclick = function () {
const content = document.getElementById('input').value
window.ipcRenderer.send('write-file', {
content: content,
});
+ window.ipcRenderer.once('write-complete', (event, data) => {
+ if (data.status) alert('文件生成成功')
+ });
};
打包及自动更新
应用打包
使用electron-builder来打包
安装依赖
npm install electron-builder --save-dev
配置build属性
在package.json文件中,添加build属性
"build": {
"appId": "com.test.electron", // 包名
"productName": "electron-project", // 项目名,也是生成包的前缀名
"mac": { // mac平台相关配置
"icon": "public/icon.png", // mac应用图标,最小为512x512
"target": [ "dmg", "zip" ]
},
"dmg": {
"window": { // dmg安装器窗口设置
"x": 200,
"y": 200,
"width": 400,
"height": 400
}
},
"win": { // windows平台相关配置
"icon": "public/icon.png" // windows应用图标,最小为256x256
},
"nsis": { // 安装过程的配置
"oneClick": false,
"allowToChangeInstallationDirectory": true, //允许修改安装目录
"createDesktopShortcut": "always", //创建桌面图标
"createStartMenuShortcut": false, //创建开始菜单图标
"installerIcon": "", // 安装图标
"uninstallerIcon": "" // 卸载图标
}
},
配置打包命令
在package.json文件中添加script命令
"script": {
"start": "electron .",
+ "build": "electron-builder"
}
在不同平台的环境下运行npm run build
打包,成功后安装包在dist文件夹下
在打包过程中可能会遇到下载文件失败的问题,具体解决方法看文末
自动更新
可以使用electron-builder来实现自动更新
安装electron-builder
npm install electron-builder --save-dev
配置publish
配置publish
字段,在打包后生成latest.yml
文件,程序更新依赖这个文件做版本判断
latest.yml文件是打包过程生成的文件,为避免自动更新出错,打包后禁止对latest.yml文件做任何修改。如果文件有误,必须重新打包获取新的latest.yml文件
// package.json
"build": {
+ "publish": [
+ {
+ "provider": "generic",
+ "url": ""
+ }
+ ],
}
主进程代码
在main.js中添加以下autoUpdater代码
// main.js
const { autoUpdater } = require('electron-updater')
function createWindow () {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
mainWindow.loadFile(path.join(__dirname, 'index.html'))
mainWindow.webContents.openDevTools()
mainWindow.webContents.on('did-finish-load', function () {
setTimeout(() => {
updateApp()
}, 5000)
})
}
function sendUpdateMessage(message, data) {
mainWindow.webContents.send("message", { message, data });
}
function updateApp () {
let url = 'http://127.0.0.1:8080/' + process.platform // 安装包所在服务器地址,本地测试可以使用http-server搭建静态服务器
autoUpdater.setFeedURL(url); // 设置更新服务器的地址
autoUpdater.on("error", function(message) { // 报错
sendUpdateMessage("error", message);
})
autoUpdater.on("checking-for-update", function(message) { // 检查更新事件
sendUpdateMessage("checking-for-update", message);
})
autoUpdater.on("update-available", function(message) { // 有需要更新的版本
sendUpdateMessage("update-available", message);
})
autoUpdater.on("update-not-available", function(message) { // 没有需要更新的版本
sendUpdateMessage("update-not-available", message);
})
autoUpdater.on("download-progress", function(progressObj) { // 更新下载进度事件
sendUpdateMessage("downloadProgress", progressObj);
})
autoUpdater.on("update-downloaded", function( // 下载成功事件
event,
releaseNotes,
releaseName,
releaseDate,
updateUrl,
quitAndUpdate
) {
ipcMain.on("updateNow", (e, arg) => {
// 停止当前程序并安装
autoUpdater.quitAndInstall();
});
sendUpdateMessage("isUpdateNow", null);
})
autoUpdater.checkForUpdates(); // 执行检查更新
}
渲染进程代码
在渲染进程的js文件中添加以下代码
window.ipcRenderer.on("message", (event, { message, data }) => {
switch (message) {
case "isUpdateNow":
if (confirm("现在更新?")) {
ipcRenderer.send("updateNow");
}
break;
default:
break;
}
});
上传应用
更新packge.json文件中的版本号,打包后将安装包和yml文件(MAC下是latest-mac.yml,zip和dmg文件;Windows下是latest.yml和exe文件)放在服务器对应平台的目录下
-
mac
-
windows
打开低版本的应用,electron-updater会通过对应url下的yml文件检查更新
问题
依赖安装失败问题解决
在创建项目的过程中,安装依赖可能会报错,可以尝试使用以下方式解决
1. 设置国内electron镜像地址
mac直接运行以下命令
export ELECTRON_MIRROR="https://npm.taobao.org/mirrors/electron/"
window需要添加环境变量 ELECTRON_MIRROR
值为 https://npm.taobao.org/mirrors/electron/
2. 重新npm install
electron-builder打包失败
electron-builder 在打包时会检测cache中是否有electron 包,如果没有的话会从github上拉去,在国内网络环境中拉取的过程大概率会失败,所以你可以自己去下载一个包放到cache目录里
各个平台的目录地址
MacOs: ~/Library/Caches/electron
Window: %LOCALAPPDATA%/electron/Cache or ~/AppData/Local/electron/Cache/
Linux: $XDG_CACHE_HOME or ~/.cache/electron/