5分钟 electron 入门

番茄钟应用

在一个番茄钟应用中,主进程和渲染进程要做什么?

image.png

从上方的进程工作内容可以发现,有些进程工作内容不仅仅是番茄应用会用到,其实所有应用都会用到。这些是一个应用基本通用的。
比如主进程 app 模块管理应用、BrowserWindow 模块创建窗口,进程间通信。

起步

安装

npm install electron --save-dev

初始化

npm init -y

package.json 的 main 字段指定的是 Electron 应用的入口文件,就像 vite 构建 SPA 的入口文件是 html 一样。因此这个入口文件很重要,它就是主进程。
author、license 和 description可为任意值,但对于应用打包是必填项。

启动 electron 项目

electron .

这个命令会找到入口文件并执行。

通常我们会设为一个 script 脚本来使用:

{
  "scripts": {
    "dev": "electron .",
  }
}

nodemon 启动项目

开发的时候,频繁手动启停应用很麻烦。我们可以借助 nodemon 自动监听文件更改,执行electron . 命令。

nodemon 可以监听文件变动,并执行命令。
它有几个主要配置:

  • –watch:监听的文件,默认为*.*,也就是当前目录的所有文件
  • –ext:监听的文件后缀,默认为 js,mjs,html
  • –ignore:忽略监听,默认已经忽略常见的应该忽略的文件,如 node_module
  • –exec:执行命令,默认可以执行 js 脚本。
  • –delay:延迟启动。默认情况下当它检测到文件更改时就会立即重启你的应用程序。然而,如果你想要控制 nodemon 的重启频率,以避免在保存文件时过于频繁地重启(例如,当你正在进行大量编辑时),你可以使用 --delay(简写为 -d)选项来设置重启延迟。-d 1.5 :延迟1.5秒启动

nodemon 最终会监听的文件规则:
nodemon 会先读取 watch 里面需要监听的文件或文件路径,再从文件中选择监控 ext 中指定的后缀名,最后去掉从 ignore 中指定的忽略文件或文件路径。

electron 项目中,nodemon 已经监听了 js,但没有 html。我们希望改了 html,也能重新启动,所以要加上后缀为 html 的文件监听。

{
  "scripts": {
    "dev": "nodemon --ext js,html,json --delay 1.5 --exec electron ."
  }
}

当然如果配置项太多,也可以单独添加一个配置文件:

{
  "ext": "js,html,json",
  "delay": "1.5"
}

主进程 app 和窗口管理 BrowserWindow

app 、BrowserWindow

// 主进程
const { app, BrowserWindow } = require("electron");

我们使用 CommonJS 语法导入了两个 Electron 的核心模块:

  • app,它就是主进程,控制您应用程序的事件生命周期。
  • BrowserWindow,它负责创建和管理应用窗口。

您可能注意到了 app 和 BrowserWindow 两个模块名的大小写差异。 Electron 遵循 JavaScript 传统约定,以帕斯卡命名法 (PascalCase) 命名可实例化的类 (如 BrowserWindow, Tray 和 Notification),以驼峰命名法 (camelCase) 命名不可实例化的函数、变量等 (如 app, ipcRenderer, webContents) 。

// 为了更好做类型检查,如使用ts。则从 electron/main 中引入
const { app, BrowserWindow } = require('electron/main')

Electron 的许多核心模块都是 Node.js 的事件触发器,遵循 Node.js 的异步事件驱动架构。 app 模块就是其中一个。
node 事件与异步 IO 模型
Events | Node.js v21.6.2 Documentation

// node.js 自定义事件
const EventEmitter = require('events');

const myEmitter = new EventEmitter();

myEmitter.on('test', () => {
  console.log('test event occurred');
});

myEmitter.emit('test');

ready 事件

app 模块的事件中,ready 事件最为重要。它被触发了,说明主线程启动完毕了,这时候才可以创建 BrowserWindows 实例,也就是建立窗口界面。

通常我们使用触发器的 .on 函数来监听 Node.js 事件。但是 ready 事件特殊一点,Electron 暴露了 app.whenReady() 方法,作为其 ready 事件的专用监听器,这样可以避免直接监听 .on 事件带来的一些问题。

app.on('ready', () => {})
  
app.whenReady().then(() => {})

在 Electron 中,只有在 app 模块的 ready 事件触发后才能创建 BrowserWindows 实例。 您可以通过使用 app.whenReady() API 来监听此事件,并在其成功后调用 createWindow() 方法。

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

const createWindow = () => {
  // 新建窗口
  const win = new BrowserWindow({
    // 窗口大小
    width: 800,
    height: 600,
    // 窗口出现位置,默认是居中出现
    x: 1500,
    y: 200,
    // 窗口置顶,方便开发时查看
    alwaysOnTop: true
  })

  // 装载 UI 界面
  win.loadFile('index.html')

  // 与渲染进程的网页内容交互
  const contents = win.webContents
  console.log(contents)
}

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

webContent:主进程控制网页

主进程的主要目的是使用 BrowserWindow 模块创建和管理应用程序窗口。
BrowserWindow 类的每次实例化就是创建一个应用程序窗口,也就是一个 html 网页,也就是一个渲染进程。
可以从主进程用窗口实例的 webContent 对象与网页进行交互。

比如应用启动就打开网页调试台:

const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600
  })

  // 打开调试台
  win.webContents.toggleDevTools();
}

退出应用

创建您的第一个应用程序 | Electron

在 Windows 和 Linux 上,应用的使用习惯一般是关闭所有窗口后,应用就自动退出了。现在我们希望让 Electron 应用继续保持这个使用习惯。
可以监听 app 模块的 window-all-closed 事件,并调用 app.quit() 来退出您的应用程序。

通过检查 Node.js 的 process.platform 变量,您可以针对特定平台运行特定代码。 请注意,Electron 目前只支持三个平台:win32 (Windows), linux (Linux) 和 darwin (macOS) 。

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

MacOS 用户的使用习惯并不是这样的。即使该应用的所有窗口关闭了,应用实际也没退出,重新点击它就会立即打开一个窗口。

为了让 Electron 应用在 Mac 平台继续保持这个用户习惯,可以监听 app 模块的 activate 事件。如果没有任何活动的 BrowserWindow,调用 createWindow() 方法新建一个。

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

  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

装载网页到窗口

在 Electron 中,每个窗口展示一个页面,后者可以来自本地的 HTML,也可以来自 URL。

资源来源安全声明

html 文件需要指定加载资源的源:

 <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
      />

如果没有这句meta,electron 会在控制台报警告。因为 electron 对于渲染进程引入的资源有安全限制。
image.png

self 表示自己的。比如当前页面允许引入 b站的资源,则可以在后面补上 b 站的源链接。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
      />
    <meta
      http-equiv="X-Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
      />
    <title>Hello from Electron renderer!</title>
  </head>
  <body>
    <h1>Hello from Electron renderer!</h1>
    <p>👋</p>
  </body>
</html>

SPA 单页应用

一个窗口就是一个网页,一个网页就是一个渲染进程。

网页中的要求是符合 web 技术规范。html 中可以使用 css,引入 js 脚本。
这种原始的基本网页结构有没有似曾相识的感觉?是的,单独一个网页就是 SPA 应用。这里网页引入的 js 脚本完全可以是构建工具打包后的 js 文件。

如果加载的是 SPA,这样一个 electron 窗口,就能实现复杂的功能。
electron 也就是这样和 vue 或 react 结合的。

进程的环境

Chromium 沙盒

Chromium的一个关键安全特性是,有些进程是在沙盒中运行。沙盒隔离了这些进程的风险,同时限制了它的权限。
沙盒里的进程如果要执行权限外的操作,沙盒进程需要通过 IPC 将任务交给权限更大的进程去完成。

Chromium 中除主进程外,其他绝大部分进程都是沙盒进程,其中包括渲染器进程,以及功能性进程,如音频服务、GPU 服务和网络服务。

Electron 主进程

每个 Electron 应用都有一个单一的主进程,作为应用程序的入口点。
Electron 的主进程是一个拥有着完全操作系统访问权限的 Node.js 环境,这意味着它具有 require 模块和使用所有 Node.js API 的能力。

Electron 渲染进程

从 Electron 20 开始,渲染进程也默认启用了沙盒。 electron 中的沙盒进程和 Chromium 中的沙盒差不多。
一个沙盒化的渲染器不会有一个 Node.js 环境,它只有 chromium 网页的环境。因此渲染进程中无法执行 Node.js 代码,比如 require 函数等。

那渲染器进程用户界面怎样才能与 Node.js 和 Electron 的原生桌面功能进行交互。 而事实上,确实没有直接导入 Electron 內容脚本的方法。

配置 Electron 沙盒

对于大多数应用程序来说,沙盒是最佳选择。 在某些与沙盒不兼容的使用情况下(例如,在渲染器中使用原生的 Node.js 模块时),可以禁用特定进程的沙盒。 但这会带来安全风险,特别是当未受信任的代码或内容存在于未沙盒化的进程中时。

为单个进程禁用沙盒。

两种方式二选一:

  1. sandbox: false:关闭该渲染进程沙盒
  2. nodeIntegration: true:允许该渲染进程集成 Node 环境。这个操作也能关闭沙盒
app.whenReady().then(() => {
  const win = new BrowserWindow({
    webPreferences: {
      sandbox: false
      // nodeIntegration: true
    }
  })
  win.loadURL('https://google.com')
})

预加载脚本

概念

为了将 Electron 的不同类型的进程桥接在一起,我们需要使用被称为 预加载 的特殊脚本。

这些脚本虽运行于渲染器的环境中,却具有访问 HTML DOM 和 Node.js、Electron API 的部分权限。
这意味着预加载脚本可以增强沙盒中渲染进程的能力。

什么叫部分权限?
从 Electron 20 开始,预加载脚本也默认 沙盒化 ,不再拥有完整 Node.js 环境的访问权。

这意味着预加载脚本中的 require 函数是个打了 polyfilled 的阉割版函数。它只能加载一些特定的模块。

对于一些页面上想用的第三方 npm 包,你可能会想是否可以在预加载脚本中用,然后再暴露给网页脚本,也就是渲染进程用。想法很美好,现实很骨感。因为是个阉割版,所以这个想法是行不通的。
页面想用第三方 npm 包,必须借助构建工具打包成 SPA 应用。

预加载脚本注入时机就像谷歌浏览器插件中的内容脚本一样。它会在渲染器加载网页之前注入。

什么是内容脚本?
内容脚本是在网页环境中运行的文件。通过使用标准文档对象模型 (DOM),用户能够读取浏览器所访问网页的详细信息,对这些网页进行更改,并将信息传递给父级扩展程序。
比如你想写一个插件,功能是修改网页的背景色,那修改背景色的代码就是内容脚本,它会在渲染器加载网页之前注入。

注册预加载脚本

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

const path = require("node:path");

const createWindow = () => {
    const win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            preload: path.join(__dirname, "preload.js")
        }
    });
    win.loadFile(path.resolve(__dirname, "index.html"));
};
// ...

基本使用

预加载脚本可以增强渲染器,具体怎么做?

可以通过两个对象:

  1. Window
  2. contextBridge

Window 就是 BOM 中的那个 Window 对象。
因为预加载脚本与浏览器共享同一个全局 Window 接口,并且可以访问 Node.js API,所以它通过在全局 window 中暴露任意 API 来增强渲染器,以便你的网页内容使用。
说白了就是预加载脚本把内容挂载 window 对象上,然后网页中的脚本来 window 中取。

// preload.js
window.myAPI = {
  desktop: true
}

// renderer.js
console.log(window.myAPI)
// => undefined

怎么上面结果是 undefined?

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

上下文隔离

上下文隔离意味着预加载脚本与渲染器的主要运行环境是隔离开来的,是两个独立的上下文环境。这对安全性很重要,因为它有助于阻止网站访问 Electron 的内部组件 和 您的预加载脚本可访问的高等级权限的API 。
这意味着,实际上,您的预加载脚本访问的 window 对象并不是网站所能访问的对象。 例如,如果您在预加载脚本中设置 window.hello = ‘wave’ 并且启用了上下文隔离,当网站尝试访问window.hello对象时将返回 undefined。

所以我们將使用 contextBridge 模块来安全地实现交互:

  • contextBridge:在隔离的上下文中创建一个安全的、双向的、同步的桥梁。

通过上下文桥可以安全的将内容挂载到 window 对象上,页面脚本也是从 window 对象上取取数据。

// Preload (Isolated World)
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld(
  'electron',
  {
    doThing: () => ipcRenderer.send('do-a-thing')
  }
)


// Renderer (Main World)
window.electron.doThing()

关闭该渲染进程的上下文隔离:

  • contextIsolation: false

换句话说,要无视风险释放渲染进程的所有能力,就是要允许渲染进程集成 node 环境,并关闭上下文隔离。

app.whenReady().then(() => {
  const win = new BrowserWindow({
    webPreferences: {
       nodeIntegration: true,
       contextIsolation: false
    }
  })
  win.loadURL('https://google.com')
})

结合 ts

另外,如果您正在使用 TypeScript 构建 Electron 应用程序,则必须给通过 context bridge 暴露的 API 添加类型声明。要不然 window 对象拿不到数据。

contextBridge.exposeInMainWorld('electronAPI', {
  loadPreferences: () => ipcRenderer.invoke('load-prefs')
})

您可以创建一个 interface.d.ts 类型声明文件,并且全局增强 Window 接口。

// interface.d.ts
export interface IElectronAPI {
  loadPreferences: () => Promise<void>,
}

declare global {
  interface Window {
    electronAPI: IElectronAPI
  }
}

预加载脚本的主要用途

  1. 实现 IPC 通信。在预加载脚本中暴露 IPC 通信代码。
    1. 注意:IPC 默认是无法在网页脚本中发起的,因为里面都没有 require 函数,都无法加载 ipcRenderer 模块

注意:不要直接暴露 API 函数。这样相当于所有渲染进程都有了 IPC 能力,这上下文隔离隔离了个寂寞。
正确的做法是只暴露具体实现。

// ❌ 错误使用 暴露API
contextBridge.exposeInMainWorld('myAPI', {
  send: ipcRenderer.send
})

// ✅ 正确使用 暴露具体实现
contextBridge.exposeInMainWorld('myAPI', {
  loadPreferences: () => ipcRenderer.invoke('load-prefs')
})
  1. 如果您正在为远程 URL 上托管的现有 web 应用开发 Electron 封裝,则您可在渲染器的 window 全局变量上添加自定义的属性,好在 web 客户端用上仅适用于桌面应用的设计逻辑 。相当于补充一下环境变量信息。

进程间通信

进程间通信 | Electron
ipcMain | Electron
ipcRenderer | Electron

主要会用到两个模块:

  1. 主进程:ipcMain
  2. 渲染进程:ipcRenderer

两者发消息有几种模式:

  1. 渲染进程单方面发给主进程,主进程只管收(只有单向)
  2. 渲染进程单方面发给主进程,主进程不仅能收,还能响应结果给渲染进程(双向沟通了)
  3. 主进程向渲染进程发消息
  4. 渲染进程向另一个渲染进程发消息

第二种模式。双向 IPC 的一个常见应用是从渲染器进程代码调用主进程模块并等待结果。这是最常用的,因为渲染进程比较弱鸡,大部分工作要交给主进程完成,自己只要拿结果。

  1. ipcRenderer.invoke():渲染进程发消息,并在 Promise 回调中获取结果
  2. ipcMain.handle():主进程收到消息,并在回调中返回结果
// 渲染进程 invoke 发消息
ipcRenderer.invoke('some-name', someArgument).then((result) => {
  // 此处拿到主进程返回的结果 result
})

// 主进程 handle 处理消息
ipcMain.handle('some-name', async (event, someArgument) => {
  // 调用主进程里的代码
  const result = await doSomeWork(someArgument)
  // 返回结果给渲染进程
  return result
})

注意:实际上 IPC 通信,会有三个环境参与。渲染进程、预加载脚本环境、主进程。不是上面 API 演示的这么简单。

// preload.js
const { contextBridge, ipcRenderer } = require('electron/renderer')

contextBridge.exposeInMainWorld('electronAPI', {
  openFile: () => ipcRenderer.invoke('dialog:openFile')
})
// renderer.js
const btn = document.getElementById('btn')
const filePathElement = document.getElementById('filePath')

btn.addEventListener('click', async () => {
  const filePath = await window.electronAPI.openFile()
  filePathElement.innerText = filePath
})
// main.js
const handleFileOpen = () => {};

app.whenReady().then(() => {
  ipcMain.handle('dialog:openFile', handleFileOpen)
  createWindow()
})

第三种模式,主进程向渲染进程发消息。

消息需要通过其 WebContents 实例发送到渲染器进程。 此 WebContents 实例包含一个 send 方法,其使用方式与 ipcRenderer.send 相同。
webContents是一个EventEmitter. 负责渲染和控制网页, 是 BrowserWindow 对象的一个属性。

// main.js
function createWindow () {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

  // 主进程发送消息
  mainWindow.webContents.send('update-counter', 1),
}
app.whenReady().then(() => {
  // 主进程监听接收消息
  ipcMain.on('counter-value', (_event, value) => {
    console.log(value) // will print value to Node console
  })
  createWindow()
}
// prelaod.js
const { contextBridge, ipcRenderer } = require('electron/renderer')

contextBridge.exposeInMainWorld('electronAPI', {
  onUpdateCounter: (callback) => ipcRenderer.on('update-counter', (_event, value) => callback(value)),
  counterValue: (value) => ipcRenderer.send('counter-value', value)
})
// renderer.js
const counter = document.getElementById('counter')

// 渲染进程监听接收消息
window.electronAPI.onUpdateCounter((value) => {
  const oldValue = Number(counter.innerText)
  const newValue = oldValue + value
  counter.innerText = newValue.toString()
  // 渲染进程发送消息
  window.electronAPI.counterValue(newValue)
})

案例:番茄钟应用

番茄钟应用 2

  • 26
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Electron 入门指的是学习如何使用 Electron 框架来开发跨平台的桌面应用程序。首先需要了解 Electron 的基本概念和架构,包括主进程和渲染进程、应用程序的生命周期等。然后需要学习如何使用 HTML、CSS 和 JavaScript 来开发界面和逻辑,并且掌握 Electron 提供的 API 来访问操作系统的底层功能,比如文件系统、网络、窗口管理等。最后需要了解如何打包和发布应用程序,以及如何调试和测试应用程序。 ### 回答2: Electron 是一个用于构建跨平台桌面应用程序的开源框架。它使用 HTML、CSS 和 JavaScript 来创建桌面应用程序,并且可以在不同的操作系统上运行,如 Windows、Mac 接来和 Linux。 学习 Electron 入门可以按照以下步骤进行: 1. 准备开发环境:首先要安装 Node.js 和 npm,它们用于安装 Electron。可以在官方网站上下载并安装最新版本的 Node.js。安装完成后,打开终端或命令提示符窗口,运行命令 `npm install -g electron` 安装 Electron。 2. 创建一个新项目:在合适的目录下创建一个新的文件夹作为项目文件夹。在终端或命令提示符中,切换到项目文件夹,并运行命令 `npm init` 创建一个新的 npm 项目。按照提示一步一步地填写项目信息。完成后,会生成一个 `package.json` 文件,用于管理项目依赖和配置。 3. 安装必要的依赖:现在,需要安装 Electron 的依赖。在终端或命令提示符中运行命令 `npm install electron --save-dev` 安装 Electron。 4. 编写主进程代码:在项目文件夹中创建一个新的 JavaScript 文件,作为 Electron 的主进程代码。主进程代码负责启动应用程序并管理窗口。 5. 编写页面代码:创建一个新的 HTML 文件,用于作为应用程序的主页面。 6. 运行 Electron:在终端或命令提示符中,切换到项目文件夹,并运行命令 `electron .` 启动应用程序。应用程序的窗口将会显示出来,同时运行主进程和页面代码。 通过上述步骤,可以快速入门 Electron,并开始构建跨平台桌面应用程序。可以通过学习 Electron 的文档、示例代码和社区资源深入了解和掌握 Electron 的更多特性和功能。 ### 回答3: electron是一种跨平台的开源桌面应用程序框架,可用于构建基于HTML、CSS和JavaScript的桌面应用程序。它通过将现代Web技术与Node.js的强大功能相结合,提供了一种简单而又强大的方式来开发可在Windows、Mac和Linux上运行的桌面应用程序。 要入门electron,首先需要安装electron的开发环境。可以通过npm(Node.js的包管理器)来安装所需的工具和依赖项。然后,创建一个新的electron项目,并安装electron所需的依赖包。接下来,可以使用任何常见的Web开发工具(如HTML、CSS和JavaScript)来进行应用程序的开发。 在electron中,应用程序的主进程由一个main.js文件来控制,而渲染进程由HTML和JavaScript文件来控制。通过main.js文件,可以创建一个主窗口并控制窗口的行为。在渲染进程中,可以使用Web技术来构建应用程序的用户界面,例如使用HTML构建页面结构,使用CSS样式化页面,使用JavaScript实现交互和动态效果。 electron还提供了许多API和功能,使开发者能够与底层操作系统进行交互,例如访问文件系统、显示原生对话框、打开外部链接等。此外,electron还支持构建可打包为独立安装包的应用程序,以便更方便地在不同操作系统上分发和部署应用程序。 总的来说,入门electron需要安装开发环境,创建项目,使用常见的Web技术进行开发,并利用electron提供的API和功能来构建强大的跨平台桌面应用程序。由于其简单易用的特点,electron已成为许多开发者喜爱的桌面应用程序开发框架之一。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值