2023 最新 Node.Js 语言实现 Electron.js 桌面应用开发详细教程(进阶篇)
作者:mmseoamin日期:2023-12-25

Electron 基本概述

Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 嵌入 Chromium 和 Node.js 到 二进制的 Electron 允许您保持一个 JavaScript 代码代码库并创建。在 Windows 上运行的跨平台应用 macOS 和 Linux

2023 最新 Node.Js 语言实现 Electron.js 桌面应用开发详细教程(进阶篇),在这里插入图片描述,第1张

Electron Fiddle 运行实例

Electron Fiddle 是由 Electron 开发并由其维护者支持的沙盒程序。 我们强烈建议将其作为一个学习工具来安装,以便在开发过程中对Electron的api进行实验或对特性进行原型化。

脚手架创建工作环境

mkdir my-electron-app && cd my-electron-app
npm init

package.json

{
  "name": "my-electron-app",
  "version": "1.0.0",
  "description": "Hello World!",
  "main": "main.js",
  "author": "Jane Doe",
  "license": "MIT"
}

下载 Electron.Js

Electron.js Github 仓库地址:https://github.com/electron/electron/releases

安装 npm install electron 需要开加速器(亲测)

2023 最新 Node.Js 语言实现 Electron.js 桌面应用开发详细教程(进阶篇),在这里插入图片描述,第2张

npm install --save-dev electron

下载过程中,出现如 ELIFECYCLE、EAI_AGAIN、ECONNRESET 和 ETIMEDOUT 等错误都是此类网络问题的标志。

在较慢的网络上, 最好使用 --verbose 标志来显示下载进度:

npm install --verbose electron

下载异常解决方案

使用 cnpm 淘宝源下载 electron(如何改了 npm 淘宝镜像地址 还是失败的话,这种基本也是寄)

npm install cnpm -g
cnpm install electron -g

配置 .npmrc eletron 镜像地址(其他 electron 依赖使用梯子)亲测成功!!!

2023 最新 Node.Js 语言实现 Electron.js 桌面应用开发详细教程(进阶篇),在这里插入图片描述,第3张

使用香港的梯子

2023 最新 Node.Js 语言实现 Electron.js 桌面应用开发详细教程(进阶篇),在这里插入图片描述,第4张

提示:测试下载(注意仓库地址还是使用官方地址(不使用淘宝))

C:\Users\Administrator>npm install electron -g
added 75 packages in 2m
20 packages are looking for funding
  run `npm fund` for details
npm notice
npm notice New minor version of npm available! 10.1.0 -> 10.2.3
npm notice Changelog: https://github.com/npm/cli/releases/tag/v10.2.3
npm notice Run npm install -g npm@10.2.3 to update!
npm notice

保底方案(基本有梯子肯定能解决)

GitHub eletron 地址:https://github.com/electron/electron/releases

选择合适的版本进行下载 zip 包(若 windows 操作系统)

2023 最新 Node.Js 语言实现 Electron.js 桌面应用开发详细教程(进阶篇),在这里插入图片描述,第5张

Electron 进程模型

Electron 继承了来自 Chromium 的多进程架构,这使得此框架在架构上非常相似于一个现代的网页浏览器。

为什么不是一个单一的进程?

网页浏览器是个极其复杂的应用程序。 除了显示网页内容的主要能力之外,他们还有许多次要的职责,例如:管理众多窗口 ( 或 标签页 ) 和加载第三方扩展。

在早期,浏览器通常使用单个进程来处理所有这些功能。 虽然这种模式意味着您打开每个标签页的开销较少,但也同时意味着一个网站的崩溃或无响应会影响到整个浏览器。

多进程模型

为了解决这个问题,Chrome 团队决定让每个标签页在自己的进程中渲染, 从而限制了一个网页上的有误或恶意代码可能导致的对整个应用程序造成的伤害。 然后用单个浏览器进程控制这些标签页进程,以及整个应用程序的生命周期。 下方来自 Chrome 漫画 的图表可视化了此模型:

2023 最新 Node.Js 语言实现 Electron.js 桌面应用开发详细教程(进阶篇),在这里插入图片描述,第6张

Electron 应用程序的结构非常相似。 作为应用开发者,你将控制两种类型的进程:主进程 和 渲染器进程。 这类似于上文所述的 Chrome 的浏览器和渲染器进程。

主进程 Main Process

每个 Electron 应用都有一个单一的主进程,作为应用程序的入口点。 主进程在 Node.js 环境中运行,这意味着它具有 require 模块和使用所有 Node.js API 的能力。

窗口管理

主进程的主要目的是使用 BrowserWindow 模块创建和管理应用程序窗口。

BrowserWindow 类的每个实例创建一个应用程序窗口,且在单独的渲染器进程中加载一个网页。 您可从主进程用 window 的 webContent 对象与网页内容进行交互。

const { BrowserWindow } = require('electron')
const win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('https://github.com')
const contents = win.webContents
console.log(contents)

注意:渲染器进程也是为 web embeds 而被创建的,例如 BrowserView 模块。 嵌入式网页内容也可访问

webContents 对象。

由于 BrowserWindow 模块是一个 EventEmitter, 所以您也可以为各种用户事件 ( 例如,最小化 或 最大化您的窗口 ) 添加处理程序。

当一个 BrowserWindow 实例被销毁时,与其相应的渲染器进程也会被终止。

应用程序生命周期

主进程还能通过 Electron 的 app 模块来控制您应用程序的生命周期。 该模块提供了一整套的事件和方法,可以让您用来添加自定义的应用程序行为 (例如:以编程方式退出您的应用程序、修改应用程序坞,或显示一个关于面板) 。

app.on('window-all-closed', () => {
	if (process.platform !== 'darwin') app.quit()
})

原生 API

为了使 Electron 的功能不仅仅限于对网页内容的封装,主进程也添加了自定义的 API 来与用户的作业系统进行交互。 Electron 有着多种控制原生桌面功能的模块,例如菜单、对话框以及托盘图标。

Electron 渲染器进程

每个 Electron 应用都会为每个打开的 BrowserWindow ( 与每个网页嵌入 ) 生成一个单独的渲染器进程。 洽如其名,渲染器负责 渲染 网页内容。 所以实际上,运行于渲染器进程中的代码是须遵照网页标准的 (至少就目前使用的 Chromium 而言是如此) 。

因此,一个浏览器窗口中的所有的用户界面和应用功能,都应与您在网页开发上使用相同的工具和规范来进行攥写。

虽然解释每一个网页规范超出了本指南的范围,但您最起码要知道的是:

以一个 HTML 文件作为渲染器进程的入口点。
使用层叠样式表 (Cascading Style Sheets, CSS) 对 UI 添加样式。
通过 
  

预加载脚本 preload.js

const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
    setTitle: (title) => ipcRenderer.send('set-title', title)
})

渲染进程 renderer.js

const setButton = document.getElementById('btn')
const titleInput = document.getElementById('title')
setButton.addEventListener('click', () => {
    const title = titleInput.value
    window.electronAPI.setTitle(title)
});

运行 main.js 主进程

任何 Electron 应用程序的入口都是 main 文件。 这个文件控制了主进程,它运行在一个完整的Node.js环境中,负责控制您应用的生命周期,显示原生界面,执行特殊操作并管理渲染器进程。

执行期间,Electron 将依据应用中 package.json配置下main字段中配置的值查找此文件,您应该已在应用脚手架步骤中配置。

2023 最新 Node.Js 语言实现 Electron.js 桌面应用开发详细教程(进阶篇),在这里插入图片描述,第7张

要初始化这个main文件,需要在您项目的根目录下创建一个名为 main.js 的空文件。

在 main.js 引入 electron 环境包

const { app, BrowserWindow } = require('electron')

根据 package.json 配置 start 脚本进行运行:npm run start

"scripts": {
	"start": "electron-forge start",
}

创建 HTML 页面

在可以为我们的应用创建窗口前,我们需要先创建加载进该窗口的内容。 在Electron中,各个窗口显示的内容可以是本地HTML文件,也可以是一个远程url。



	
	    
	    
	    你好!
	
	
	    

你好!

我们正在使用 Node.js , Chromium , 和 Electron .

窗口加载 HTML 页面

现在您有了一个页面,将它加载进应用窗口中。 要做到这一点,你需要 两个Electron模块:

app 模块,它控制应用程序的事件生命周期。
BrowserWindow 模块,它创建和管理应用程序 窗口。

因为主进程运行着 Node.js,您可以在 main.js 文件头部将它们导入作为 CommonJS 模块:

const { app, BrowserWindow } = require('electron')

添加 createWindow() 方法来将index.html加载进一个新的BrowserWindow实例。

const createWindow = () => {
	const win = new BrowserWindow({
		width: 800, height: 600
	})
	win.loadFile('index.html')
}

调用 createWindow() 函数来打开您的窗口

在 Electron 中,只有在 app 模块的 ready 事件被激发后才能创建浏览器窗口。 您可以通过使用 app.whenReady() API来监听此事件。 在 whenReady() 成功后调用createWindow()。

app.whenReady().then(() => { createWindow() })

窗口生命周期

关闭所有窗口时退出应用 (Windows & Linux)

在Windows和Linux上,关闭所有窗口通常会完全退出一个应用程序。

为了实现这一点,你需要监听 app 模块的 ‘window-all-closed’ 事件。如果用户不是在 macOS(darwin) 上运行程序,则调用 app.quit()。

app.on('window-all-closed', () => {
	if (process.platform !== 'darwin') app.quit()
})

process.platform 可能值

'aix'    'darwin'    'freebsd'   'linux'   'openbsd'    'sunos'    'win32'

Electron 预加载脚本

通过预加载脚本从渲染器访问Node.js。

现在,最后要做的是输出Electron的版本号和它的依赖项到你的web页面上。

在主进程通过Node的全局 process 对象访问这个信息是微不足道的。 然而,你不能直接在主进程中编辑DOM,因为它无法访问渲染器 文档 上下文。 它们存在于完全不同的进程!

window.addEventListener('DOMContentLoaded', () => {
	const replaceText = (selector, text) => {
		const element = document.getElementById(selector)
		if (element) element.innerText = text
	}
	
	for (const dependency of ['chrome', 'node', 'electron']) {
		replaceText(`${dependency}-version`, process.versions[dependency])
	}
})

要将此脚本附加到渲染器流程,请在你现有的 BrowserWindow 构造器中将路径中的预加载脚本传入 webPreferences.preload 选项。

const { app, BrowserWindow } = require('electron')
const path = require('path')
const createWindow = () => {
	const win = new BrowserWindow({
		width: 800,
		height: 600,
		webPreferences: {
			preload: path.join(__dirname, 'preload.js')
		}
	})
	
	win.loadFile('index.html')
}
__dirname 字符串指向当前正在执行脚本的路径 (在本例中,它指向你的项目的根文件夹)。
path.join API 将多个路径联结在一起,创建一个跨平台的路径字符串。

对于与您的网页内容的任何交互,您想要将脚本添加到您的渲染器进程中。 由于渲染器运行在正常的 Web 环境中,因此您可以在 index.html 文件关闭 标签之前添加一个

 

窗口开启调试

按:shift + ctrl + i

2023 最新 Node.Js 语言实现 Electron.js 桌面应用开发详细教程(进阶篇),在这里插入图片描述,第8张

进程隔离 require 报错

2023 最新 Node.Js 语言实现 Electron.js 桌面应用开发详细教程(进阶篇),在这里插入图片描述,第9张

这个错误通常是由于在渲染进程中使用了 Node.js 模块而导致的。在 Electron 中,主进程和渲染进程是分开的,它们有不同的上下文和作用域。在渲染进程中,Node.js 模块是不可用的,因此当你在渲染进程中使用 require 时,会出现 “Uncaught ReferenceError: require is not defined” 错误。

nodeIntegration boolean (optional) - Whether node integration is enabled. Default is false.

在 Electron 中,nodeIntegration 是一个安全策略,用于隔离不受信任的资源。如果攻击者以某种方式设法改变远程网站的内容(例如通过直接攻击源或者通过在应用和实际目的地之间进行攻击),他们将能够在用户的机器上执行本地代码。通过禁用 Node.js 集成(即设置 nodeIntegration 为 False),可以有助于防止这类攻击。

然而,有时候我们需要启用 nodeIntegration,比如在需要使用本地资源或执行本地代码时。在 Electron 工程的 main.js 文件中,可以通过设置 webPreferences:{nodeIntegration: true} 来启用 Node.js 集成。

const win = new BrowserWindow({
    webPreferences: {
        contextIsolation: false,
        nodeIntegration: true
    }
})

原生 API 概述

为了使 Electron 的功能不仅仅限于对网页内容的封装,主进程也添加了自定义的 API 来与用户的作业系统进行交互。 Electron 有着多种控制原生桌面功能的模块,例如菜单、对话框以及托盘图标。

Preload 预加载脚本

预加载(preload)脚本包含了那些执行于渲染器进程中,且先于网页内容开始加载的代码 。 这些脚本虽运行于渲染器的环境中,却因能访问 Node.js API 而拥有了更多的权限。

预加载脚本可以在 BrowserWindow 构造方法中的 webPreferences 选项里被附加到主进程。

const { BrowserWindow } = require('electron')
const win = new BrowserWindow({
	webPreferences: {
		preload: 'path/to/preload.js'
	}
})

因为预加载脚本与浏览器共享同一个全局 Window 接口,并且可以访问 Node.js API,所以它通过在全局 window 中暴露任意 API 来增强渲染器,以便你的网页内容使用。

虽然预加载脚本与其所附着的渲染器在共享着一个全局 window 对象,但您并不能从中直接附加任何变动到 window 之上,因为 contextIsolation 是默认的。

preload.js

window.myAPI = { desktop: true }

renderer.js

console.log(window.myAPI)

CSS 窗口可拖拽区

应用程序需要在 CSS 中指定 -webkit-app-region: drag 来告诉 Electron 哪些区域是可拖拽的?在可拖拽区域内部使用 -webkit-app-region: no-drag 则可以将其中部分区域排除。拖动行为可能与选择文本冲突。 例如, 当您拖动标题栏时, 您可能会意外地选择标题栏上的文本。 为防止此操作, 您需要在可区域中禁用文本选择。

.titlebar {
	-webkit-app-region: drag;
	-webkit-user-select: none;
}

在某些平台上,可拖拽区域不被视为窗口的实际内容,而是作为窗口边框处理,因此在右键单击时会弹出系统菜单。 要使上下文菜单在所有平台上都正确运行, 您永远也不要在可拖拽区域上使用自定义上下文菜单。

备注:如果你在某些页面设置了可拖拽区,跳转到一个新的页面,而这个新的页面没有设置可拖拽区,则会沿用上一页面的可拖拽区,感觉很奇怪,也没有相应的 dom 支持,就像取用的上一页面固定的像素区域一样(测试发现是这样,不准确之处请指正,谢谢)。如果拖拽区域不同,这种情况下,在新的页面中设置上拖拽区域即可。

语境隔离 contextIsolation

语境隔离(Context Isolation)意味着预加载脚本与渲染器的主要运行环境是隔离开来的,以避免泄漏任何具特权的 API 到您的网页内容代码中。

取而代之,我们將使用 contextBridge 模块来安全地实现交互:

preload.js

const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('myAPI', {
  desktop: true
})

renderer.js

console.log(window.myAPI)