Electron入门教程

Electron是由Github开发,用HTML,CSS和JavaScript来构建跨平台桌面应用程序的一个开源库。 Electron通过将 ChromiumNode.js合并到同一个运行时环境中,并将其打包为Mac,Windows和Linux系统下的应用来实现这一目的。

相信大部分前端同学都听说或者了解过NodeJs 它是一个基于Chrome V8 引擎的JavaScript运行时, 而 Chromium 是Google为发展自家浏览器Chrome而开启的开源浏览器项目,可以看成是是Chrome的先行版。大家较为熟悉的VS Code 和 Atom就是使用Electron来完成的。

从开发的角度来看, Electron application 本质上是一个 Node. js 应用程序。可以让前端开发者让你使用纯 JavaScript 调用丰富的原生操作系统APIs来创造桌面应用~

Quick Start

确认你的网络可以访问github , 若访问受限参考 安装指南 来了解如何用代理、镜像和自定义缓存

你肯定已经已经安装好git和node了,那么只要:

1
2
3
4
5
6
# 官网已经有 electron-quick-start 仓库克隆下来
git clone https://github.com/electron/electron-quick-start
# 进入文件夹
cd electron-quick-start
# 安装依赖包并运行
npm install && npm start

然后你的第一个桌面应用就开启了!

若你跟着一起实践到这里,肯定会发现界面有些许不同?我这里多了个调试界面,那么来看下代码看看。在Electron中main.js是入口文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//main.js
const { app, BrowserWindow } = require('electron')

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win

function createWindow () {
// 创建浏览器窗口。
win = new BrowserWindow({ width: 800, height: 600 })

// 然后加载应用的 index.html。对应的index.html 就是初始界面。
win.loadFile('index.html')

// 打开开发者工具
win.webContents.openDevTools()

//关于win 窗口的生命周期我们之后再研究……
// 当 window 被关闭,这个事件会被触发。
win.on('closed', () => {
// 取消引用 window 对象,如果你的应用支持多窗口的话,
// 通常会把多个 window 对象存放在一个数组里面,
// 与此同时,你应该删除相应的元素。
win = null
})
}
//关于 app 主进程的生命周期我们之后再研究……
// Electron 会在初始化后并准备
// 创建浏览器窗口时,调用这个函数。
// 部分 API 在 ready 事件触发后才能使用。
app.on('ready', createWindow)

// 当全部窗口关闭时退出。
app.on('window-all-closed', () => {
// 在 macOS 上,除非用户用 Cmd + Q 确定地退出,
// 否则绝大部分应用及其菜单栏会保持激活。
if (process.platform !== 'darwin') {
app.quit()
}
})

app.on('activate', () => {
// 在macOS上,当单击dock图标并且没有其他窗口打开时,
// 通常在应用程序中重新创建一个窗口。
if (win === null) {
createWindow()
}
})

// 在这个文件中,你可以续写应用剩下主进程代码。
// 也可以拆分成几个文件,然后用 require 导入。

与现有React工程结合

迫于不会Vue ,在这里介绍下React工程如何和Electron结合起来。

偷懒就直接使用 create-react-app 来创建react工程:

1
2
3
4
5
#若已安装 请忽略
npm install -g create-react-app
create-react-app react-electron
cd react-electron
npm start

添加 Electron 配置并启动

一、 安装 Electron包

1
2
# 在 react-electron 目录下安装 Electron 包
npm install -save electron

二、 添加main.js

在 react-electron 目录下添加main.js ,直接使用上面 main.js的内容 ,然后

1
2
//win.loadFile('index.html') 这一行替换为:
win.loadFile('http://localhost:3000/')

这样就将入口界面指定到react的初始界面了。

三、 启动Electron!

修改 package.json ,添加 main homepage字段,并添加electron-start命令:

1
2
3
4
5
6
7
8
9
10
11
  "main": "main.js",
"homepage":".",

"scripts": {
"start": "react-scripts start",
"build": "react-scriptsbuild",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject",
"electron": "electron ."
},
}

启动 Electron:

1
2
3
4
# 启动react项目
npm start
# 启动electron
npm run electron

看到一篇 文章 CREATE AN APP WITH ELECTRON AND REACT 写create-react-app和Electron结合的不错,大家也可以参考这个。

四、在react中使用electron

直接在react中使用import electron会产生问题 issues/7300,可以写成这种形式:

1
2
3
const electron = window.require('electron');
const fs = electron.remote.require('fs');
const ipcRenderer = electron.ipcRenderer;

主进程和渲染进程

Electron 与web应用的区别不是很大,在原web应用的基础上添加主线程交互代码后,甚至可以将一个线上web应用迅速的包装成为一个客户端应用! Electron并且内集成了 Nodejs,Nodejs 在主进程和渲染进程中都可以使用,这为我们提供了npm成千上万的模块。

所以个人感觉应用Electron的重要在于理解主进程和渲染进程,和进程间的交互。其他API可以自己花点时间阅读文档。

运行Electron入口文件的进程(也就是上文运行main.js的进程)被称为主进程,它控制着整个 App 的生命周期,从打开到关闭。 它也管理着系统原生元素比如菜单,菜单栏,Dock 栏,托盘等。 主进程负责创建 APP 的每个渲染进程。而且整个 Node API 都集成在里面;

而在主进程创建的一个个web页面(对应上文中的win窗口)也都运行着自己的进程,即渲染进程,渲染进程各自独立,各自拥有自己的生命周期。与主进程不同的是,它能够同时存在多个而且运行在不一样的进程。而且它们也能够被隐藏。在通常的浏览器内,网页通常运行在一个沙盒的环境挡住并且不能够使用原生的资源。 然而 Electron 的用户在 Node.js 的 API 支持下可以在页面中和操作系统进行一些低级别的交互。

进程间通信

Electron提供了几种通信方式:

一 使用ipc-rendereripc-main :

ipc-renderer 和 ipc-main 异步交互:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//在渲染进程中:
const {ipcRenderer} = require('electron')
//发送asynchronous-message事件到主进程
ipcRenderer.send('asynchronous-message', 'ping')
//接收主进程的asynchronous-reply通知
ipcRenderer.on('asynchronous-reply', (event, arg) => {
console.log('asynchronous-reply : args:',arg)
const message = `Asynchronous message reply: ${arg}`
document.getElementById('async-reply').innerHTML = message
})

//在主进程中:
const {ipcMain} = require('electron')
//接收渲染进程的asynchronous-message通知
ipcMain.on('asynchronous-message', (event, arg) => {
//发送asynchronous-reply事件到渲染进程
event.sender.send('asynchronous-reply', {'ping':'pong','num':'1'})
})

ipc-renderer 和 ipc-main 同步交互:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//在渲染进程中:
const {ipcRenderer} = require('electron')

const syncMsgBtn = document.getElementById('sync-msg')

syncMsgBtn.addEventListener('click', () => {
const reply = ipcRenderer.sendSync('synchronous-message', 'ping')
const message = `Synchronous message reply: ${reply}`
document.getElementById('sync-reply').innerHTML = message
})
//在主进程中:
const {ipcMain} = require('electron')

ipcMain.on('synchronous-message', (event, arg) => {
event.returnValue = 'pong'
})

二 在渲染进程使用remote模块

1
2
3
4
5
// 在渲染进程打开提示对话框
const {dialog} = require('electron').remote
dialog.showMessageBox(options, (index) => {
....
})

三 在主进程向渲染进程webContents发送消息

1
win.webContents.send('ping', 'whoooooooh!')

四 渲染进程之间的通信

在两个网页(渲染进程)间共享数据最简单的方法是使用浏览器中已经实现的 HTML5 API。 其中比较好的方案是用 Storage API, localStorage,sessionStorage 或者 IndexedDB。

你还可以用 Electron 内的 IPC 机制实现。将数据存在主进程的某个全局变量中,然后在多个渲染进程中使用 remote 模块来访问它。

1
2
3
4
5
6
7
8
// 在主进程中
global.sharedObject = {
someProperty: 'default value'
}
// 在第一个页面中
require('electron').remote.getGlobal('sharedObject').someProperty = 'new value'
// 在第二个页面中
console.log(require('electron').remote.getGlobal('sharedObject').someProperty)