作者:李晓健。苏宁视频云前端部门经理。拥有7年前端业务研发和架构经验,目前负责苏宁云视频前端研发和架构工作。
Electron简介
Electron 是一个使用 JavaScript,HTML 和 CSS 等 Web 技术创建原生桌面程序的框架。官网是https://electronjs.org/
Electron就是使用Web技术来开发,然后打包成桌面客户端的一个框架。它渲染Web内容使用的是Chromium,Chromium基本上和大家常用的chrome浏览器差不多,并且Electron在打包时是将Chromium嵌入到了应用里,所以它并不依赖用户机器上安装的浏览器,大家都知道做Web开发,最大的一个问题就是兼容性的问题,经常相同的代码在不同操作系统中不同的浏览器里,甚至不同版本的浏览器里都可能会出现不同的效果。Electron相当于是把一个固定版本的浏览器嵌入到应用中,所以他就不存在兼容性问题,只要我们开发时写的代码可以正常运行,就不存在打包后在用户的机器上因为兼容性问题无法使用的情况。
Electron支持Node.js的原生模块,也就是说可以在Electron应用中直接使用Node.js的模块。Web应用为了安全等一系列的问题考虑,限制了很多的功能,比如本地文件的读写、操作系统中硬件设备的调用等等,但是Node.js并没有这些限制。也就是说在Electron应用中,我们不光可以使用Web技术的特点,还能够突破Web技术的限制,使用Node.js的模块功能,使应用功能更加丰富。
Electron开发出来的应用是跨平台的,当应用开发完成后,我们可以将同一套代码打包成Linux、macOS、Windows平台上的可执行程序或安装包。
Electron中比较重要的概念
学习任何一门框架,里面或多或少都会有一些新的概念,只有把这些新的概念都弄懂了,才能更好的掌握和运用这门框架,Electron中新的概念并不是很多,接下来我就就来说说最重要的3个概念。
1. 主进程:Electron应用中运行package.json的main脚本的进程被称为主进程,在主进程中运行的脚本通过创建web页面来展示用户界面。一个 Electron 应用总是有且只有一个主进程。
2. 渲染进程:Electron应用中是通过在主进程里创建web页面来展示用户界面的,在主进程中可以通过BrowserWindow 实例创建页面,这里的一个web页面就是运行在一个渲染进程里的,一个Electron应用可以包含多个web页面,所以一个Electron 应用中是可以有多个渲染进程的,当一个 BrowserWindow 实例被销毁后,相应的渲染进程也会被终止。
3. IPC:既然有主进程和渲染进程,而主进程管控的是一个应用全局的设置和方法,而渲染进程只在一个web页面中存在,那他们肯定不会始终完全孤立的存在,有时也是需要有通信的,比如我在页面做了一个操作,整个应用需要去做一个响应,就是就需要用到主进程和渲染进程的通信。就里就有一个概念IPC(Inter-Proess Communication),进程中通信。IPC就提供了相应的方法来供主进程和渲染进程间进行通信。
所以他们大概的关系就是一个应用中有一个主进程,然后主进程可以创建多个渲染进程,主进程和渲染进程间通过IPC来通信。如下图:
Electron的安装
Electron是以Node.js中的模块的方式进行发布的,所以他的安装也非常简单,通过npm来进行安装就可以了。既然Electron是通过Node.js的模块起先发布的,那它的安装就需要依赖Node.js的环境,也就是需要在我们的电脑上安装Node.js,安装Node.js直接去其官网( https://nodejs.org)下载相应的安装包,然后按照默认的设置一路安装就行了,在安装Node.js的同时也会安装上npm,安装完成后检测Node.js和npm是否安装成功,只需要在命令行工具拦执行相应的版本查看命令就行了,如果能看到有版本号输出,则说明安装成功。如下图:
接下来我们就可以直接使用npm来安装我们所需要的模块了。首先进入到我们的项目开发目录,然后初始化好项目(创建好package.json文件),然后在该目录执行
npm install electron –save
然后等待安装完成就行了。
在这个安装过程中,可能由于网络问题会安装失败,我们可以修改npm的镜像源,将npm的镜像源修改成国内的镜像源:在命令行执行 npm config set registry http://registry.npm.taobao.org/ 或者安装cnpm,然后通过cnpm来安装Node.js的模块。
安装完成后,在命令行执行
electron -v
可以看到electron的版本号,就说明Electron安装成功了。
Electron项目的搭建
Electron安装好就,就可以搭建项目了。官网给我们提供了一个空的种子项目,这个项目里面非常的干净,所以可以直接使用,首先我们通过命令行进入我们工作目录,克隆下这个项目(前提是我们的电脑需要安装git),执行
git clonehttps://github.com/electron/electron-quick-start.git
克隆完成后,我们的工作目录中就多了一个 electron-quick-start 目录,这个目录就是
我们的项目目录,这里面有几个文件是我们项目需要用到的文件
1. package.json:项目的一些依赖和配置文件,里面有一些我们项目的基本信息,我们可以根据自己项目的情况来修改这里的值 bash
{
"name": "electron-quick-start", //项目的名称
"version": "1.0.0", //项目的版本号
"description": "A minimal Electron application", //项目的描述
"main": "main.js", //主入口文件
"scripts": { //快捷脚本
"start": "electron ."
},
"repository": "https://github.com/electron/electron-quick-start", //代码仓库
"keywords": [ //项目的关键词
"Electron",
"quick",
"start",
"tutorial",
"demo"
],
"author": "GitHub", //项目的作者
"license": "CC0-1.0", //项目的许可协议
"devDependencies": { //项目的依赖
"electron": "^2.0.0"
}
}
2. main.js:这个文件是项目的启动文件,也是项目的入口文件,该文件就是运行在主进程中的。
// 引入electron中的API
const {app, BrowserWindow} = require(‘electron’)
//定义一个web窗口 这个窗口就运行在一个一个渲染进程中
let mainWindow
//创建web窗口的方法
function createWindow () {
// 通过BrowserWindow来创建一个窗口
mainWindow = new BrowserWindow({width: 800, height: 600})
// 在web窗口中加载html页面
mainWindow.loadFile(‘index.html’)
// 监听窗口关闭的事件
mainWindow.on(‘closed’, function () {
mainWindow = null
})
}
// 当应用准备好了 就调用创建web窗口的方法
app.on(‘ready’, createWindow)
// 当所有的窗口都关闭后,就退出应用
app.on(‘window-all-closed’, function () {
//在非MAC平台下退出应用
//在MAC平台下所有窗口关闭 应用并不需要退出,它还可以通过dock来重新激活应用
if (process.platform !== ‘darwin’) {
app.quit()
}
})
//当应用被激活事件
app.on(‘activate’, function () {
//如果窗口不存在 就创建一个窗口
if (mainWindow === null) {
createWindow()
}
})
3. index.html:这是web窗口里展示需要展示的内容,当然这里也可以是一个远程html的url
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
<!-- All of the Node.js APIs are available in this renderer process. -->
We are using Node.js <script>document.write(process.versions.node)</script>,
Chromium <script>document.write(process.versions.chrome)</script>,
and Electron <script>document.write(process.versions.electron)</script>.
<script>
// You can also require other files to run in this process
require('./renderer.js')
</script>
</body>
</html>
这里就是一个普通的html,但是这里面也有和普通html不同之处,我们可以看到html的script标签里有用到process.xxx.xxx;并且前面没没有定义process,这里确可以直接访问,html里引入script用了require(‘./renderer.js’)。这些写法在我们平常的script里都会报错的,但是这里确可以正常运行的,原因就是我们前面说过的Electron应用中可以直接使用Node.js的Api,这里的process就是Node.js里的变量,require就是Node.js中引入外部文件的关键字。
renderer.js:这个文件是被引入到index.html中的,所以它就是用来写html中逻辑的文件,所以这个文件也是运行在渲染进程中的。
我们上面说到的4个文件的文件名和路径并不是固定的,也可以根据项目需要进行修改,他们的关系是main.js 就是 package.json中main属性对应的值,如果需要修改main.js的名称,这两处需要同时修改。index.html 就是在main.js中 mainWindow.loadFile(‘index.html’) 引入的文件,如果需要修改这两处也需要同时修改。renderer.js就是在index.html中require(‘./renderer.js’) 引入的js文件,当然这里也可以引入多个js谁的,和我们普通的html一样。
文件作用和关系都理清楚了,我们就可以安装依赖了。在命令行执行
npm install
等待安装完成之后,就可以运行项目了,在命令行执行
npm run start
这里的start 就是我们在package.json中scripts属性中配置的值 “start”: “electron .”,我们在命令行运行 npmrun start 就相当于我们运行 npm run electron . ;因为有时有些命令比较长或带了比较多的参数,所以我们在package.json中配置一些别名会大大减少我们在命令行的输入。等命令运行完成我们就可以看到我们的桌面上打开了一个客户端,如下图:
这样我们的一个初始项目就算搭建完成了。
Electron中常用的功能模块
electron不光是可以将我们的页面打包成客户端应用,它也为我们提供了大量的功能模块,方便我们的开发,下面我们就简单介绍一下用的比较多的功能模块。
1. app: 控制应用程序的事件生命周期,用于监听应用的的事件,该模块在主进程中。
2. BrowserWindow:创建和控制浏览器窗口,在创建窗口时可以传入很多的参数,来控制窗口的样式和行为该模块在主进程中。
3. ipcMain:从主进程到渲染进程的异步通信,该模块在主进程中。
4. ipcRenderer:从渲染进程到主进程的异步通信,该模块在渲染进程中。
5. remote:在渲染进程中引入主进程模块,该模块在渲染进程。
6. dialog:显示用于打开和保存文件、警报等的本机系统对话框,该模块在主进程中。
7. crashReporter:将崩溃日志提交给远程服务器
8. globalShortcut:当应用程序没有键盘焦点时监听全局键盘事件,该模块在主进程中。
9. Menu:创建原生应用菜单和上下文菜单,该模块在主进程中。
10. desktopCapturer:可以访问那些用于从桌面上捕获音频和视频的媒体源信息,该模块在渲染进程中。
11. net:使用Chromium的原生网络库发出HTTP/ HTTPS请求,该模块在主进程中。
12. autoUpdater:使应用程序能够自动更新,该模块在主进程中。
以上的这些模块都是比较常用的模块,所有的功能模块并不是在任何地方都可以调用,有些模块在主进程中可以直接引入,有些模块只能在渲染进程中引入,当然也有些模块可以直接主进程和渲染进程中引入使用。这些在主进程中的模块也是可以在渲染进程中引入的,不过不能直接引入,需要通过remote模块来间接引入,所以在主进程中的模块也是可以在渲染进程中使用,例如我们想在渲染进程中使用BrowserWindow:
//我们需要先引入remote模块
const {remote} = require('electron');
//再从remote模块中引入BrowserWindow
const {BrowserWindow} = remote
let win = new BrowserWindow({width: 800, height: 600})
win.loadURL('http://www,pptvyun.com')
更多的功能模块和使用方法可以参考Electron的文档:https://electronjs.org/docs
Electron的Demo
Electron的大概内容已经介绍的差不多了,接下来我们就使用Electron来做一个拍照保存的小demo,能正常运行需要您的电脑上有摄像头。我们的这个demo就基于前面搭建好的空项目来做。前面我们说过,项目的文件名和路径并不是固定的,接下来我们就来调整一下前面那个项目的目录结构,我们将我们需要写代码都放到src目录下,调整个目录如下:
我们将html代码放到view目录下,将样式文件放到style目录下,将图片放入到images目录下,将我们的js代码放到scripts目录下,将main.js改名为index.js(这里改文件只是为了说明这个文件名是可以修改的,并不是固定的),然后直接放到src目录下。
package.json 修改如下
{
"name": "take-photo",
"version": "0.0.1",
"description": "A take photo Electron application",
"main": "src/index.js",
"scripts": {
"start": "electron ."
},
"repository": "",
"keywords": ["Electron","demo"],
"author": "pptvyun",
"license": "MIT",
"devDependencies": {
"electron": "^2.0.0"
}
}
index.js 修改如下
// 引入electron中的API
const {app, BrowserWindow} = require(‘electron’)
//定义一个web窗口 这个窗口就运行在一个一个渲染进程中
let mainWindow
//创建web窗口的方法
function createWindow () {
// 通过BrowserWindow来创建一个窗口
mainWindow = new BrowserWindow({width: 800, height: 600})
// 在web窗口中加载html页面
mainWindow.loadFile(`${__dirname}/view/index.html`)
// 监听窗口关闭的事件
mainWindow.on(‘closed’, function () {
mainWindow = null
})
}
// 当应用准备好了 就调用创建web窗口的方法
app.on(‘ready’, createWindow)
// 当所有的窗口都关闭后,就退出应用
app.on(‘window-all-closed’, function () {
//在非MAC平台下退出应用
//在MAC平台下所有窗口关闭 应用并不需要退出,它还可以通过dock来重新激活应用
if (process.platform !== ‘darwin’) {
app.quit()
}
})
//当应用被激活事件
app.on(‘activate’, function () {
//如果窗口不存在 就创建一个窗口
if (mainWindow === null) {
createWindow()
}
})
index.html 修改如下
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
<!-- All of the Node.js APIs are available in this renderer process. -->
We are using Node.js <script>document.write(process.versions.node)</script>,
Chromium <script>document.write(process.versions.chrome)</script>,
and Electron <script>document.write(process.versions.electron)</script>.
<script>
//这里修改了renderer.js的引入路径
require('../scripts/renderer.js')
</script>
</body>
</html>
接下来我们就根据我们的业务来写具体的代码,首先我们的代码肯定是需要调试的,Electron使用的是Chromium来做渲染的,所以它也给我们提供了chrome的开发者工具供我们使用。开发完成后的代码如下
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>take photo</title>
<link rel="stylesheet" href="../style/index.css">
</head>
<body>
<header>拍照保存</header>
<div class="content">
<video src="" id="video"></video>
<canvas width="250" height="187" id="canvas"></canvas>
<div class="button-box">
<a href="javascript:;" id="openCamera" class="button">打开摄像头</a>
<a href="javascript:;" id="takePhoto" class="button">拍摄照片</a>
<a href="javascript:;" id="savePhoto" class="button">保存照片</a>
</div>
</div>
<script>
require('../scripts/renderer.js')
</script>
</body>
</html>
src/style/index.css
src/index.js
// 引入electron中的API
const {app, BrowserWindow,globalShortcut} = require('electron')
//定义一个web窗口 这个窗口就运行在一个一个渲染进程中
let mainWindow
//创建web窗口的方法
function createWindow () {
// 通过BrowserWindow来创建一个窗口
mainWindow = new BrowserWindow({width: 800, height: 600})
// 在web窗口中加载html页面
mainWindow.loadFile(`${__dirname}/view/index.html`)
// 监听窗口关闭的事件
mainWindow.on('closed', function () {
mainWindow = null
})
//这里我们注册一个打开 开发者工具有快捷键 win下是Ctrl+F12 macOS下是command+f12
globalShortcut.register('CommandOrControl+F12', () => {
mainWindow.webContents.openDevTools()
})
}
// 当应用准备好了 就调用创建web窗口的方法
app.on('ready', createWindow)
// 当所有的窗口都关闭后,就退出应用
app.on('window-all-closed', function () {
//在非MAC平台下退出应用
//在MAC平台下所有窗口关闭 应用并不需要退出,它还可以通过dock来重新激活应用
if (process.platform !== 'darwin') {
app.quit()
}
})
//当应用被激活事件
app.on('activate', function () {
//如果窗口不存在 就创建一个窗口
if (mainWindow === null) {
createWindow()
}
})
src/sctipts/renderer.js
//引入Electron中的dialog 用来显示消息提示
const {dialog} = require('electron').remote
//引入Node.js的fs模块 用来写文件
const fs = require('fs');
//页面打开摄像头的按钮
const button = document.querySelector('#openCamera');
//页面拍摄照片的按钮
const takephoto = document.querySelector('#takePhoto');
//页面保存照片的按钮
const saveImage = document.querySelector('#savePhoto');
//页面的video标签,用来展示摄像头拍摄的画面
const video = document.querySelector('#video');
//页面的canvas标签,用来展示拍摄的照片
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
//获取摄像头的参数
const mediaConfig = {
video: true
}
//点击打开摄像头的按钮
button.addEventListener('click',e=>{
//调用电脑的摄像头
navigator.mediaDevices.getUserMedia(mediaConfig)
.then(function(stream) {
//将摄像头拍摄的内容给到video标签,并从video标签上播放
video.srcObject = stream;
video.onloadedmetadata = function(e) {
video.play();
};
})
.catch(function(err) {
alert('打开挺像头失败')
});
})
//点击拍照按钮
takephoto.addEventListener('click',e=>{
//将video上的画面画到canvas上
ctx.drawImage(video,0,0,250,187);
})
//点击保存图片按钮
saveImage.addEventListener('click',e=>{
//选择图片的保存路径
dialog.showOpenDialog({properties:['openDirectory']},filePaths=>{
if(filePaths && filePaths[0]){
//保存图片的目录
const floder = filePaths[0]+'/';
//奖canvas上的图片转成base64编码
let dataURL = canvas.toDataURL('image/png');
var regex = /^data:.+\/(.+);base64,(.*)$/;
if(dataURL){
var matches = dataURL.match(regex);
var buffer = new Buffer(matches[2], 'base64');
//将文件流写入选择的目录里
const file = floder+Date.now()+'.' + matches[1];
fs.writeFile(file, buffer,err=>{
if(err){
dialog.showErrorBox("错误提示", '图片保存失败')
}else{
//图片保存成功的提示
dialog.showMessageBox({
type:'info',
title:'提示信息',
message:'图片保存成功,文件保存在'+file,
})
}
});
}
}
})
})
代码开发完成后,运行项目,在命令行输入
npm run start
就可以看到项目结果了,页面会出现3个按钮,点击打开摄头,客户端上就会出现摄像头拍摄的内容,然后点击保存照片按钮,客户端就会出现一张静态的图片,然后点击保存按钮,这张图片就会保存在我们选择的目录下。效果如下图:
Electron项目的构建
这个demo开发到这里就算完成了,但是我们的客户端还是需要给别人来使用,总不能让别人把我们的代码拷过去,然后安装依赖,在命令行运行npm run start吧,我们常见的客户端都是有一个安装程序,安装之后就可以直接双击运行的,或者是一个绿色版,有一个可执行程序,我们双击就可以运行的。接下来我们就也把我们的demo打包成一个安装程序,可供用户安装使用。
将Electron打包成安装程序有多种方式,今天我们说的是通过 electron-builder 模块来打包,electron-builder也是一个Node.js的模块,所以我们可以直接通过npm来安装,在命令行运行
npm install electron-builder –save-dev
等待安装完成就可以了,它的文档在https://github.com/electron-userland/electron-builder ,它可以打包出window、macOS、Linux上的可执行程序。然后我们参照文档修改配置文件,这里只需要修改package.json就好。修改后如下
{
"name": "take-photo",
"version": "0.0.1",
"description": "A take photo Electron application",
"main": "src/index.js",
"scripts": {
"start": "electron .",
"build-all": "electron-builder -mwl",
"build-win": "electron-builder --win --x64",
"build-mac": "electron-builder --mac --x64",
"build-linux": "electron-builder --linux --x64"
},
"build": {
"appId": "com.pptvyun.test",
"copyright": "pptvyun",
"productName": "demo",
"mac": {
"target": "dmg"
},
"dmg": {
"window": {
"x": 200,
"y": 200,
"width": 800,
"height": 600
}
},
"win": {
"icon": "logo.png",
"target": "nsis"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"artifactName": "Setup-${productName}-${version}-${arch}.${ext}"
},
"snap": {}
},
"repository": "",
"keywords": [
"Electron",
"demo"
],
"author": "pptvyun",
"license": "MIT",
"devDependencies": {
"electron": "^2.0.0",
"electron-builder": "^20.19.2"
}
}
在scripts中添加了在各个平台上的打包脚本,添加了build属性,用来配置在各个平台上的打包参数。我们用打包出window平台的安装程序为例,在命令行运行
npm run build-win
等待打包完成,第一次可能会有点慢,因为它需要下载一些依赖,打包完成后我们的项目里就会多出来一个build的目录,当然这个目录是我们在package.json中配置的,这个目录里就是打包之后的文件,里面有一个win-unpacked的目录,这个目录里有一个demo.exe的文件,这个文件就是可执行程序,这个win-unpacked目录里的文件就相当于是我们平常见的绿色免安装版,直接双击demo.exe就可以运行这个demo。还有一个Setup-demo-0.0.1.exe文件,这个文件就是我们需要的安装程序,双击它就可以运行安装,安装完成后也会在我们的桌面创建快捷方式,不同平台的打包命令可能需要到相应的平台上去执行,比如在window上执行macOS的打包命令可能会失败。
至此,我们的demo就算开发完成了,当然这里还有一些细节上的东西需要注意,比如我们的应用需要在各大平台上发布,供其他用户来下载安装,就需要对代码进行签名,代码签名具体可参考文档https://electronjs.org/docs/tutorial/code-signing ,这里的客户端界面我们用的是默认的,当然我们也可以做一个无边框的应用,然后自定义客户端的界面,还有自动升级等等功能,Electron都是可以支持的,可以查看它的官方文档做出功能丰富的客户端面应用。