用JS开发跨平台桌面应用,从原理到实践

论坛 期权论坛 期权     
奇舞周刊   2019-7-14 05:52   1835   0
[h1]导读[/h1]使用
  1. Electron
复制代码
开发客户端程序已经有一段时间了,整体感觉还是非常不错的,其中也遇到了一些坑点,本文是从【运行原理】到【实际应用】对
  1. Electron
复制代码
进行一次系统性的总结。【多图,长文预警~】
本文所有实例代码均在我的github electron-react上,结合代码阅读文章效果更佳。另外
  1. electron-react
复制代码
还可作为使用
  1. Electron + React + Mobx + Webpack
复制代码
技术栈的脚手架工程。
[h1]一、桌面应用程序[/h1]
桌面应用程序,又称为 GUI 程序(Graphical User Interface),但是和 GUI 程序也有一些区别。桌面应用程序 将 GUI 程序从GUI 具体为“桌面”,使冷冰冰的像块木头一样的电脑概念更具有 人性化,更生动和富有活力。
我们电脑上使用的各种客户端程序都属于桌面应用程序,近年来
  1. WEB
复制代码
和移动端的兴起让桌面程序渐渐暗淡,但是在某些日常功能或者行业应用中桌面应用程序仍然是必不可少的。
传统的桌面应用开发方式,一般是下面两种:
[h2]1.1 原生开发[/h2]直接将语言编译成可执行文件,直接调用系统
  1. API
复制代码
,完成UI绘制等。这类开发技术,有着较高的运行效率,但一般来说,开发速度较慢,技术要求较高,例如:
  • 使用
    1. C++ / MFC
    复制代码
    开发
    1. Windows
    复制代码
    应用
  • 使用
    1. Objective-C
    复制代码
    开发
    1. MAC
    复制代码
    应用
[h2]1.2 托管平台[/h2]一开始就有本地开发和UI开发。一次编译后,得到中间文件,通过平台或虚机完成二次加载编译或解释运行。运行效率低于原生编译,但平台优化后,其效率也是比较可观的。就开发速度方面,比原生编译技术要快一些。例如:
  • 使用
    1. C# / .NET Framework
    复制代码
    (只能开发
    1. Windows应用
    复制代码
    )
    1. Java / Swing
    复制代码
不过,上面两种对前端开发人员太不友好了,基本是前端人员不会涉及的领域,但是在这个【大前端】的时代,前端开发者正在想方设法涉足各个领域,使用
  1. WEB
复制代码
技术开发客户端的方式横空出世。
[h2]1.3 WEB开发[/h2]使用
  1. WEB
复制代码
技术进行开发,利用浏览器引擎完成
  1. UI
复制代码
渲染,利用
  1. Node.js
复制代码
实现服务器端
  1. JS
复制代码
编程并可以调用系统
  1. API
复制代码
,可以把它想像成一个套了一个客户端外壳的
  1. WEB
复制代码
应用。
在界面上,
  1. WEB
复制代码
的强大生态为
  1. UI
复制代码
带来了无限可能,并且开发、维护成本相对较低,有
  1. WEB
复制代码
开发经验的前端开发者很容易上手进行开发。
本文就来着重介绍使用
  1. WEB
复制代码
技术开发客户端程序的技术之一【
  1. electron
复制代码

[h1]二、Electron[/h1]
  1. Electron
复制代码
是由
  1. Github
复制代码
开发,用
  1. HTML,CSS
复制代码
  1. JavaScript
复制代码
来构建跨平台桌面应用程序的一个开源库。
  1. Electron
复制代码
通过将
  1. Chromium
复制代码
  1. Node.js
复制代码
合并到同一个运行时环境中,并将其打包为
  1. Mac,Windows
复制代码
  1. Linux
复制代码
系统下的应用来实现这一目的。
[h2]2.1 使用Electron开发的理由:[/h2]
  • 1.使用具有强大生态的
    1. Web
    复制代码
    技术进行开发,开发成本低,可扩展性强,更炫酷的
    1. UI
    复制代码
  • 2.跨平台,一套代码可打包为
    1. Windows、Linux、Mac
    复制代码
    三套软件,且编译快速
  • 3.可直接在现有
    1. Web
    复制代码
    应用上进行扩展,提供浏览器不具备的能力
  • 4.你是一个前端~
当然,我们也要认清它的缺点:性能比原生桌面应用要低,最终打包后的应用比原生应用大很多。
[h2]2.2 开发体验[/h2]兼容性
虽然你还在用
  1. WEB
复制代码
技术进行开发,但是你不用再考虑兼容性问题了,你只需要关心你当前使用
  1. Electron
复制代码
的版本对应
  1. Chrome
复制代码
的版本,一般情况下它已经足够新来让你使用最新的
  1. API
复制代码
和语法了,你还可以手动升级
  1. Chrome
复制代码
版本。同样的,你也不用考虑不同浏览器带的样式和代码兼容问题。
Node环境
这可能是很多前端开发者曾经梦想过的功能,在
  1. WEB
复制代码
界面中使用
  1. Node.js
复制代码
提供的强大
  1. API
复制代码
,这意味着你在
  1. WEB
复制代码
页面直接可以操作文件,调用系统
  1. API
复制代码
,甚至操作数据库。当然,除了完整的
  1. Node API
复制代码
,你还可以使用额外的几十万个
  1. npm
复制代码
模块。
跨域
你可以直接使用
  1. Node
复制代码
提供的
  1. request
复制代码
模块进行网络请求,这意味着你无需再被跨域所困扰。
强大的扩展性
借助
  1. node-ffi
复制代码
,为应用程序提供强大的扩展性(后面的章节会详细介绍)。
[h2]2.3 谁在用Electron[/h2]
现在市面上已经有非常多的应用在使用
  1. Electron
复制代码
进行开发了,包括我们熟悉的
  1. VS Code
复制代码
客户端、
  1. GitHub
复制代码
客户端、
  1. Atom
复制代码
客户端等等。印象很深的,去年迅雷在发布迅雷X
  1. 10.1
复制代码
时的文案:
从迅雷X 10.1版本开始,我们采用Electron软件框架完全重写了迅雷主界面。使用新框架的迅雷X可以完美支持2K、4K等高清显示屏,界面中的文字渲染也更加清晰锐利。从技术层面来说,新框架的界面绘制、事件处理等方面比老框架更加灵活高效,因此界面的流畅度也显著优于老框架的迅雷。至于具体提升有多大?您一试便知。
你可以打开
  1. VS Code
复制代码
,点击【帮助】【切换开发人员工具】来调试
  1. VS Code
复制代码
客户端的界面。


[h1]三、Electron运行原理[/h1]
  1. Electron
复制代码
结合了
  1. Chromium
复制代码
  1. Node.js
复制代码
和用于调用操作系统本地功能的
  1. API
复制代码

[h2]3.1 Chromium[/h2]
  1. Chromium
复制代码
  1. Google
复制代码
为发展
  1. Chrome
复制代码
浏览器而启动的开源项目,
  1. Chromium
复制代码
相当于
  1. Chrome
复制代码
的工程版或称实验版,新功能会率先在
  1. Chromium
复制代码
上实现,待验证后才会应用在
  1. Chrome
复制代码
上,故
  1. Chrome
复制代码
的功能会相对落后但较稳定。
  1. Chromium
复制代码
  1. Electron
复制代码
提供强大的
  1. UI
复制代码
能力,可以在不考虑兼容性的情况下开发界面。
[h2]3.2 Node.js[/h2]
  1. Node.js
复制代码
是一个让
  1. JavaScript
复制代码
运行在服务端的开发平台,
  1. Node
复制代码
使用事件驱动,非阻塞
  1. I/O
复制代码
模型而得以轻量和高效。
单单靠
  1. Chromium
复制代码
是不能具备直接操作原生
  1. GUI
复制代码
能力的,
  1. Electron
复制代码
内集成了
  1. Nodejs
复制代码
,这让其在开发界面的同时也有了操作系统底层
  1. API
复制代码
的能力,
  1. Nodejs
复制代码
中常用的
  1. Path、fs、Crypto
复制代码
等模块在
  1. Electron
复制代码
可以直接使用。
[h2]3.3 系统API[/h2]为了提供原生系统的
  1. GUI
复制代码
支持,
  1. Electron
复制代码
内置了原生应用程序接口,对调用一些系统功能,如调用系统通知、打开系统文件夹提供支持。
在开发模式上,
  1. Electron
复制代码
在调用系统
  1. API
复制代码
和绘制界面上是分离开发的,下面我们来看看
  1. Electron
复制代码
关于进程如何划分。
[h2]3.4 主进程[/h2]
  1. Electron
复制代码
区分了两种进程:主进程和渲染进程,两者各自负责自己的职能。
  1. Electron
复制代码
运行
  1. package.json
复制代码
  1. main
复制代码
脚本的进程被称为主进程。一个
  1. Electron
复制代码
应用总是有且只有一个主进程。
职责:
  • 创建渲染进程(可多个)
  • 控制了应用生命周期(启动、退出
    1. APP
    复制代码
    以及对
    1. APP
    复制代码
    做一些事件监听)
  • 调用系统底层功能、调用原生资源
可调用的API:
    1. Node.js API
    复制代码
    1. Electron
    复制代码
    提供的主进程
    1. API
    复制代码
    (包括一些系统功能和
    1. Electron
    复制代码
    附加功能)
[h2]3.5 渲染进程[/h2]由于
  1. Electron
复制代码
使用了
  1. Chromium
复制代码
来展示
  1. web
复制代码
页面,所以
  1. Chromium
复制代码
的多进程架构也被使用到。 每个
  1. Electron
复制代码
中的
  1. web
复制代码
页面运行在它自己的渲染进程中。
主进程使用 BrowserWindow 实例创建页面。 每个 BrowserWindow 实例都在自己的渲染进程里运行页面。 当一个 BrowserWindow 实例被销毁后,相应的渲染进程也会被终止。
你可以把渲染进程想像成一个浏览器窗口,它能存在多个并且相互独立,不过和浏览器不同的是,它能调用
  1. Node API
复制代码

职责:
    1. HTML
    复制代码
    1. CSS
    复制代码
    渲染界面
    1. JavaScript
    复制代码
    做一些界面交互
可调用的API:
    1. DOM API
    复制代码
    1. Node.js API
    复制代码
    1. Electron
    复制代码
    提供的渲染进程
    1. API
    复制代码
[h1]四、Electron基础[/h1][h2]4.1 Electron API[/h2]在上面的章节我们提到,渲染进和主进程分别可调用的
  1. Electron API
复制代码
。所有
  1. Electron
复制代码
  1. API
复制代码
都被指派给一种进程类型。 许多
  1. API
复制代码
只能被用于主进程中,有些
  1. API
复制代码
又只能被用于渲染进程,又有一些主进程和渲染进程中都可以使用。
你可以通过如下方式获取
  1. Electron API
复制代码
  1. const { BrowserWindow, ... } = require('electron')
复制代码
下面是一些常用的
  1. Electron API
复制代码


在后面的章节我们会选择其中常用的模块进行详细介绍。
[h2]4.2 使用 Node.js 的 API[/h2]
你可以同时在
  1. Electron
复制代码
的主进程和渲染进程使用
  1. Node.js API
复制代码
,)所有在
  1. Node.js
复制代码
可以使用的
  1. API
复制代码
,在
  1. Electron
复制代码
中同样可以使用。
  1. import {shell} from 'electron';
  2. import os from 'os';
  3. document.getElementById('btn').addEventListener('click', () => {
  4.   shell.showItemInFolder(os.homedir());
  5. })
复制代码
有一个非常重要的提示: 原生Node.js模块 (即指,需要编译源码过后才能被使用的模块) 需要在编译后才能和Electron一起使用。
[h2]4.3 进程通信[/h2]主进程和渲染进程虽然拥有不同的职责,然是他们也需要相互协作,互相通讯。
例如:在
  1. web
复制代码
页面管理原生
  1. GUI
复制代码
资源是很危险的,会很容易泄露资源。所以在
  1. web
复制代码
页面,不允许直接调用原生
  1. GUI
复制代码
相关的
  1. API
复制代码
。渲染进程如果想要进行原生的
  1. GUI
复制代码
操作,就必须和主进程通讯,请求主进程来完成这些操作。

[h2]4.4 渲染进程向主进程通信[/h2]
  1. ipcRenderer
复制代码
是一个
  1. EventEmitter
复制代码
的实例。 你可以使用它提供的一些方法,从渲染进程发送同步或异步的消息到主进程。 也可以接收主进程回复的消息。
在渲染进程引入
  1. ipcRenderer
复制代码
  1. import { ipcRenderer } from 'electron';
复制代码
异步发送:
通过
  1. channel
复制代码
发送同步消息到主进程,可以携带任意参数。
在内部,参数会被序列化为
  1. JSON
复制代码
,因此参数对象上的函数和原型链不会被发送。
  1. ipcRenderer.send('sync-render', '我是来自渲染进程的异步消息');
复制代码
同步发送:
  1. const msg = ipcRenderer.sendSync('async-render', '我是来自渲染进程的同步消息');
复制代码
注意: 发送同步消息将会阻塞整个渲染进程,直到收到主进程的响应。
主进程监听消息:
  1. ipcMain
复制代码
模块是
  1. EventEmitter
复制代码
类的一个实例。 当在主进程中使用时,它处理从渲染器进程(网页)发送出来的异步和同步信息。 从渲染器进程发送的消息将被发送到该模块。
  1. ipcMain.on
复制代码
:监听
  1. channel
复制代码
,当接收到新的消息时
  1. listener
复制代码
会以
  1. listener(event, args...)
复制代码
的形式被调用。
  1.   ipcMain.on('sync-render', (event, data) => {
  2.     console.log(data);
  3.   });
复制代码
[h2]4.5 主进程向渲染进程通信[/h2]在主进程中可以通过
  1. BrowserWindow
复制代码
  1. webContents
复制代码
向渲染进程发送消息,所以,在发送消息前你必须先找到对应渲染进程的
  1. BrowserWindow
复制代码
对象。:
  1. const mainWindow = BrowserWindow.fromId(global.mainId);
  2. mainWindow.webContents.send('main-msg', `ConardLi]`)
复制代码
根据消息来源发送:
  1. ipcMain
复制代码
接受消息的回调函数中,通过第一个参数
  1. event
复制代码
的属性
  1. sender
复制代码
可以拿到消息来源渲染进程的
  1. webContents
复制代码
对象,我们可以直接用此对象回应消息。
  1.   ipcMain.on('sync-render', (event, data) => {
  2.     console.log(data);
  3.     event.sender.send('main-msg', '主进程收到了渲染进程的【异步】消息!')
  4.   });
复制代码
渲染进程监听:
  1. ipcRenderer.on
复制代码
:监听
  1. channel
复制代码
, 当新消息到达,将通过
  1. listener(event, args...)
复制代码
调用
  1. listener
复制代码
  1. ipcRenderer.on('main-msg', (event, msg) => {
  2.     console.log(msg);
  3. })
复制代码
[h2]4.6 通信原理[/h2]
  1. ipcMain
复制代码
  1. ipcRenderer
复制代码
都是
  1. EventEmitter
复制代码
类的一个实例。
  1. EventEmitter
复制代码
类是
  1. NodeJS
复制代码
事件的基础,它由
  1. NodeJS
复制代码
中的
  1. events
复制代码
模块导出。
  1. EventEmitter
复制代码
的核心就是事件触发与事件监听器功能的封装。它实现了事件模型需要的接口, 包括
  1. addListener,removeListener
复制代码
,
  1. emit
复制代码
及其它工具方法. 同原生
  1. JavaScript
复制代码
事件类似, 采用了发布/订阅(观察者)的方式, 使用内部
  1. _events
复制代码
列表来记录注册的事件处理器。
我们通过
  1. ipcMain
复制代码
  1. ipcRenderer
复制代码
  1. on、send
复制代码
进行监听和发送消息都是
  1. EventEmitter
复制代码
定义的相关接口。
[h2]4.7 remote[/h2]
  1. remote
复制代码
模块为渲染进程(web页面)和主进程通信(
  1. IPC
复制代码
)提供了一种简单方法。 使用
  1. remote
复制代码
模块, 你可以调用
  1. main
复制代码
进程对象的方法, 而不必显式发送进程间消息, 类似于
  1. Java
复制代码
  1. RMI
复制代码
  1. import { remote } from 'electron';
  2. remote.dialog.showErrorBox('主进程才有的dialog模块', '我是使用remote调用的')
复制代码


但实际上,我们在调用远程对象的方法、函数或者通过远程构造函数创建一个新的对象,实际上都是在发送一个同步的进程间消息。
在上面通过
  1. remote
复制代码
模块调用
  1. dialog
复制代码
的例子里。我们在渲染进程中创建的
  1. dialog
复制代码
对象其实并不在我们的渲染进程中,它只是让主进程创建了一个
  1. dialog
复制代码
对象,并返回了这个相对应的远程对象给了渲染进程。
[h2]4.8 渲染进程间通信[/h2]
  1. Electron
复制代码
并没有提供渲染进程之间相互通信的方式,我们可以在主进程中建立一个消息中转站。
渲染进程之间通信首先发送消息到主进程,主进程的中转站接收到消息后根据条件进行分发。
[h2]4.9 渲染进程数据共享[/h2]在两个渲染进程间共享数据最简单的方法是使用浏览器中已经实现的
  1. HTML5 API
复制代码
。 其中比较好的方案是用
  1. Storage API
复制代码
  1. localStorage,sessionStorage
复制代码
或者
  1. IndexedDB。
复制代码
就像在浏览器中使用一样,这种存储相当于在应用程序中永久存储了一部分数据。有时你并不需要这样的存储,只需要在当前应用程序的生命周期内进行一些数据的共享。这时你可以用
  1. Electron
复制代码
内的
  1. IPC
复制代码
机制实现。
将数据存在主进程的某个全局变量中,然后在多个渲染进程中使用
  1. remote
复制代码
模块来访问它。


在主进程中初始化全局变量:
  1. global.mainId = ...;
  2. global.device = {...};
  3. global.__dirname = __dirname;
  4. global.myField = { name: 'ConardLi' };
复制代码
在渲染进程中读取:
  1. import { ipcRenderer, remote } from 'electron';
  2. const { getGlobal } = remote;
  3. const mainId = getGlobal('mainId')
  4. const dirname = getGlobal('__dirname')
  5. const deviecMac = getGlobal('device').mac;
复制代码
在渲染进程中改变:
  1. getGlobal('myField').name = 'code秘密花园';
复制代码
多个渲染进程共享同一个主进程的全局变量,这样即可达到渲染进程数据共享和传递的效果。
[h1]五、窗口[/h1][h2]5.1 BrowserWindow[/h2]主进程模块
  1. BrowserWindow
复制代码
用于创建和控制浏览器窗口。
  1.   mainWindow = new BrowserWindow({
  2.     width: 1000,
  3.     height: 800,
  4.     // ...
  5.   });
  6.   mainWindow.loadURL('http://www.conardli.top/');
复制代码
你可以在这里查看它所有的构造参数。

[h2]5.2 无框窗口[/h2]
无框窗口是没有镶边的窗口,窗口的部分(如工具栏)不属于网页的一部分。
  1. BrowserWindow
复制代码
的构造参数中,将
  1. frame
复制代码
设置为
  1. false
复制代码
可以指定窗口为无边框窗口,将工具栏隐藏后,就会产生两个问题:
  • 1.窗口控制按钮(最小化、全屏、关闭按钮)会被隐藏
  • 2.无法拖拽移动窗口
可以通过指定
  1. titleBarStyle
复制代码
选项来再将工具栏按钮显示出来,将其设置为
  1. hidden
复制代码
表示返回一个隐藏标题栏的全尺寸内容窗口,在左上角仍然有标准的窗口控制按钮。
  1. new BrowserWindow({
  2.     width: 200,
  3.     height: 200,
  4.     titleBarStyle: 'hidden',
  5.     frame: false
  6.   });
复制代码
[h2]5.3 窗口拖拽[/h2]默认情况下, 无边框窗口是不可拖拽的。我们可以在界面中通过
  1. CSS
复制代码
属性
  1. -webkit-app-region: drag
复制代码
手动制定拖拽区域。
在无框窗口中, 拖动行为可能与选择文本冲突,可以通过设定
  1. -webkit-user-select: none;
复制代码
禁用文本选择:
  1. .header {
  2.   -webkit-user-select: none;
  3.   -webkit-app-region: drag;
  4. }
复制代码
相反的,在可拖拽区域内部设置
  1. -webkit-app-region: no-drag
复制代码
则可以指定特定不可拖拽区域。
[h2]5.4 透明窗口[/h2]通过将
  1. transparent
复制代码
选项设置为
  1. true
复制代码
, 还可以使无框窗口透明:
  1. new BrowserWindow({
  2.     transparent: true,
  3.     frame: false
  4.   });
复制代码
[h2]5.5 Webview[/h2]使用
  1. webview
复制代码
标签在
  1. Electron
复制代码
应用中嵌入 "外来" 内容。外来内容包含在
  1. webview
复制代码
容器中。 应用中的嵌入页面可以控制外来内容的布局和重绘。
  1. iframe
复制代码
不同,
  1. webview
复制代码
在与应用程序不同的进程中运行。它与您的网页没有相同的权限, 应用程序和嵌入内容之间的所有交互都将是异步的。
[h1]六、对话框[/h1]
  1. dialog
复制代码
模块提供了
  1. api
复制代码
来展示原生的系统对话框,例如打开文件框,
  1. alert
复制代码
框,所以
  1. web
复制代码
应用可以给用户带来跟系统应用相同的体验。
注意:dialog是主进程模块,想要在渲染进程调用可以使用remote


[h2]6.1 错误提示[/h2]
  1. dialog.showErrorBox
复制代码
用于显示一个显示错误消息的模态对话框。
  1. remote.dialog.showErrorBox('错误', '这是一个错误弹框!')
复制代码
[h2]6.2 对话框[/h2]
  1. dialog.showErrorBox
复制代码
用于调用系统对话框,可以为指定几种不同的类型: "
  1. none
复制代码
", "
  1. info
复制代码
", "
  1. error
复制代码
", "
  1. question
复制代码
" 或者 "
  1. warning
复制代码
"。
在 Windows 上, "question" 与"info"显示相同的图标, 除非你使用了 "icon" 选项设置图标。 在 macOS 上, "warning" 和 "error" 显示相同的警告图标
  1. remote.dialog.showMessageBox({
  2.   type: 'info',
  3.   title: '提示信息',
  4.   message: '这是一个对话弹框!',
  5.   buttons: ['确定', '取消']
  6. }, (index) => {
  7.   this.setState({ dialogMessage: `【你点击了${index ? '取消' : '确定'}!!】` })
  8. })
复制代码
[h2]6.3 文件框[/h2]
  1. dialog.showOpenDialog
复制代码
用于打开或选择系统目录。
  1. remote.dialog.showOpenDialog({
  2.   properties: ['openDirectory', 'openFile']
  3. }, (data) => {
  4.   this.setState({ filePath: `【选择路径:${data[0]}】 ` })
  5. })
复制代码
[h2]6.4 信息框[/h2]这里推荐直接使用
  1. HTML5 API
复制代码
,它只能在渲染器进程中使用。
  1. let options = {
  2.   title: '信息框标题',
  3.   body: '我是一条信息~~~',
  4. }
  5. let myNotification = new window.Notification(options.title, options)
  6. myNotification.onclick = () => {
  7.   this.setState({ message: '【你点击了信息框!!】' })
  8. }
复制代码
[h1]七、系统[/h1][h2]7.1 获取系统信息[/h2]
通过
  1. remote
复制代码
获取到主进程的
  1. process
复制代码
对象,可以获取到当前应用的各个版本信息:
    1. process.versions.electron
    复制代码
    1. electron
    复制代码
    版本信息
    1. process.versions.chrome
    复制代码
    1. chrome
    复制代码
    版本信息
    1. process.versions.node
    复制代码
    1. node
    复制代码
    版本信息
    1. process.versions.v8
    复制代码
    1. v8
    复制代码
    版本信息
获取当前应用根目录:
  1. remote.app.getAppPath()
复制代码
使用
  1. node
复制代码
  1. os
复制代码
模块获取当前系统根目录:
  1. os.homedir();
复制代码
[h2]7.2 复制粘贴[/h2]
  1. Electron
复制代码
提供的
  1. clipboard
复制代码
在渲染进程和主进程都可使用,用于在系统剪贴板上执行复制和粘贴操作。
以纯文本的形式写入剪贴板:
  1. clipboard.writeText(text[, type])
复制代码
以纯文本的形式获取剪贴板的内容:
  1. clipboard.readText([type])
复制代码
[h2]7.3 截图[/h2]
  1. desktopCapturer
复制代码
用于从桌面捕获音频和视频的媒体源的信息。它只能在渲染进程中被调用。

下面的代码是一个获取屏幕截图并保存的实例:
  1.   getImg = () => {
  2.     this.setState({ imgMsg: '正在截取屏幕...' })
  3.     const thumbSize = this.determineScreenShotSize()
  4.     let options = { types: ['screen'], thumbnailSize: thumbSize }
  5.     desktopCapturer.getSources(options, (error, sources) => {
  6.       if (error) return console.log(error)
  7.       sources.forEach((source) => {
  8.         if (source.name === 'Entire screen' || source.name === 'Screen 1') {
  9.           const screenshotPath = path.join(os.tmpdir(), 'screenshot.png')
  10.           fs.writeFile(screenshotPath, source.thumbnail.toPNG(), (error) => {
  11.             if (error) return console.log(error)
  12.             shell.openExternal(`file://${screenshotPath}`)
  13.             this.setState({ imgMsg: `截图保存到: ${screenshotPath}` })
  14.           })
  15.         }
  16.       })
  17.     })
  18.   }
  19.   determineScreenShotSize = () => {
  20.     const screenSize = screen.getPrimaryDisplay().workAreaSize
  21.     const maxDimension = Math.max(screenSize.width, screenSize.height)
  22.     return {
  23.       width: maxDimension * window.devicePixelRatio,
  24.       height: maxDimension * window.devicePixelRatio
  25.     }
  26.   }
复制代码
[h1]八、菜单[/h1]应用程序的菜单可以帮助我们快捷的到达某一功能,而不借助客户端的界面资源,一般菜单分为两种:
  • 应用程序菜单:位于应用程序顶部,在全局范围内都能使用
  • 上下文菜单:可自定义任意页面显示,自定义调用,如右键菜单
  1. Electron
复制代码
为我们提供了
  1. Menu
复制代码
模块用于创建本机应用程序菜单和上下文菜单,它是一个主进程模块。
你可以通过
  1. Menu
复制代码
的静态方法
  1. buildFromTemplate(template)
复制代码
,使用自定义菜单模版来构造一个菜单对象。
  1. template
复制代码
是一个
  1. MenuItem
复制代码
的数组,我们来看看
  1. MenuItem
复制代码
的几个重要参数:
    1. label
    复制代码
    :菜单显示的文字
    1. click
    复制代码
    :点击菜单后的事件处理函数
    1. role
    复制代码
    :系统预定义的菜单,例如
    1. copy
    复制代码
    (复制)、
    1. paste
    复制代码
    (粘贴)、
    1. minimize
    复制代码
    (最小化)…
    1. enabled
    复制代码
    :指示是否启用该项目,此属性可以动态更改
    1. submenu
    复制代码
    :子菜单,也是一个
    1. MenuItem
    复制代码
    的数组
推荐:最好指定role与标准角色相匹配的任何菜单项,而不是尝试手动实现click函数中的行为。内置role行为将提供最佳的本地体验。
下面的实例是一个简单的额菜单
  1. template
复制代码
  1. const template = [
  2.   {
  3.     label: '文件',
  4.     submenu: [
  5.       {
  6.         label: '新建文件',
  7.         click: function () {
  8.           dialog.showMessageBox({
  9.             type: 'info',
  10.             message: '嘿!',
  11.             detail: '你点击了新建文件!',
  12.           })
  13.         }
  14.       }
  15.     ]
  16.   },
  17.   {
  18.     label: '编辑',
  19.     submenu: [{
  20.       label: '剪切',
  21.       role: 'cut'
  22.     }, {
  23.       label: '复制',
  24.       role: 'copy'
  25.     }, {
  26.       label: '粘贴',
  27.       role: 'paste'
  28.     }]
  29.   },
  30.   {
  31.     label: '最小化',
  32.     role: 'minimize'
  33.   }
  34. ]
复制代码
[h2]8.1 应用程序菜单[/h2]使用
  1. Menu
复制代码
的静态方法
  1. setApplicationMenu
复制代码
,可创建一个应用程序菜单,在
  1. Windows
复制代码
  1. Linux
复制代码
上,
  1. menu
复制代码
将被设置为每个窗口的顶层菜单。
注意:必须在模块ready事件后调用此 API app。
我们可以根据应用程序不同的的生命周期,不同的系统对菜单做不同的处理。

  1. app.on('ready', function () {
  2.   const menu = Menu.buildFromTemplate(template)
  3.   Menu.setApplicationMenu(menu)
  4. })
  5. app.on('browser-window-created', function () {
  6.   let reopenMenuItem = findReopenMenuItem()
  7.   if (reopenMenuItem) reopenMenuItem.enabled = false
  8. })
  9. app.on('window-all-closed', function () {
  10.   let reopenMenuItem = findReopenMenuItem()
  11.   if (reopenMenuItem) reopenMenuItem.enabled = true
  12. })
  13. if (process.platform === 'win32') {
  14.   const helpMenu = template[template.length - 1].submenu
  15.   addUpdateMenuItems(helpMenu, 0)
  16. }
复制代码
[h2]8.2 上下文菜单[/h2]使用
  1. Menu
复制代码
的实例方法
  1. menu.popup
复制代码
可自定义弹出上下文菜单。

  1.     let m = Menu.buildFromTemplate(template)
  2.     document.getElementById('menuDemoContainer').addEventListener('contextmenu', (e) => {
  3.       e.preventDefault()
  4.       m.popup({ window: remote.getCurrentWindow() })
  5.     })
复制代码
[h2]8.3 快捷键[/h2]在菜单选项中,我们可以指定一个
  1. accelerator
复制代码
属性来指定操作的快捷键:
  1.   {
  2.     label: '最小化',
  3.     accelerator: 'CmdOrCtrl+M',
  4.     role: 'minimize'
  5.   }
复制代码
另外,我们还可以使用
  1. globalShortcut
复制代码
来注册全局快捷键。
  1.     globalShortcut.register('CommandOrControl+N', () => {
  2.       dialog.showMessageBox({
  3.         type: 'info',
  4.         message: '嘿!',
  5.         detail: '你触发了手动注册的快捷键.',
  6.       })
  7.     })
复制代码
CommandOrControl代表在macOS上为Command键,以及在Linux和Windows上为Control键。
[h1]九、打印[/h1]很多情况下程序中使用的打印都是用户无感知的。并且想要灵活的控制打印内容,往往需要借助打印机给我们提供的
  1. api
复制代码
再进行开发,这种开发方式非常繁琐,并且开发难度较大。第一次在业务中用到
  1. Electron
复制代码
其实就是用到它的打印功能,这里就多介绍一些。
  1. Electron
复制代码
提供的打印api可以非常灵活的控制打印设置的显示,并且可以通过html来书写打印内容。
  1. Electron
复制代码
提供了两种方式进行打印,一种是直接调用打印机打印,一种是打印到
  1. pdf
复制代码

并且有两种对象可以调用打印:
  • 通过
    1. window
    复制代码
    1. webcontent
    复制代码
    对象,使用此种方式需要单独开出一个打印的窗口,可以将该窗口隐藏,但是通信调用相对复杂。
  • 使用页面的
    1. webview
    复制代码
    元素调用打印,可以将
    1. webview
    复制代码
    隐藏在调用的页面中,通信方式比较简单。
上面两种方式同时拥有
  1. print
复制代码
  1. printToPdf
复制代码
方法。


[h2]9.1 调用系统打印[/h2]
  1. contents.print([options], [callback]);
复制代码
打印配置(options)中只有简单的三个配置:
    1. silent
    复制代码
    :打印时是否不展示打印配置(是否静默打印)
    1. printBackground
    复制代码
    :是否打印背景
    1. deviceName
    复制代码
    :打印机设备名称
首先要将我们使用的打印机名称配置好,并且要在调用打印前首先要判断打印机是否可用。
使用
  1. webContents
复制代码
  1. getPrinters
复制代码
方法可获取当前设备已经配置的打印机列表,注意配置过不是可用,只是在此设备上安装过驱动。
通过
  1. getPrinters
复制代码
获取到的打印机对象:https://electronjs.org/docs/api/structures/printer-info
我们这里只管关心两个,
  1. name
复制代码
  1. status
复制代码
  1. status
复制代码
  1. 0
复制代码
时表示打印机可用。
  1. print
复制代码
的第二个参数
  1. callback
复制代码
是用于判断打印任务是否发出的回调,而不是打印任务完成后的回调。所以一般打印任务发出,回调函数即会调用并返回参数
  1. true
复制代码
。这个回调并不能判断打印是否真的成功了。
  1.     if (this.state.curretnPrinter) {
  2.       mainWindow.webContents.print({
  3.         silent: silent, printBackground: true, deviceName: this.state.curretnPrinter
  4.       }, () => { })
  5.     } else {
  6.       remote.dialog.showErrorBox('错误', '请先选择一个打印机!')
  7.     }
复制代码
[h2]9.2 打印到PDF[/h2]
  1. printToPdf
复制代码
的用法基本和
  1. print
复制代码
相同,但是
  1. print
复制代码
的配置项非常少,而
  1. printToPdf
复制代码
则扩展了很多属性。这里翻了一下源码发现还有很多没有被贴进api的,大概有三十几个包括可以对打印的margin,打印页眉页脚等进行配置。
  1. contents.printToPDF(options, callback)
复制代码
  1. callback
复制代码
函数在打印失败或打印成功后调用,可获取打印失败信息或包含
  1. PDF
复制代码
数据的缓冲区。
  1.     const pdfPath = path.join(os.tmpdir(), 'webviewPrint.pdf');
  2.     const webview = document.getElementById('printWebview');
  3.     const renderHtml = '我是被临时插入webview的内容...';
  4.     webview.executeJavaScript('document.documentElement.innerHTML =`' + renderHtml + '`;');
  5.     webview.printToPDF({}, (err, data) => {
  6.       console.log(err, data);
  7.       fs.writeFile(pdfPath, data, (error) => {
  8.         if (error) throw error
  9.         shell.openExternal(`file://${pdfPath}`)
  10.         this.setState({ webviewPdfPath: pdfPath })
  11.       });
  12.     });
复制代码
这个例子中的打印是使用
  1. webview
复制代码
完成的,通过调用
  1. executeJavaScript
复制代码
方法可动态向
  1. webview
复制代码
插入打印内容。
[h2]9.3 两种打印方案的选择[/h2]上面提到,使用
  1. webview
复制代码
  1. webcontent
复制代码
都可以调用打印功能,使用
  1. webcontent
复制代码
打印,首先要有一个打印窗口,这个窗口不能随时打印随时创建,比较耗费性能。可以将它在程序运行时启动好,并做好事件监听。
此过程需和调用打印的进行做好通信,大致过程如下:

可见通信非常繁琐,使用
  1. webview
复制代码
进行打印可实现同样的效果但是通信方式会变得简单,因为渲染进程和
  1. webview
复制代码
通信不需要经过主进程,通过如下方式即可:
  1.   const webview = document.querySelector('webview')
  2.   webview.addEventListener('ipc-message', (event) => {
  3.     console.log(event.channel)
  4.   })
  5.   webview.send('ping');
  6.   const {ipcRenderer} = require('electron')
  7.   ipcRenderer.on('ping', () => {
  8.     ipcRenderer.sendToHost('pong')
  9.   })
复制代码
之前专门为
  1. ELectron
复制代码
打印写过一个
  1. DEMO
复制代码
:electron-print-demo有兴趣可以
  1. clone
复制代码
下来看一下。
[h2]9.4 打印功能封装[/h2]下面是几个针对常用打印功能的工具函数封装。
  1. /**
  2. * 获取系统打印机列表
  3. */
  4. export function getPrinters() {
  5.   let printers = [];
  6.   try {
  7.     const contents = remote.getCurrentWindow().webContents;
  8.     printers = contents.getPrinters();
  9.   } catch (e) {
  10.     console.error('getPrintersError', e);
  11.   }
  12.   return printers;
  13. }
  14. /**
  15. * 获取系统默认打印机
  16. */
  17. export function getDefaultPrinter() {
  18.   return getPrinters().find(element => element.isDefault);
  19. }
  20. /**
  21. * 检测是否安装了某个打印驱动
  22. */
  23. export function checkDriver(driverMame) {
  24.   return getPrinters().find(element => (element.options["printer-make-and-model"] || '').includes(driverMame));
  25. }
  26. /**
  27. * 根据打印机名称获取打印机对象
  28. */
  29. export function getPrinterByName(name) {
  30.   return getPrinters().find(element => element.name === name);
  31. }
复制代码
[h1]十、程序保护[/h1]
[h2]10.1 崩溃[/h2]崩溃监控是每个客户端程序必备的保护功能,当程序崩溃时我们一般期望做到两件事:
  • 1.上传崩溃日志,及时报警
  • 2.监控程序崩溃,提示用户重启程序
  1. electron
复制代码
为我们提供给了
  1. crashReporter
复制代码
来帮助我们记录崩溃日志,我们可以通过
  1. crashReporter.start
复制代码
来创建一个崩溃报告器:
  1. const { crashReporter } = require('electron')
  2. crashReporter.start({
  3.   productName: 'YourName',
  4.   companyName: 'YourCompany',
  5.   submitURL: 'https://your-domain.com/url-to-submit',
  6.   uploadToServer: true
  7. })
复制代码
当程序发生崩溃时,崩溃报日志将被储存在临时文件夹中名为
  1. YourName Crashes
复制代码
的文件文件夹中。
  1. submitURL
复制代码
用于指定你的崩溃日志上传服务器。 在启动崩溃报告器之前,您可以通过调用
  1. app.setPath('temp', 'my/custom/temp')
复制代码
API来自定义这些临时文件的保存路径。你还可以通过
  1. crashReporter.getLastCrashReport()
复制代码
来获取上次崩溃报告的日期和
  1. ID
复制代码

我们可以通过
  1. webContents
复制代码
  1. crashed
复制代码
来监听渲染进程的崩溃,另外经测试有些主进程的崩溃也会触发该事件。所以我们可以根据主
  1. window
复制代码
是否被销毁来判断进行不同的重启逻辑,下面使整个崩溃监控的逻辑:
  1. import { BrowserWindow, crashReporter, dialog } from 'electron';
  2. // 开启进程崩溃记录
  3. crashReporter.start({
  4.   productName: 'electron-react',
  5.   companyName: 'ConardLi',
  6.   submitURL: 'http://xxx.com',  // 上传崩溃日志的接口
  7.   uploadToServer: false
  8. });
  9. function reloadWindow(mainWin) {
  10.   if (mainWin.isDestroyed()) {
  11.     app.relaunch();
  12.     app.exit(0);
  13.   } else {
  14.     // 销毁其他窗口
  15.     BrowserWindow.getAllWindows().forEach((w) => {
  16.       if (w.id !== mainWin.id) w.destroy();
  17.     });
  18.     const options = {
  19.       type: 'info',
  20.       title: '渲染器进程崩溃',
  21.       message: '这个进程已经崩溃.',
  22.       buttons: ['重载', '关闭']
  23.     }
  24.     dialog.showMessageBox(options, (index) => {
  25.       if (index === 0) mainWin.reload();
  26.       else mainWin.close();
  27.     })
  28.   }
  29. }
  30. export default function () {
  31.   const mainWindow = BrowserWindow.fromId(global.mainId);
  32.   mainWindow.webContents.on('crashed', () => {
  33.     const errorMessage = crashReporter.getLastCrashReport();
  34.     console.error('程序崩溃了!', errorMessage); // 可单独上传日志
  35.     reloadWindow(mainWindow);
  36.   });
  37. }
复制代码
[h2]10.2 最小化到托盘[/h2]有的时候我们并不想让用户通过点关闭按钮的时候就关闭程序,而是把程序最小化到托盘,在托盘上做真正的退出操作。
首先要监听窗口的关闭事件,阻止用户关闭操作的默认行为,将窗口隐藏。
  1. function checkQuit(mainWindow, event) {
  2.   const options = {
  3.     type: 'info',
  4.     title: '关闭确认',
  5.     message: '确认要最小化程序到托盘吗?',
  6.     buttons: ['确认', '关闭程序']
  7.   };
  8.   dialog.showMessageBox(options, index => {
  9.     if (index === 0) {
  10.       event.preventDefault();
  11.       mainWindow.hide();
  12.     } else {
  13.       mainWindow = null;
  14.       app.exit(0);
  15.     }
  16.   });
  17. }
  18. function handleQuit() {
  19.   const mainWindow = BrowserWindow.fromId(global.mainId);
  20.   mainWindow.on('close', event => {
  21.     event.preventDefault();
  22.     checkQuit(mainWindow, event);
  23.   });
  24. }
复制代码
这时程序就再也找不到了,任务托盘中也没有我们的程序,所以我们要先创建好任务托盘,并做好事件监听。
windows平台使用
  1. ico
复制代码
文件可以达到更好的效果
  1. export default function createTray() {
  2.   const mainWindow = BrowserWindow.fromId(global.mainId);
  3.   const iconName = process.platform === 'win32' ? 'icon.ico' : 'icon.png'
  4.   tray = new Tray(path.join(global.__dirname, iconName));
  5.   const contextMenu = Menu.buildFromTemplate([
  6.     {
  7.       label: '显示主界面', click: () => {
  8.         mainWindow.show();
  9.         mainWindow.setSkipTaskbar(false);
  10.       }
  11.     },
  12.     {
  13.       label: '退出', click: () => {
  14.         mainWindow.destroy();
  15.         app.quit();
  16.       }
  17.     },
  18.   ])
  19.   tray.setToolTip('electron-react');
  20.   tray.setContextMenu(contextMenu);
  21. }
复制代码
[h1]十一、扩展能力[/h1]

在很多情况下,你的应用程序要和外部设备进行交互,一般情况下厂商会为你提供硬件设备的开发包,这些开发包基本上都是通过
  1. C++
复制代码
编写,在使用
  1. electron
复制代码
开发的情况下,我们并不具备直接调用
  1. C++
复制代码
代码的能力,我们可以利用
  1. node-ffi
复制代码
来实现这一功能。
  1. node-ffi
复制代码
提供了一组强大的工具,用于在
  1. Node.js
复制代码
环境中使用纯
  1. JavaScript
复制代码
调用动态链接库接口。它可以用来为库构建接口绑定,而不需要使用任何
  1. C++
复制代码
代码。
注意
  1. node-ffi
复制代码
并不能直接调用
  1. C++
复制代码
代码,你需要将
  1. C++
复制代码
代码编译为动态链接库:在
  1. Windows
复制代码
下是
  1. Dll
复制代码
,在
  1. Mac OS
复制代码
下是
  1. dylib
复制代码
  1. ,Linux
复制代码
  1. so
复制代码
  1. node-ffi
复制代码
加载
  1. Library
复制代码
是有限制的,只能处理
  1. C
复制代码
风格的
  1. Library
复制代码
下面是一个简单的实例:
  1. const ffi = require('ffi');
  2. const ref = require('ref');
  3. const SHORT_CODE = ref.refType('short');
  4. const DLL = new ffi.Library('test.dll', {
  5.     Test_CPP_Method: ['int', ['string',SHORT_CODE]],
  6.   })
  7. testCppMethod(str: String, num: number): void {
  8.   try {
  9.     const result: any = DLL.Test_CPP_Method(str, num);
  10.     return result;
  11.   } catch (error) {
  12.     console.log('调用失败~',error);
  13.   }
  14. }
  15. this.testCppMethod('ConardLi',123);
复制代码
上面的代码中,我们用
  1. ffi
复制代码
包装
  1. C++
复制代码
接口生成的动态链接库
  1. test.dll
复制代码
,并使用
  1. ref
复制代码
进行一些类型映射。
使用
  1. JavaScript
复制代码
调用这些映射方法时,推荐使用
  1. TypeScript
复制代码
来约定参数类型,因为弱类型的
  1. JavaScript
复制代码
在调用强类型语言的接口时可能会带来意想不到的风险。
借助这一能力,前端开发工程师也可以在
  1. IOT
复制代码
领域一展身手了~
[h1]十二、环境选择[/h1]一般情况下,我们的应用程序可能运行在多套环境下(
  1. production
复制代码
  1. beta
复制代码
  1. uat
复制代码
  1. moke
复制代码
  1. development
复制代码
…),不同的开发环境可能对应不同的后端接口或者其他配置,我们可以在客户端程序中内置一个简单的环境选择功能来帮助我们更高效的开发。


具体策略如下:


  • 在开发环境中,我们直接进入环境选择页面,读取到选择的环境后进行响应的重定向操作
  • 在菜单保留环境选择入口,以便在开发过程中切换
  1. const envList = ["moke", "beta", "development", "production"];
  2. exports.envList = envList;
  3. const urlBeta = 'https://wwww.xxx-beta.com';
  4. const urlDev = 'https://wwww.xxx-dev.com';
  5. const urlProp = 'https://wwww.xxx-prop.com';
  6. const urlMoke = 'https://wwww.xxx-moke.com';
  7. const path = require('path');
  8. const pkg = require(path.resolve(global.__dirname, 'package.json'));
  9. const build = pkg['build-config'];
  10. exports.handleEnv = {
  11.   build,
  12.   currentEnv: 'moke',
  13.   setEnv: function (env) {
  14.     this.currentEnv = env
  15.   },
  16.   getUrl: function () {
  17.     console.log('env:', build.env);
  18.     if (build.env === 'production' || this.currentEnv === 'production') {
  19.       return urlProp;
  20.     } else if (this.currentEnv === 'moke') {
  21.       return urlMoke;
  22.     } else if (this.currentEnv === 'development') {
  23.       return urlDev;
  24.     } else if (this.currentEnv === "beta") {
  25.       return urlBeta;
  26.     }
  27.   },
  28.   isDebugger: function () {
  29.     return build.env === 'development'
  30.   }
  31. }
复制代码
[h1]十三、打包[/h1]最后也是最重要的一步,将写好的代码打包成可运行的
  1. .app
复制代码
  1. .exe
复制代码
可执行文件。
这里我把打包氛围两部分来做,渲染进程打包和主进程打包。
[h2]13.1 渲染进程打包和升级[/h2]一般情况下,我们的大部分业务逻辑代码是在渲染进程完成的,在大部分情况下我们仅仅需要对渲染进程进行更新和升级而不需要改动主进程代码,我们渲染进程的打包实际上和一般的
  1. web
复制代码
项目打包没有太大差别,使用
  1. webpack
复制代码
打包即可。
这里我说说渲染进程单独打包的好处:
打包完成的
  1. html
复制代码
  1. js
复制代码
文件,我们一般要上传到我们的前端静态资源服务器下,然后告知服务端我们的渲染进程有代码更新,这里可以说成渲染进程单独的升级。
注意,和壳的升级不同,渲染进程的升级仅仅是静态资源服务器上
  1. html
复制代码
  1. js
复制代码
文件的更新,而不需要重新下载更新客户端,这样我们每次启动程序的时候检测到离线包有更新,即可直接刷新读取最新版本的静态资源文件,即使在程序运行过程中要强制更新,我们的程序只需要强制刷新页面读取最新的静态资源即可,这样的升级对用户是非常友好的。
这里注意,一旦我们这样配置,就意味着渲染进程和主进程打包升级的完全分离,我们在启动主窗口时读取的文件就不应该再是本地文件,而是打包完成后放在静态资源服务器的文件。
为了方便开发,这里我们可以区分本地和线上加载不同的文件:
  1. function getVersion (mac,current){
  2.   // 根据设备mac和当前版本获取最新版本
  3. }
  4. export default function () {
  5.   if (build.env === 'production') {
  6.     const version = getVersion (mac,current);
  7.     return 'https://www.xxxserver.html/electron-react/index_'+version+'.html';
  8.   }
  9.   return url.format({
  10.     protocol: 'file:',
  11.     pathname: path.join(__dirname, 'env/environment.html'),
  12.     slashes: true,
  13.     query: { debugger: build.env === "development" }
  14.   });
  15. }
复制代码
具体的
  1. webpack
复制代码
配置这里就不再贴出,可以到我的
  1. github
复制代码
  1. electron-react
复制代码
  1. /scripts
复制代码
目录下查看。
这里需要注意,在开发环境下我们可以结合
  1. webpack
复制代码
  1. devServer
复制代码
  1. electron
复制代码
命令来启动
  1. app
复制代码
  1.   devServer: {
  2.     contentBase: './assets/',
  3.     historyApiFallback: true,
  4.     hot: true,
  5.     port: PORT,
  6.     noInfo: false,
  7.     stats: {
  8.       colors: true,
  9.     },
  10.     setup() {
  11.       spawn(
  12.         'electron',
  13.         ['.'],
  14.         {
  15.           shell: true,
  16.           stdio: 'inherit',
  17.         }
  18.       )
  19.         .on('close', () => process.exit(0))
  20.         .on('error', e => console.error(e));
  21.     },
  22.   },//...
复制代码
[h2]13.2 主进程打包[/h2]主进程,即将整个程序打包成可运行的客户端程序,常用的打包方案一般有两种,
  1. electron-packager
复制代码
  1. electron-builder
复制代码
  1. electron-packager
复制代码
在打包配置上我觉得有些繁琐,而且它只能将应用直接打包为可执行程序。
这里我推荐使用
  1. electron-builder
复制代码
,它不仅拥有方便的配置
  1. protocol
复制代码
的功能、内置的
  1. Auto Update
复制代码
、简单的配置
  1. package.json
复制代码
便能完成整个打包工作,用户体验非常不错。而且
  1. electron-builder
复制代码
不仅能直接将应用打包成
  1. exe app
复制代码
等可执行程序,还能打包成
  1. msi dmg
复制代码
等安装包格式。
你可以在
  1. package.json
复制代码
方便的进行各种配置:
  1.   "build": {
  2.     "productName": "electron-react", // app中文名称
  3.     "appId": "electron-react",// app标识
  4.     "directories": { // 打包后输出的文件夹
  5.       "buildResources": "resources",
  6.       "output": "dist/"
  7.     }
  8.     "files": [ // 打包后依然保留的源文件
  9.       "main_process/",
  10.       "render_process/",
  11.     ],
  12.     "mac": { // mac打包配置
  13.       "target": "dmg",
  14.       "icon": "icon.ico"
  15.     },
  16.     "win": { // windows打包配置
  17.       "target": "nsis",
  18.       "icon": "icon.ico"
  19.     },
  20.     "dmg": { // dmg文件打包配置
  21.       "artifactName": "electron_react.dmg",
  22.       "contents": [
  23.         {
  24.           "type": "link",
  25.           "path": "/Applications",
  26.           "x": 410,
  27.           "y": 150
  28.         },
  29.         {
  30.           "type": "file",
  31.           "x": 130,
  32.           "y": 150
  33.         }
  34.       ]
  35.     },
  36.     "nsis": { // nsis文件打包配置
  37.       "oneClick": false,
  38.       "allowToChangeInstallationDirectory": true,
  39.       "shortcutName": "electron-react"
  40.     },
  41.   }
复制代码
执行
  1. electron-builder
复制代码
打包命令时,可指定参数进行打包。
  1.   --mac, -m, -o, --macos   macOS打包
  2.   --linux, -l              Linux打包
  3.   --win, -w, --windows     Windows打包
  4.   --mwl                    同时为macOS,Windows和Linux打包
  5.   --x64                    x64 (64位安装包)
  6.   --ia32                   ia32(32位安装包)
复制代码
关于主进程的更新你可以使用
  1. electron-builder
复制代码
自带的
  1. Auto Update
复制代码
模块,在
  1. electron-react
复制代码
也实现了手动更新的模块,由于篇幅原因这里就不再赘述,如果有兴趣可以到我的
  1. github
复制代码
查看
  1. main
复制代码
下的
  1. update
复制代码
模块。
[h2]13.3 打包优化[/h2]
  1. electron-builder
复制代码
打包出来的
  1. App
复制代码
要比相同功能的原生客户端应用体积大很多,即使是空的应用,体积也要在
  1. 100mb
复制代码
以上。原因有很多:
第一点;为了达到跨平台的效果,每个
  1. Electron
复制代码
应用都包含了整个
  1. V8
复制代码
引擎和
  1. Chromium
复制代码
内核。
第二点:打包时会将整个
  1. node_modules
复制代码
打包进去,大家都知道一个应用的
  1. node_module
复制代码
体积是非常庞大的,这也是使得
  1. Electron
复制代码
应用打包后的体积较大的原因。
第一点我们无法改变,我们可以从第二点对应用体积进行优化:
  1. Electron
复制代码
在打包时只会将
  1. denpendencies
复制代码
的依赖打包进去,而不会将
  1. devDependencies
复制代码
中的依赖进行打包。所以我们应尽可能的减少
  1. denpendencies
复制代码
中的依赖。在上面的进程中,我们使用
  1. webpack
复制代码
对渲染进程进行打包,所以渲染进程的依赖全部都可以移入
  1. devDependencies
复制代码

另外,我们还可以使用双
  1. packajson.json
复制代码
的方式来进行优化,把只在开发环境中使用到的依赖放在整个项目的根目录的
  1. package.json
复制代码
下,将与平台相关的或者运行时需要的依赖装在
  1. app
复制代码
目录下。具体详见two-package-structure。
[h1]参考[/h1]
  • https://electronjs.org/docs
  • http://jlord.us/essential-electron/
  • https://imweb.io/topic/5b9f500cc2ec8e6772f34d79
  • https://www.jianshu.com/p/1ece6fd7a80c
  • https://zhuanlan.zhihu.com/p/52991793
本项目源码地址:https://github.com/ConardLi/electron-react
[h1]小结[/h1]希望你阅读本篇文章后可以达到以下几点:
  • 了解
    1. Electron
    复制代码
    的基本运行原理
  • 掌握
    1. Electron
    复制代码
    开发的核心基础知识
  • 了解
    1. Electron
    复制代码
    关于弹框、打印、保护、打包等功能的基本使用
文中如有错误,欢迎在评论区指正,如果这篇文章帮助到了你,请点在看支持。
推荐关注我的微信公众号【code秘密花园】,每天推送高质量文章,我们一起交流成长。
关注公众号后回复【加群】拉你进入优质前端交流群。
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:25
帖子:5
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP