只需五分钟,就会Electron
。
关于Electron
Electron是由Github开发,用HTML
,CSS
和JavaScript
来构建跨平台桌面应用程序的一个开源库。 Electron通过将Chromium和
Node.js 合并到同一个运行时环境中,打包出可以运行在Mac
,Windows
和Linux
系统下的应用。
核心理念:
- 为了保持Electron的小 (文件体积) 和可持续性 (依赖和API的扩展) ,Electron限制了使用的核心项目的范围。
- Electron只用了
Chromium
的 渲染库 而不是全部。 - Electron所添加的的新特性应主要用于原生API(node.js)。
Chromium是什么?
Chromium是由Google主导开发的网页浏览器,说明白点:
Chromium是Chrome浏览器背后的引擎。
几个无聊的联想
Chromium + Node.js = ?
如果把 浏览器 和 Node.js 联系在一起,一般想到的一个web服务,TCP以及其衍生协议充当了页面
与服务器
通信的桥梁。
Web + Native = ?
浏览器可以调用原生功能,很容易让人联想到Hybrid,web通过自定义的桥接协议
与Native通信,Native拦截请求从而得知web需要调用的功能。
Electron的原理是什么?
在一个Electron桌面应用分成两个进程:
- 主进程 —— MainProcess
- 渲染进程 —— RenderProcess
主进程负责管理原生操作,例如: 文件读写,图形处理,程序开关等,同时还管理渲染进程以及应用的生命周期。
渲染进程通过 Chromium 绘制前端界面,用户可以在页面上触发事件。
那么,Node.js 如何与Chromium通信?
答案是 IPC——进程间通信。
Electron IPC通信模型
Electron IPC提供了基于事件的API,在渲染进程和后台进程中都可以向对方发送事件,也可以在事件处理函数中通过发送的新的事件回复对方:
- 通过
EventEmitter
实例向对方发送事件 - 发送事件需要制定对方句柄,不支持全局广播
- 支持同步事件和异步事件
创建一个桌面应用
Step 1. 环境
- Node.js = 8.x | 9.x
Step 2. 安装electron包
npm install electron --save-dev --save-exact
如果下的慢,可以尝试淘宝镜像。
Step 3. 项目结构
.
└── app
├── main.js
├── index.js
├── index.html
├── package.json
main.js 是主进程执行文件, index.html 和 index.js 是渲染进程的视图,和平时写的网页一样。
需要在package.json写明App名称等信息,同时 需要指定入口文件(即main.js
),举个例子:
{
"name": "app",
"productName": "app",
"version": "0.0.1",
"main": "./app/index.js"
}
更详细的范例可以参照官方示例:electron-quick-start
Step 4. 启动electron
主进程服务加上npm scripts
:
"dev:electron-main": "cross-env NODE_ENV='development' electron -r babel-register ./",
启动主进程:
npm run dev:electron-main
Step 5. 开发
electron主进程文件修改之后需要重启应用才能生效,每次手动重启不方便,推荐使用鄙人抖机灵撸的一个包:
食用方法,在主进程文件中插入下面代码片段:
if (process.env.NODE_ENV === 'development') {
require('electron-watch')(
__dirname,
'dev:electron-main', // means: npm run dev:electron-main
path.join(__dirname, './'),
);
}
** PS: 该库Windows上有bug,正确的食用发放是只按一次Ctrl+S
,同时按几次会重启多个应用。 **
electron模块
electron提供了丰富的API让你调用原生的功能,参见electron文档。
但是过一下下面这几个模块,就可以快速撸出一个Electron App。
1. app
一个桌面应用对象,提供API控制应用,同时可以监控应用程序的事件生命周期。
例如在最后一个窗口被关闭时退出应用:
const {app} = require('electron');
app.on('window-all-closed', () => {
app.quit();
});
可以监控的生命周期事件:
will-finish-launching
:当应用程序完成基础的启动的时候被触发ready
: 当 Electron`: 完成初始化时被触发window-all-closed
:当所有的窗口都被关闭时触发before-quit
:在应用程序开始关闭窗口之前触发will-quit
: 当所有窗口都已关闭并且应用程序将退出时发出quit
:在应用程序退出时发出
2. BrowserWindow
一个浏览器窗口
创建一个浏览器窗口:
import { app, BrowserWindow } from 'electron';
app.on('ready', () => {
const win = new BrowserWindow({width: 800, height: 600});
// 加载远程URL
win.loadURL('https://www.coolecho.net');
// 或加载本地HTML文件
win.loadURL(`file://${__dirname}/app/index.html`);
});
父子窗口:
const {BrowserWindow} = require('electron');
const top = new BrowserWindow();
/**
* 加上model属性,子窗口为模态窗口,父窗口被禁用
*/
const child = new BrowserWindow({parent: top})
child.show()
top.show()
child
窗口将总是显示在 top
窗口的顶部,长得像这个样子:
3. ipcMain & ipcRender
ipcMain
是EventEmitter
类的一个实例。 当在主进程中使用时,ipcRender
处理从 渲染器进程(网页) 发送出来的异步和同步信息。 从渲染器进程发送的消息将被发送到该模块。
ipcRenderer
也是一个 EventEmitter
的实例。 你可以使用它提供的一些方法从渲染进程 (web 页面) 发送同步或异步的消息到主进程。 也可以接收主进程回复的消息。
下面是在渲染和主进程之间发送和处理消息的一个例子:
// 在主进程中
const {ipcMain} = require('electron');
// 异步
ipcMain.on('asynchronous-message', (event, arg) => {
console.log(arg); // prints "ping"
event.sender.send('asynchronous-reply', 'pong');
});
// 同步
ipcMain.on('synchronous-message', (event, arg) => {
console.log(arg) // prints "ping"
event.returnValue = 'pong'
});
//在渲染器进程 (网页) 中
const {ipcRenderer} = require('electron');
// 同步请求
console.log(ipcRenderer.sendSync('synchronous-message', 'ping'));
// 异步发送请求
ipcRenderer.on('asynchronous-reply', (event, arg) => {
console.log(arg) // prints "pong"
});
// 异步接收返回
ipcRenderer.send('asynchronous-message', 'ping');
你可以通过ipcMain
和ipcRenderer
模块,在渲染页面中调用原生功能处理一些事物,当然electron
做到的远不止这些,它还提供了remote模块。使用 remote
模块, 你可以调用 主进程对象 的方法, 而不必显式发送进程间消息。
4. Menu
创建原生应用菜单和上下文菜单。
import { Menu } from 'electron';
const menuTemplete = [{
label: 'File',
submenu: [{
label: 'New Note',
accelerator: 'CmdOrCtrl+N',
enabled: false,
// role: 'new file',
click: () => mainWindow.webContents.send('new-file', 1),
}, {
label: 'New Project',
accelerator: 'Shift+CmdOrCtrl+N',
enabled: false,
// role: 'new project',
click: () => mainWindow.webContents.send('new-project'),
}, {
type: 'separator',
}, {
label: 'Save',
accelerator: 'CmdOrCtrl+S',
role: 'save',
enabled: false,
click: () => mainWindow.webContents.send('save-content'),
}],
}];
const menu = Menu.buildFromTemplate(menuTemplete);
Menu.setApplicationMenu(menu);
效果:
还可以创建Context菜单:
const menu = new Menu();
menu.append(new MenuItem({
label: 'Rename',
click: () => mainWindow.webContents.send('rename-project'),
}));
menu.append(new MenuItem({
label: 'Delete',
click: () => mainWindow.webContents.send('delete-project'),
}));
menu.append(new MenuItem({
type: 'separator',
}));
menu.append(new MenuItem({
label: 'New Notebook',
click: () => mainWindow.webContents.send('new-project'),
}));
// 项目右键菜单
ipcMain.on('show-context-menu-project-item', (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
menu.popup(win);
});
效果:
菜单项
Electron
提供了很多属性来定制一个菜单项,例如上面例子中的label
,click
,role
等等:
click
: 点击菜单项的回调方法role
:Electron
定制的内置事件,有click
的时候,此项将被忽略type
: 可以是normal
、separator
、submenu
、checkbox
或radio
。label
: 菜单名称,当设置role时默认为roleaccelerator
: 定义快捷键icon
sublabel
enabled
: 如果为 false,该菜单项将会置灰且不可点击visible
: 控制菜单项是否可见checked
: 控制菜单项是否选中,type
为chckbox
或radio
时有效submenu
: 定义子菜单项id
: 可以通过它来引用该菜单项position
: 允许对给定菜单中的特定位置进行细粒度定义(没试过)
其他模块
基本上知道上诉三个模块,就可以开发一个Electron APP了,但Electron的模块远远不止这些,它还囊括网络、电源、通知、进程、菜单、本地化、协议、会话、Shell等等模块,详细的介绍参阅Electron文档。
分发应用
应用开发完了,接下来得打包了。
有三种种打包方式:
- 手动打包
- 打包工具
- 通过重编译源代码来进行重新定制
1,3两种方式有点繁琐,这里介绍几个常用的打包工具:
如果想在APP STORE上架应用,,这需使用XCode填写证书和开发者信息,手动打包。
我用的是electron-packer
,打包起来十分方便,看看npm script
:
"packager:mac": "electron-packager ./lib Yosoro --overwrite --platform=darwin --arch=x64 --out=out --icon=assets/icons/osx/app.icns",
"packager:win": "electron-packager ./lib Yosoro --overwrite --platform=win32 --arch=ia32 --out=out --icon=assets/icons/win/app.ico",
"packager:linux": "electron-packager ./lib Yosoro --overwrite --platform=linux --arch=x64 --out=out",
--icon
参数是用来指定应用的logo的,macOS
下是.icns
格式, Windows
下是.ico
格式。
为啥Linux
的没有这个命令行参数呢?
因为Linux
的Logo需要在初始化Electron应用的时候指定,格式为PNG
和JPEG
,建议使用PNG
:
const options = {
title: 'Yosoro',
width: 1180,
height: 786,
};
if (process.platform === 'linux') { // 加上logo
options.icon = path.join(__dirname, './resource/app.png');
}
mainWindow = new BrowserWindow(options);
options.icon
这个属性在macOS
和Windows
下是无效的。
最后,你以为直接npm run
这三条命令就完事了吗?
electron-packager
只在对的地方干对的事,例如,在macOS上只打macOS上运行的包,Windows上只打Windows上运行的包,从不会干多余的打包工作。比如在macOS
下会直接打成一个APP
包:
如果你想分别打三端的包,需要分别在macOS
,Windows
,Linux
这三个环境下打包。
总结
关于Electron
还有非常强大的功能,例如热更新等等,本文只是菜鸡随手笔记,有错误的地方欢迎指正。
最后,关于Electron结合react的实例 戳这里,求star。