Electron学习笔记

Electron学习笔记

  1. electron官方文档入口:https://www.electronjs.org/zh/docs/latest/
  2. 官方文档写得不错,笔记记一点重要的东西就好了

基本操作与认识

生命周期

  1. ready:app初始化完成
  2. dom-ready:一个窗口中的文本加载完成
  3. did-finish-load:导航完成时触发
  4. window-all-closed:所有窗口都被关闭时触发
  5. before-quit:在窗口关闭前触发
  6. will-quit:在窗口关闭且应用退出时触发
  7. quit:当所有窗口都被关闭时触发

创建并初始化项目

mkdir my-electron-app // 创建项目
cd my-electron-app // 转移到项目文件夹
yarn init // 入口点应当是main.js
yarn add electron --dev // 安装electron

窗口尺寸

ES语法

Electron 目前对 ECMAScript 语法 (如使用 import 来导入模块) 的支持还不完善

不同平台

可以检查 Node.js 的 process.platform 变量,帮助我们在不同操作系统上运行特定代码。 请注意,Electron 目前只支持三个平台:win32 (Windows), linux (Linux) 和 darwin (macOS)

进程通信

Electron 的主进程和渲染进程有着清楚的分工并且不可互换。 这代表着无论是从渲染进程直接访问 Node.js 接口,亦或者是从主进程访问 HTML 文档对象模型 (DOM),都是不可能的。

增加应用的复杂程度

  1. 增加渲染进程的网页应用代码复杂度
  2. 深化与操作系统和 Node.js 的集成
    1. Electron提供了丰富的工具集,可以让你和桌面环境整合起来。从建立托盘图标到添加全局的快捷方式,再到显示原生的菜单,都不在话下
    2. Electron 还赋予你在主进程中访问 Node.js 环境的所有能力

这组能力使得 Electron 应用能够从浏览器运行网站中脱胎换骨

打包

  1. 安装electron forge
yarn add --dev @electron-forge/cli
npx electron-forge import
  1. 打包程序
yarn run make
  1. 这个曾经失败过的打包过程,是这样解决的:打包的时候项目是不能运行的,需要先把项目关掉,打包才能正常进行下去。


Electron中的流程

流程模型

作为应用开发者,你将控制两种类型的进程:主进程 和 渲染器进程

主进程
  1. 主要目的是使用 BrowserWindow 模块创建和管理应用程序窗口
  2. 能通过 Electron 的 app 模块来控制您应用程序的生命周期
  3. 主进程添加了自定义的 API 来与用户的作业系统进行交互, Electron 有着多种控制原生桌面功能的模块,例如菜单、对话框以及托盘图标
渲染器进程
  1. 每个 Electron 应用都会为每个打开的 BrowserWindow ( 与每个网页嵌入 ) 生成一个单独的渲染器进程
  2. 以一个 HTML 文件作为渲染器进程的入口点
  3. 渲染器无权直接访问 require 或其他 Node.js API。 为了在渲染器中直接包含 NPM 模块,您必须使用与在 web 开发时相同的打包工具 (例如 webpack 或 parcel)
  4. 没有直接导入 Electron 內容脚本的方法
Preload脚本
  1. 预加载(preload)脚本包含了那些执行于渲染器进程中,且先于网页内容开始加载的代码 。 这些脚本虽运行于渲染器的环境中,却因**能访问 Node.js API **而拥有了更多的权限。
  2. 预加载脚本可以在 BrowserWindow 构造方法中的 webPreferences 选项里被附加到主进程
  3. 通过在全局 window 中暴露任意 API 来增强渲染器,以便网页内容使用,但是因为语境隔离(Context Isolation)的原因,我们只能通过contextBridge模块与ipcRenderer模块来进行安全地交互

上下文隔离

  1. 上下文隔离功能将确保您的 预加载脚本 和 Electron的内部逻辑 运行在所加载的 webcontent网页 之外的另一个独立的上下文环境里
  2. 这对安全性很重要,因为它有助于阻止网站访问 Electron 的内部组件 和 您的预加载脚本可访问的高等级权限的API
安全事项
  1. 暴露进程间通信相关 API 的正确方法是为每一种通信消息提供一种实现方法
// ✅ 正确使用
contextBridge.exposeInMainWorld('myAPI', {
  loadPreferences: () => ipcRenderer.invoke('load-prefs')
})
// ❌ 错误使用 
// 它直接暴露了一个没有任何参数过滤的高等级权限 API
// 这将允许任何网站发送任意的 IPC 消息
contextBridge.exposeInMainWorld('myAPI', {
  send: ipcRenderer.send
})

进程间通信

在 Electron 中,进程使用 ipcMainipcRenderer 模块,通过开发人员定义的“通道”传递消息来进行通信

渲染器进程到主进程(单向)

要将单向 IPC 消息从渲染器进程发送到主进程,您可以使用 ipcRenderer.send API 发送消息,然后使用 ipcMain.on API 接收

渲染器进程到主进程(双向)

双向 IPC 的一个常见应用是从渲染器进程代码调用主进程模块并等待结果。 这可以通过将 ipcRenderer.invokeipcMain.handle 搭配使用来完成

主进程到渲染器进程

将消息从主进程发送到渲染器进程时,需要指定是哪一个渲染器接收消息。 消息需要通过其 WebContents 实例发送到渲染器进程。 此 WebContents 实例包含一个 send 方法,其使用方式与 ipcRenderer.send 相同

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello from Electron renderer!</title>
</head>
<body>
    <p id="info"></p>
    Title: <input id="title"/>
    <button id="setButton" type="button">Set</button>
    <button type="button" id="btn">Open a File</button>
    File path: <strong id="filePath"></strong>
    Current value: <strong id="counter">0</strong>
</body>
<script src="./renderer.js"></script>
</html>

main.js

// Electron 目前对 ECMAScript 语法 (如使用 import 来导入模块) 的支持还不完善

// app 控制您的应用的事件生命周期
// BrowserWindow 它负责创建和管理应用的窗口
const { app, BrowserWindow, ipcMain, dialog, Menu  } = require('electron')
const path = require('path')

function handleSetTitle(event, title){ // 标题设置
  // 确认渲染器
  const webContents = event.sender
  // 修改渲染器title
  const win = BrowserWindow.fromWebContents(webContents)
  win.setTitle(title)
}

async function handleFileOpen() { // 异步函数 打开文件
  // 原生的打开文件对话框
  const { canceled, filePaths } = await dialog.showOpenDialog()
  if (canceled) {
    return
  } else { // 返回第一个文件路径
    return filePaths[0]
  }
}

const createWindow = () => { // 创建页面
  // 应用设置
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    // 将脚本附在渲染进程上
    // __dirname: 指向当前正在执行脚本的路径
    // path.join: 将多个路径联结在一起,创建一个跨平台的路径字符串。
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
    },
  })

  const menu = Menu.buildFromTemplate([
    {
      label: app.name,
      submenu: [
        {
          click: () => win.webContents.send('update-counter', 1),
          label: 'Increment',
        },
        {
          click: () => win.webContents.send('update-counter', -1),
          label: 'Decrement',
        }
      ]
    }

  ])

  Menu.setApplicationMenu(menu)

  // 加载主页面
  win.loadFile('index.html')

  // Open the DevTools.
  // win.webContents.openDevTools()
}

app.whenReady().then(() => { // 应用就绪
  // 使用 ipcMain.on API 在 set-title 通道上设置一个 IPC 监听器
  ipcMain.on("set-title", handleSetTitle)
  // ipcMain.handle用于双向通信 监听
  ipcMain.handle('dialog:openFile', handleFileOpen)
  // 主进程向渲染器进程发送信息
  ipcMain.on('counter-value', (_event, value) => {
    console.log(value) // will print value to Node console
  })
  // 创建页面
  createWindow()

  // activate事件 监听活动状态?
  app.on('activate', () => {
    // 如果没有任何活动的窗口
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})


app.on('window-all-closed', () => { // 事件 所有窗口关闭之后
  // 调用app.quit()方法退出应用 此方法不适用于macOS
  if (process.platform !== 'darwin') app.quit()
})

renerer.js

// 使用DOM接口来替换 id 属性为 info 的 HTML 元素显示文本
const information = document.getElementById('info')
information.innerText = `本应用正在使用Chrome (v${versions.chrome()}),Node.js (v${versions.node()}),和Electron (v${versions.electron()})`

// 单向通信
const setButton = document.getElementById('setButton')
const titleInput = document.getElementById('title')
setButton.addEventListener('click', () => {
    const title = titleInput.value
    // 进程通信 渲染器调用了通过preload脚本暴露出来的electronAPI的setTitle方法 并传进去了一个参数title
    window.electronAPI.setTitle(title)
    
});

// 双向通信
const btn = document.getElementById('btn')
const filePathElement = document.getElementById('filePath')

btn.addEventListener('click', async () => { // 因为文件是需要等待用户操作的所以是异步调用吗
  // 进程通信 调用暴露的方法openFile 等待返回值filePath
  const filePath = await window.electronAPI.openFile()
  filePathElement.innerText = filePath
})

// 主进程向渲染器进程发送信息
const counter = document.getElementById('counter')

window.electronAPI.onUpdateCounter((_event, value) => {
    const oldValue = Number(counter.innerText)
    const newValue = oldValue + value
    counter.innerText = newValue
})

preload.js

// 创建一个将应用中的 Chrome、Node、Electron 版本号暴露至渲染器的预加载脚本
// ipcRenderer用于进程间的通信
const { contextBridge, ipcRenderer  } = require('electron')

// 在主进程中通过versions进行暴露
contextBridge.exposeInMainWorld('versions', {
    node: () => process.versions.node,
    chrome: () => process.versions.chrome,
    electron: () => process.versions.electron,
    // 能暴露的不仅仅是函数,我们还可以暴露变量
})

contextBridge.exposeInMainWorld('electronAPI', {
    // ipcRenderer.send单向发送信息 往set-title通道发送信息 传递参数title
    setTitle: (title) => ipcRenderer.send('set-title', title),
    // ipcRenderer.invoke双向发送信息
    openFile: () => ipcRenderer.invoke('dialog:openFile'),
    // 
    handleCounter: (callback) => ipcRenderer.on('update-counter', callback)
})


API

nativeTheme

  1. 作用:读取并响应Chromium本地色彩主题中的变化
  2. 进程:主进程
shouldUseDarkColors
  1. 只读
  2. 此属性的值为一个 boolean 类型的值,代表着当前OS / Chromium是否正处于dark模式,或者应用程序是否正被建议使用dark模式的皮肤
themeSource
  1. 一个类型为string的属性,此属性可能的值为:system, light or dark. 它被用来覆盖、重写Chromium内部的相应的值
  2. 默认情况下 themeSource 是 system
  3. 将此属性设置为 dark 将产生以下效果:
    1. 当访问 nativeTheme.shouldUseDarkColors 时值为 true
    2. 任何在 Linux 和 Windows 上的 UI Electron 渲染,包括 context menus、devtools 等等,都会使用暗色界面。
    3. 在 MacOS 上打开的任何 UI 界面,包括 menus、window frames 等,渲染时都会使用暗色界面。
    4. prefers-color-scheme CSS 查询将匹配 dark 模式(这个是我们用来改变页面样式的)
    5. updated 事件将被触发
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值