4000-520-616
欢迎来到免疫在线!(蚂蚁淘生物旗下平台)  请登录 |  免费注册 |  询价篮
主营:原厂直采,平行进口,授权代理(蚂蚁淘为您服务)
咨询热线电话
4000-520-616
当前位置: 首页 > 新闻动态 >
新闻详情
使用Electron打造跨平台桌面应用_晴树的专栏-CSDN博客
来自 : CSDN技术社区 发布时间:2021-03-24

https://uinika.github.io/web/server/electron.html

早期桌面应用的开发主要借助原生 C/C API 进行 由于需要反复经历编译过程 且无法分离界面 UI 与业务代码 开发调试极为不便。后期出现的 QT 和 WPF 在一定程度上解决了界面代码分离和跨平台的问题 却依然无法避免较长时间的编译过程。近几年伴随互联网行业的迅猛发展 尤其是 NodeJS、Chromium 这类基于 W3C 标准开源应用的不断涌现 原生代码与 Web 浏览器开发逐步走向融合 Electron 正是在这种背景下诞生的。

\"\"

Electron 是由 Github 开发 通过将Chromium和NodeJS整合为一个运行时环境 实现使用 HTML、CSS、JavaScript 构建跨平台的桌面应用程序的目的。Electron 源于 2013 年 Github 社区提供的开源编辑器 Atom 后于 2014 年在社区开源 并在 2016 年的 5 月和 8 月 通过了 Mac App Store 和 Windows Store 的上架许可 VSCode、Skype 等著名开源或商业应用程序 都是基于 Electron 打造。为了方便编写测试用例 笔者在 Github 搭建了一个简单的 Electron 种子项目Octopus 读者可以基于此来运行本文涉及的示例代码。

\"construction\"

Getting Start

首先 让我们通过npm init和git init新建一个项目 然后通过如下npm语句安装最新的 Electron 稳定版。

1
➜ npm i -D electron latest

然后向项目目录下的package.json文件添加一条scripts语句 便于后面通过npm start命令启动 Electron 应用。

1
{ // ... ... author : Hank , main : resource/main.js , scripts : { start : electron .  devDependencies : { electron : ^3.0.7 

然后在项目根目录下新建resource文件夹 里面分别再建立index.html和main.js两个文件 最终形成如下的项目结构

1
electron-demo├── node_modules├── package.json├── package-lock.json├── README.md└── resource ├── index.html └── main.js

main.js是 Electron 应用程序的主入口点 当在命令行运行这段程序的时候 就会启动一个 Electron 的主进程 主进程当中可以通过代码打开指定的 Web 页面去展示 UI。

1
/** main.js */const { app, BrowserWindow } require( electron let mainWindow;app.on( ready , () { mainWindow new BrowserWindow({ width: 800, height: 500 }); mainWindow.setMenu(null); // mainWindow.loadFile( index.html // 隐藏Chromium菜单 // mainWindow.webContents.openDevTools() // 开启调试模式 mainWindow.on( closed , () { mainWindow null;app.on( window-all-closed , () { /* 在Mac系统用户通过Cmd Q显式退出之前 保持应用程序和菜单栏处于激活状态。*/ if (process.platform ! darwin ) { app.quit();app.on( activate , () { /* 当dock图标被点击并且不会有其它窗口被打开的时候 在Mac系统上重新建立一个应用内的window。*/ if (mainWindow null) { createWindow();

Web 页面index.html运行在自己的渲染进程当中 但是能够通过 NodeJS 提供的 API 去访问操作系统的原生资源 例如下面代码中的process.versions语句 这正是 Electron 能够跨平台执行的原因所在。

1
 !DOCTYPE html  html  head  meta charset UTF-8 /  title Hello Electron /title  /head  body  h1 你好 Electron /h1  !-- 所有NodeJS可用的API都可以通过renderer.js的process属性访问 --  当前Electron版本  script  document.write(process.versions.electron); /script  /h2  当前NodeJS版本 script  document.write(process.versions.node); /script  /h2  当前Chromium版本  script  document.write(process.versions.chrome); /script  /h2  script  // 这里也可以包含运行在当前进程里的其它文件 require( ./renderer.js  /script  /body  /html 

使用命令行工具执行npm start命令之后 上述 HTML 代码在笔者 Linux 操作系统内被渲染为如下界面。应用当中 可以通过CTRL R重新加载页面 或者使用CTRL SHIFT I打开浏览器控制台。

\"hello-electron\"

一个 Electron 应用的主进程只会有一个 渲染进程则会有多个。

主进程与渲染进程 主进程 main process 管理所有的 web 页面以及相应的渲染进程 它通过BrowserWindow来创建视图页面。渲染进程 renderer processes 用来运行页面 每个渲染进程都对应自己的BrowserWindow实例 如果实例被销毁那么渲染进程就会被终止。

\"structure\"

Electron 分别在主进程和渲染进程提供了大量 API 可以通过require语句方便的将这些 API 包含在当前模块使用。但是 Electron 提供的 API 只能用于指定进程类型 即某些 API 只能用于渲染进程 而某些只能用于主进程 例如上面提到的BrowserWindow就只能用于主进程。

1
const { BrowserWindow } require( electron ccc new BrowserWindow();

Electron 通过remote模块暴露一些主进程的 API 如果需要在渲染进程中创建一个BrowserWindow实例 那么就可以借助这个 remote 模块

1
const { remote } require( electron // 获取remote模块const { BrowserWindow } remote; // 从remote当中获取BrowserWindowconst browserWindow new BrowserWindow(); // 实例化获取的BrowserWindow

Electron 可以使用所有 NodeJS 上提供的 API 同样只需要简单的require一下。

1
const fs require( fs const root fs.readdirSync( / 

当然 NodeJS 上数以万计的 npm 包也同样在 Electron 可用 当然 如果是涉及到底层 C/C 的模块还需要单独进行编译 虽然这样的模块在 npm 仓库里并不多。

1
const S3 require( aws-sdk/clients/s3 

既然 Electron 本质是一个浏览器 跨平台中间件的组合 因此常用的前端调试技术也适用于 Electron 这里可以通过CTRL SHIFT I手动开启 Chromium 的调试控制台 或者通过下面代码在开发模式下自动打开

1
mainWindow.webContents.openDevTools(); // 开启调试模式
核心模块

本节将对require( electron )所获取的模块进行概述 便于后期进行分类查找。

app 模块

Electron 提供的app模块即提供了可用于区分开发和生产环境的app.isPackaged属性 也提供了关闭窗口的app.quit()和用于退出程序的app.exit()方法 以及window-all-closed和ready等 Electron 程序事件。

1
const { app } require( electron app.on( window-all-closed , () { app.quit(); // 当所有窗口关闭时退出应用程序

可以使用app.getLocale()获取当前操作系统的国际化信息。

BrowserWindow 模块

工作在主进程 用于创建和控制浏览器窗口。

1
// 主进程中使用如下方式获取。const { BrowserWindow } require( electron // 渲染进程中可以使用remote属性获取。const { BrowserWindow } require( electron ).remote;let window new BrowserWindow({ width: 800, height: 600 });window.on( closed , () { win null;// 加载远程URLwindow.loadURL( https://uinika.github.io/ // 加载本地HTMLwindow.loadURL( file://${__dirname}/app/index.html 

例如需要创建一个无边框窗口的 Electron 应用程序 只需将BrowserWindow配置对象中的frame属性设置为false即可

1
const { BrowserWindow } require( electron let window new BrowserWindow({ width: 800, height: 600, frame: false });window.show();

例如加载页面时 渲染进程第一次完成绘制时BrowserWindow会发出ready-to-show事件。

1
const { BrowserWindow } require( electron let win new BrowserWindow({ show: false });win.once( ready-to-show , () { win.show();

对于较为复杂的应用程序 ready-to-show事件的发出可能较晚 会让应用程序的打开显得缓慢。 这种情况下 建议通过backgroundColor属性设置接近应用程序背景色的方式显示窗口 从而获取更佳的用户体验。

1
const { BrowserWindow } require( electron let window new BrowserWindow({ backgroundColor: #272822 });window.loadURL( https://uinika.github.io/ 

如果想要创建子窗口 那么可以使用parent选项 此时子窗口将总是显示在父窗口的顶部。

1
const { BrowserWindow } require( electron let top new BrowserWindow();let child new BrowserWindow({ parent: top });child.show();top.show();

创建子窗口时 如果需要禁用父窗口 那么可以同时设置modal选项。

1
const { BrowserWindow } require( electron let child new BrowserWindow({ parent: top, modal: true, show: false });child.loadURL( https://uinika.github.io/ child.once( ready-to-show , () { child.show();
globalShortcut 模块

使用globalShortcut模块中的register()方法注册快捷键。

1
const { app, globalShortcut } require( electron app.on( ready , () { // 注册一个快捷键监听器。 globalShortcut.register( CommandOrControl Y , () { // 当按下Control Y键时触发该回调函数。

Linux 和 Windows 上【Command】键会失效, 所以要使用 CommandOrControl 既 MacOS 上是【Command】键 Linux 和 Windows 上是【Control】键 。

clipboard 模块

用于在系统剪贴板上执行复制和粘贴操作 包含有readText()、writeText()、readHTML()、writeHTML()、readImage()、writeImage()等方法。

1
const { clipboard } require( electron clipboard.writeText( 一些字符串内容 
globalShortcut 模块

用于在 Electron 应用程序失去键盘焦点时监听全局键盘事件 即在操作系统中注册或注销全局快捷键。

1
const { app, globalShortcut } require( electron app.on( ready , () { // 注册全局快捷键 const regist globalShortcut.register( CommandOrControl A , () { console.log( 快捷键被摁下  if (!regist) { console.log( 注册失败  // 检查快捷键是否注册成功 console.log(globalShortcut.isRegistered( CommandOrControl A app.on( will-quit , () { // 注销快捷键 globalShortcut.unregister( CommandOrControl A  // 清空所有快捷键 globalShortcut.unregisterAll();
ipcMain 与 ipcRenderer 模块

用于主进程到渲染进程的异步通信 下面是一个主进程与渲染进程之间发送和处理消息的例子

1
// 主进程const { ipcMain } require( electron ipcMain.on( asynchronous-message , (event, arg) { console.log(arg); // 打印 ping  event.sender.send( asynchronous-reply , pong ipcMain.on( synchronous-message , (event, arg) { console.log(arg); // 打印 ping  event.returnValue pong 
1
//渲染器进程 即网页const { ipcRenderer } require( electron console.log(ipcRenderer.sendSync( synchronous-message , ping // 打印 pong ipcRenderer.on( asynchronous-reply , (event, arg) { console.log(arg); // 打印 pong ipcRenderer.send( asynchronous-message , ping 

如果需要完成渲染器进程到主进程的异步通信 可以选择使用ipcRenderer对象。

Menu 与 MenuItem 模块

用于主进程 用于创建原生应用菜单和上下文菜单。

1
const { app, BrowserWindow, Menu } require( electron let mainWindow;const template [ label: 自定义菜单 , submenu: [{ label: 菜单项-1 }, { label: 菜单项-2 }]app.on( ready , () { mainWindow new BrowserWindow({ width: 800, height: 500 }); mainWindow.setMenu(Menu.buildFromTemplate(template)); mainWindow.loadFile( resource/index.html 

使用MenuItem类可以添加菜单项至 Electron 应用程序菜单和上下文菜单当中。

\"menu\"

netLog 模块

用于记录网络日志。

1
const { netLog } require( electron netLog.startLogging( /user/log.info /** 一些网络事件发生之后 */netLog.stopLogging(path { console.log( 网络日志log.info保存在 , path);
powerMonitor 模块

通过 Electron 提供的powerMonitor模块监视当前电脑电源状态的改变 值得注意的是 在app模块的ready事件被触发之前, 不能引用或使用该模块。

1
const electron require( electron const { app } electron;app.on( ready , () { electron.powerMonitor.on( suspend , () { console.log( 系统将要休眠了 
powerSaveBlocker 模块

阻止操作系统进入低功耗 (休眠) 模式。

1
const { powerSaveBlocker } require( electron const ID powerSaveBlocker.start( prevent-display-sleep console.log(powerSaveBlocker.isStarted(ID));powerSaveBlocker.stop(ID);
protocol 模块

注册自定义协议并拦截基于现有协议的请求 例如下面代码实现了一个与[file://]协议等效的示例

1
const { app, protocol } require( electron const path require( path app.on( ready , () { protocol.registerFileProtocol( uinika , (request, callback) { const url request.url.substr(7); callback({ path: path.normalize( ${__dirname}/${url} ) }); error { if (error) console.error( 协议注册失败 
net 模块

net模块是一个发送 HTTP(S) 请求的客户端 API 类似于 NodeJS 的 HTTP 和 HTTPS 模块 但底层使用的是 Chromium 原生网络库。

1
const { app } require( electron app.on( ready , () { const { net } require( electron  const request net.request( https://zhihu.com/people/uinika/activities  request.on( response , response { console.log( STATUS: ${response.statusCode}  console.log( HEADERS: ${JSON.stringify(response.headers)}  response.on( data , chunk { console.log( BODY: ${chunk}  response.on( end , () { console.log( 没有更多数据  request.end();

Electron 中提供的ClientRequest类用来发起 HTTP/HTTPS 请求 IncomingMessage类则用于响应 HTTP/HTTPS 请求。

remote 模块

remote模块返回的每个对象都表示主进程中的一个对象 调用这个对象实质是在发送同步进程消息。因为 Electron 当中 GUI 相关的模块 (如 dialog、menu 等) 仅在主进程中可用, 在渲染进程中不可用 所以remote模块提供了一种渲染进程 Web 页面 与主进程 IPC 通信的简单方法。remote 模块包含了一个remote.require(module)

remote.process 主进程中的process对象 与remote.getGlobal( process )作用相同, 但结果已经被缓存。remote.getCurrentWindow() 返回BrowserWindow 即该网页所属的窗口。remote.getCurrentWebContents() 返回WebContents 即该网页的 Web 内容remote.getGlobal(name) 该方法返回主进程中名为name的全局变量。remote.require(module) 返回主进程内执行require(module)时返回的对象 参数module指定的模块相对路径将会相对于主进程入口点进行解析。
1
project/├── main│ ├── helper.js│ └── index.js├── package.json└── renderer └── index.js
1
// 主进程: main/index.jsconst { app } require( electron app.on( ready , () { /* ... */// 主进程关联的模块: main/test.jsmodule.exports This is a test! // 渲染进程: renderer/index.jsconst helper require( electron ).remote.require( ./helper // This is a test!

remote模块提供的主进程与渲染进程通信方法比ipcMain/ipcRenderer更加易于使用。

screen 模块

检索有关屏幕大小、显示器、光标位置等信息 应用的ready事件触发之前 不能使用该模块。下面的示例代码 创建了一个可以自动全屏窗口的应用

1
const electron require( electron const { app, BrowserWindow } electron;let window;app.on( ready , () { const { width, height } electron.screen.getPrimaryDisplay().workAreaSize; window new BrowserWindow({ width, height }); window.loadURL( https://github.com 
shell 模块

提供与桌面集成相关的功能 例如可以通过调用操作系统默认的应用程序管理文件或Url。

1
const { shell } require( electron shell.openExternal( https://github.com 
systemPreferences 模块

获取操作系统特定的偏好信息 例如在 Mac 下可以通过下面代码获取当前是否开启系统 Dark 模式的信息。

1
const { systemPreferences } require( electron console.log(systemPreferences.isDarkMode()); // 返回一个布尔值。
Tray 模块

用于主进程 添加图标和上下文菜单至操作系统通知区域。

1
const { app, Menu, Tray } require( electron let tray null;app.on( ready , () { tray new Tray( /images/icon  const contextMenu Menu.buildFromTemplate([{ label: Item1 , type: radio }, { label: Item2 , type: radio }, { label: Item3 , type: radio , checked: true }, { label: Item4 , type: radio }]); tray.setToolTip( This is my application.  tray.setContextMenu(contextMenu);
webFrame 模块

定义当前网页渲染的一些属性 比如缩放比例、缩放等级、设置拼写检查、执行 JavaScript 脚本等等。

1
const { webFrame } require( electron webFrame.setZoomFactor(5); // 将页面缩放至500%。
session 模块

Electron 的session模块可以创建新的session对象 主要用来管理浏览器会话、cookie、缓存、代理设置等等。

如果需要访问现有页面的session 那么可以通过BrowserWindow对象的webContents的session属性来获取。

1
const { BrowserWindow } require( electron let window new BrowserWindow({ width: 600, height: 900 });window.loadURL( https://uinika.github.io/web/server/electron.html const mySession window.webContents.session;console.log(mySession.getUserAgent());

Electron 里也可以通过session模块的cookies属性来访问浏览器的 Cookie 实例。

1
const { session } require( electron // 查询所有cookies。session.defaultSession.cookies.get({}, (error, cookies) { console.log(error, cookies);// 查询当前URL下的所有cookies。session.defaultSession.cookies.get({ url: http://www.github.com }, (error, cookies) { console.log(error, cookies);// 设置cookieconst cookie { url: https://www.zhihu.com/people/uinika/posts , name: hank , value: zhihu session.defaultSession.cookies.set(cookie, error { if (error) console.error(error);

使用Session的WebRequest属性可以访问WebRequest类的实例 WebRequest类可以在 HTTP 请求生命周期的不同阶段修改相关内容 例如下面代码为 HTTP 请求添加了一个User-Agent协议头

1
const { session } require( electron // 发送至下面URL地址的请求将会被添加User-Agent协议头const filter { urls: [ https://*.github.com/* , *://electron.github.io ]session.defaultSession.webRequest.onBeforeSendHeaders(filter, (details, callback) { details.requestHeaders[ User-Agent ] MyAgent  callback({ cancel: false, requestHeaders: details.requestHeaders });
desktopCapturer 模块

用于捕获桌面窗口里的内容 该模块只拥有一个方法 desktopCapturer.getSources(options, callback)。

options 对象 types 字符串数组 列出需要捕获的桌面类型是screen还是window。thumbnailSize 媒体源缩略图的大小 默认为150x150。callback 回调函数 拥有如下 2 个参数 error 错误信息。sources 捕获的资源数组。

如下代码工作在渲染进程当中 作用是将桌面窗口捕获为视频

1
const { desktopCapturer } require( electron desktopCapturer.getSources({ types: [ window , screen ] }, (error, sources) { if (error) throw error; for (let i i sources.length; i) { if (sources[i].name Electron ) { navigator.mediaDevices .getUserMedia({ audio: false, video: { mandatory: { chromeMediaSource: desktop , chromeMediaSourceId: sources[i].id, minWidth: 1280, maxWidth: 1280, minHeight: 800, maxHeight: 800 .then(stream handleStream(stream)) .catch(error handleError(error)); return;function handleStream(stream) { const video document.querySelector( video  video.srcObject stream; video.onloadedmetadata error video.play();function handleError(error) { console.log(error);
dialog 模块

调用操作系统原生的对话框 工作在主线程 下面示例展示了一个用于选择多个文件和目录的对话框

1
const { dialog } require( electron console.log(dialog.showOpenDialog({ properties: [ openFile , openDirectory , multiSelections ] }));

由于对话框工作在 Electron 的主线程上 如果需要在渲染器进程中使用, 那么可以通过remote来获得

1
const { dialog } require( electron ).remote;console.log(dialog);
contentTracing 模块

从 Chromium 收集跟踪数据 从而查找性能瓶颈。使用后需要在浏览器打开chrome://tracing/页面 然后加载生成的文件查看结果。

1
const { app, contentTracing } require( electron app.on( ready , () { const options { categoryFilter: * , traceOptions: record-until-full,enable-sampling  contentTracing.startRecording(options, () { console.log( 开始跟踪!  setTimeout(() { contentTracing.stopRecording( , path { console.log( 跟踪数据已经记录至 path); }, 8000);
webview 标签

Electron 的 webview 标签基于 Chromium 由于开发变动较大官方并不建议使用 而应考虑 iframe 或者 Electron 的BrowserView等选择 或者完全避免在页面进行内容嵌入。

webview 与 iframe 最大不同是运行于不同的进程当中 Electron 应用程序与嵌入内容之间的所有交互都是异步进行的 这样可以保证应用程序与嵌入内容双方的安全。

1
 webview id uinika src http://localhost:5000/web/server/electron.html /webview 
webContents 属性

webContents是BrowserWindow对象的一个属性 负责渲染和控制 Web 页面。

1
const { BrowserWindow } require( electron let window new BrowserWindow({ width: 600, height: 500 });window.loadURL( https://uinika.github.io/ let contents window.webContents;console.log(contents);
window.open() 函数

该函数用于打开一个新窗口并加载指定url 调用后将会为该url创建一个BrowserWindow实例 并返回一个BrowserWindowProxy对象 但是该对象只能对打开的url页面进行有限的控制。正常情况下 如果希望完全控制新窗口 可以直接创建一个新的BrowserWindow。

1
// window.open(url[, frameName][, features])window.open( https://github.com , _blank , nodeIntegration no 

BrowserWindowProxy对象拥有如下属性和方法

win.closed 子窗口关闭后设置为true的布尔属性。win.blur() 将焦点从子窗口中移除。win.close() 强制关闭子窗口, 而不调用其卸载事件。win.eval(code) code字符串 需要在子窗口 Eval 的代码。win.focus() 聚焦子窗口 即将子窗口置顶 。win.print() 调用子窗口的打印对话框。win.postMessage(message, targetOrigin) 向子窗口发送信息。Electron 进程

Electron 的process对象继承自 NodeJS 的process对象 但是新增了一些有用的事件、属性、方法。

1
const { app, BrowserWindow } require( electron let mainWindow;app.on( ready , () { mainWindow new BrowserWindow({ width: 800, height: 500, frame: false }); mainWindow.loadFile( resource/index.html  console.log(process.type); // 当前进程类型是browser主进程还是renderer渲染进程 browser console.log(process.versions.node); // NodeJS版本 10.2.0 console.log(process.versions.chrome); // Chrome版本 66.0.3359.181 console.log(process.versions.electron); //Electron版本 3.0.13 console.log(process.resourcesPath); // 资源目录路径 D:\\Workspace\\octopus\\node_modules\\electron\\dist\\resources
沙箱机制

Chromium 通过将 Web 前端代码放置在一个与操作系统隔离的沙箱中运行 从而保证恶意代码不会侵犯到操作系统本身。但是 Electron 中渲染进程可以调用 NodeJS 而 NodeJS 又需要涉及大量操作系统调用 因而沙箱机制默认是禁用的。

某些应用场景下 需要运行一些不确定安全性的外部前端代码 为了保证操作系统安全 可能需要开启沙箱机制。此时首先在创建BrowserWindow时传入sandbox属性 然后在命令行添加--enable-sandbox参数传递给 Electron 即可完成开启。

1
let win;app.on( ready , () { window new BrowserWindow({ webPreferences: { sandbox: true window.loadURL( http://google.com 

使用sandbox选项之后 将会阻止 Electron 在渲染器中创建一个 NodeJS 运行时环境 此时新窗口中的window.open() 将按照浏览器原生的方式工作。

MacBook TouchBar 支持

针对 Mac 笔记本电脑上配置的 TouchBar 硬件 Electron 提供了一系列相关的类与操作接口 TouchBar、TouchBarButton、TouchBarColorPicker、TouchBarGroup、TouchBarLabel、TouchBarPopover、TouchBarScrubber、TouchBarSegmentedControl、TouchBarSlider、TouchBarSpacer。

创建应用图标

用于将 PNG 或 JPG 图片设置为托盘、Dock 和应用程序的图标。

1
const { BrowserWindow, Tray } require( electron const Icon new Tray( /images/icon.png let window new BrowserWindow({ icon: /images/window.png });
安全原则

由于 Electron 的发布通常落后最新版本 Chromium 几周甚至几个月 因此特别需要注意如下这些安全性问题

使用安全的协议加载外部内容

外部资源尽量使用更安全的协议加载 比如HTTP换成HTTPS、WS换成WSS、FTP换成FTPS等。

1
 !-- 错误 --  script crossorigin src http://cdn.com/react.js /script  link rel stylesheet href http://cdn.com/scss.css /  !-- 正确 --  script crossorigin src https://cdn.com/react.js /script  link rel stylesheet href https://cdn.com/scss.css /  script  browserWindow.loadURL( http://uinika.github.io/); // 错误 browserWindow.loadURL( https://uinika.github.io/ // 正确 /script 
加载外部内容时禁用 NodeJS 集成

使用BrowserWindow、BrowserView、 webview 加载远程内容时 都需要通过禁用 NodeJS 集成去限制远程代码的执行权限 避免恶意代码跨站攻击。

1
 !-- 错误 --  webview nodeIntegration src page.html /webview  !-- 正确 --  webview src page.html /webview  script  /** 错误 */ const mainWindow new BrowserWindow(); mainWindow.loadURL( https://my-website.com  /** 正确 */ const mainWindow new BrowserWindow({ webPreferences: { nodeIntegration: false, preload: ./preload.js  /script 

对于需要与远程代码共享的变量或函数 可以通过将其挂载至当前页面的window全局对象来实现。

渲染进程中启用上下文隔离

上下文隔离是 Electron 提供的试验特性 通过为远程加载的代码创造一个全新上下文环境 避免与主进程中的代码出现冲突或者相互污染。

1
// 主进程const mainWindow new BrowserWindow({ webPreferences: { contextIsolation: true, preload: preload.js 
处理远程内容中的会话许可

当页面尝试使用某个特性时 会弹出通知让用户手动进行确认 而默认情况下 Electron 会自动批准所有的许可请求。

1
const { session } require( electron session.fromPartition( some-partition ).setPermissionRequestHandler((webContents, permission, callback) { const url webContents.getURL(); if (permission notifications ) { callback(true); // 通过许可请求 if (!url.startsWith( https://my-website.com )) { return callback(false); // 拒绝许可请求
不要禁用 webSecurity

在渲染进程禁用webSecurity将导致许多重要的安全性功能被关闭 因此 Electron 默认开启。

1
const mainWindow new BrowserWindow({ webPreferences: { webSecurity: false // 错误的做法 缺省该属性使用默认值即可。
定义 CSP 安全策略

内容安全策略 CSP 允许 Electron 通过webRequest对指定 URL 的访问进行约束 例如允许加载https://uinika.github.io/这个源 那么https://hack.attacker.com将不会被允许加载 CSP 是处理跨站脚本攻击、数据注入攻击的另外一层保护措施。

1
const { session } require( electron session.defaultSession.webRequest.onHeadersReceived((details, callback) { callback({ responseHeaders: { ...details.responseHeaders, Content-Security-Policy : [ default-src none ]

使用file://协议打开本地文件时 可以通过元数据标签 meta 的属性来添加 CSP 约束。

1
 meta http-equiv Content-Security-Policy content default-src none / 
别设置 allowRunningInsecureContent 为 true

Electron 默认不允许在 HTTPS 页面中加载 HTTP 来源的代码 如果将allowRunningInsecureContent属性设置为true会禁用这种保护。

1
const mainWindow new BrowserWindow({ webPreferences: { allowRunningInsecureContent: true // 错误的做法 缺省该属性使用默认值即可。
不要开启实验性功能

开发人员可以通过experimentalFeatures属性启用未经严格测试的 Chromium 实验性功能 不过 Electron 官方出于稳定性和安全性考虑并不建议这样做。

1
const mainWindow new BrowserWindow({ webPreferences: { experimentalFeatures: true // 错误的做法 缺省该属性使用默认值即可。
不要使用 enableBlinkFeatures

Blink 是 Chromium 内置的 HTML/CSS 渲染引擎 开发者可以通过enableBlinkFeatures启用其某些默认是禁用的特性。

1
const mainWindow new BrowserWindow({ webPreferences: { enableBlinkFeatures: [ ExecCommandInJavaScript ] // 错误的做法 缺省该属性使用默认值即可。
禁用 webview 的 allowpopups

开启allowpopups属性将使window.open()创建一个新的窗口和BrowserWindows 若非必要状况 尽量不要使用此属性。

1
 !-- 错误 --  webview allowpopups src page.html /webview  !-- 正确 --  webview src page.html /webview 
验证 webview 选项与参数

通过渲染进程创建的 WebView 默认不集成 NodeJS 但是它可以通过webPreferences属性创建出一个独立的渲染进程。在 WebView 标签开始渲染之前 Electron 将会触发一个will-attach-webview事件 可以通过该事件防止创建具有潜在不安全选项的 Web 视图。

1
app.on( web-contents-created , (event, contents) { contents.on( will-attach-webview , (event, webPreferences, params) { // 如果未使用或者验证位置合法 那么将会剥离预加载脚本。 delete webPreferences.preload; delete webPreferences.preloadURL; // 禁用NodeJS集成。 webPreferences.nodeIntegration false; // 验证正在加载的URL。 if (!params.src.startsWith( https://www.zhihu.com/people/uinika/columns )) { event.preventDefault();
禁用或限制网页跳转

如果 Electron 应用程序不需要导航或只需导航至特定页面 最佳实践是将导航限制在已知范围 并禁止其它类型的导航。可以通过在will- navigation事件处理函数中调用event.preventDefault()并添加额外的判断来实现这一点。

1
const URL require( url ).URL;app.on( web-contents-created , (event, contents) { contents.on( will-navigate , (event, navigationUrl) { const parsedUrl new URL(navigationUrl); if (parsedUrl.origin ! https://www.zhihu.com/people/uinika/posts ) { event.preventDefault();
禁用或限制新窗口创建

限制在 Electron 应用程序中创建额外窗口 并避免因此带来额外的安全隐患。webContents创建新窗口时会触发一个web-contents-created事件 该事件包含了将要打开的 URL 以及相关选项 可以在这个事件中检查窗口的创建 从而对其进行相应的限制。

1
const { shell } require( electron app.on( web-contents-created , (event, contents) { contents.on( new-window , (event, navigationUrl) { // 通知操作系统在默认浏览器上打开URL event.preventDefault(); shell.openExternal(navigationUrl);

Electron 2.0 版本开始 会在可执行文件名为 Electron 时会为开发者在控制台显示安全相关的警告和建议 开发人员也可以在process.env或window对象上配置ELECTRON_ENABLE_SECURITY_WARNINGS或ELECTRON_DISABLE_SECURITY_WARNINGS手动开启或关闭这些警告。

应用发布

Electron 的发布有别于传统桌面应用程序编译打包的发部过程 需要首先下载已经预编译完成的二进制包 Linux 下二进制包结构如下

1
➜ electron-v3.0.7-linux-x64 tree -L 2├── blink_image_resources_200_percent.pak├── content_resources_200_percent.pak├── content_shell.pak├── electron├── icudtl.dat├── libffmpeg.so├── libnode.so├── LICENSE├── LICENSES.chromium.html├── locales├── natives_blob.bin├── ui_resources_200_percent.pak├── v8_context_snapshot.bin├── version└── views_resources_200_percent.pak└── resources   ├── default_app.asar  └── electron.asar

接下来 就可以部署前面编写的源代码 Electron 里主要有如下两种部署方式

直接将代码放置到resources下的子目录 比如app目录
1
├── app│   ├── package.json│   └── resource│   ├── index.html│   └── main.js├── default_app.asar└── electron.asar
将应用打包加密为asar文件以后放置到resources目录 比如app.asar文件。
1
├── app.asar├── default_app.asar└── electron.asar
asar 打包源码

asar 是一种简单的文件扩展格式 可以将文件如同tar格式一样前后连接在一起 支持随机读写 并使用 JSON 来保存文件信息 可以方便的读取与解析。Electron 通过它可以解决 Windows 文件路径长度的限制 提高require语句的加载速度 并且避免源代码泄漏。

首先 需要安装asar这个 npm 包 然后可以选择全局安装然通过命令行使用。

1
➜ npm install asar➜ asar pack electron-demo app.asar

当然 更加工程化的方式是通过代码来执行打包操作 就像下面这样

1
let asar require( asar src ../octopus dest build/app.asar /** 打包完成后的回调函数 */callback () { console.info( asar打包完成 asar.createPackage(src, dest, callback);

Electron 在 Web 页面可以通过file:协议读取 asar 包中的文件 即将 asar 文件视为一个虚拟的文件夹来进行操作。

1
const { BrowserWindow } require( electron const mainWindow new BrowserWindow();mainWindow.loadURL( file:///path/to/example.asar/static/index.html 

如果需要对 asar 文件进行 MD5 或者 SHA 完整性校验 可以对 asar 档案文件本身进行操作。

1
asar list app.asar
rcedit 编辑可执行文件

RcEdit是一款通过编辑窗口管理器的 rc 文件来对其进行配置的工具 Nodejs 社区提供了node-rcedit工具对 Windows 操作系统的.exe文件进行配置 首先通过npm i rcedit --save-dev为项目安装该依赖项。

1
var rcedit require( rcedit rcedit(exePath, options, callback);

rcedit()函数包含有如下属性

exePath 需要进行修改的 Windows 可执行文件所在路径。options 一个拥有如下属性的配置对象。 version-string 版本字符串 file-version 文件版本 product-version 产品版本 icon 图标文件.ico的路径;requested-execution-level 需要修改的执行级别 asInvoker、highestAvailable、requireAdministrator 。application-manifest 本地清单文件的路径。callback 函数执行完毕之后回调 完整的函数签名为function(error)。yarn 包管理器

Yarn 是一款由 Facebook 推出的 JavaScript 包管理器 与 NodeJS 提供的 Npm 一样使用package.json作为包信息文件。

测试当前 Yarn 的安装版本

1
yarn --version

初始化新项目

1
yarn init

添加依赖包

1
yarn add [package]yarn add [package] [version]yarn add [package] [tag]

将依赖项添加到不同的依赖项类别 devDependencies、peerDependencies 和 optionalDependencies。

1
yarn add [package] --devyarn add [package] --peeryarn add [package] --optional

升级依赖包

1
yarn upgrade [package]yarn upgrade [package] [version]yarn upgrade [package] [tag]

移除依赖包

1
yarn remove [package]

可以直接使用yarn命令安装项目的全部依赖

1
yarn install
windows-installer

windows-installer是一个用于为 Electron 应用程序构建 Windows 安装程序的 Npn 包 底层基于Squirrel 一组用于管理C#或C 开发的 Windows 应用程序的安装、更新的工具库 进行实现。

1
npm install --save-dev electron-winstaller
1
var electronInstaller require( electron-winstaller resultPromise electronInstaller.createWindowsInstaller({ appDirectory: /tmp/build/my-app-64 , outputDirectory: /tmp/build/installer64 , authors: My App Inc. , exe: myapp.exe resultPromise.then(() console.log( It worked! ), e console.log( No dice: ${e.message} 
electron-build

除了像上面这样通过预编译包手动打包应用程序 也可以采用electron-forge、electron-packager等第三方包来完成这项工作 在这里笔者选择electron-builder来进行自动化打包任务。

Electron Userland 是一个维护 Electron 模块的第三方社区 electron-builder 是由其维护的一款能够同时处理 Windows、MacOS、Linux 多平台的打包编译工具。由于 electron-builder 工具包的文件体积较大 其社区强烈推荐使用更快速的yarn来代替npm作为包管理方案。

1
yarn add electron-builder --dev

electron-builder 能够以命令行或者JavaScript API的方式进行使用

1 如果安装在项目目录下的node_modules目录 可以直接通过 NodeJS 提供的npx以命令行方式使用

1
npx electron-builder

2 也可以像使用其它 Npm 包那样直接调用 electron-builder 提供的 API。

1
 use strict const builder require( electron-builder const Platform builder.Platform;// Promise is returnedbuilder .build({ targets: Platform.MAC.createTarget(), config: { // : build options, see https://goo.gl/QQXmcV  .then(() { // handle result .catch(error { // handle error

官方推荐使用electron-webpack-quick-start作为 Electron 应用的项目模板。

1
{ // 指定编译配置 build : { appId : your.id , mac : { category : your.app.category.type  // 添加编译指令 scripts : { pack : electron-builder --dir , dist : electron-builder  // 确保原生依赖总是匹配Electron版本 postinstall : electron-builder install-app-deps

如果项目中存在原生依赖 还需要设置nodeGypRebuild为true。

如果需要调试electron-builder的行为 那么需要设置DEBUG electron-builder环境变量。

1
set DEBUG electron-builder // Cmder$env:DEBUG electron-builder // PowerShell
electron-forge

electron-forge同样是由 Electron Userland 维护的一款命令行工具 用于快速建立、打包、发布一个 Electron 应用程序。

1
λ npm install -g electron-forgeλ electron-forge init my-new-projectλ cd my-new-projectλ electron-forge start

目前 Github 上 electron-builder 的 Star 远远超过 electron-forge 由于笔者项目需要使用 React 因而也就选用带有支持 React 项目模板的 electron-builder 需要尝试 electron-forge 的同学可以移步官网查看更多信息。

本文链接: http://appglobalinc.immuno-online.com/view-677454.html

发布于 : 2021-03-24 阅读(0)