electron 入门教程

介绍

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

安装

shell 复制代码
npm install --save-dev electron

由于众所周知的原因,你可能安装不上依赖,此时可以设置镜像。

首先查看你的镜像源。

shell 复制代码
nrm ls

确保你使用的镜像源是 npm 源。

打开npm的配置文件:

shell 复制代码
npm config edit

在里面新增如下配置:

shell 复制代码
registry=https://registry.npmmirror.com
electron_mirror=https://cdn.npmmirror.com/binaries/electron/
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/

然后关闭窗口,重新安装依赖。

创建第一个应用程序

在 Electron 中,每个窗口展示一个页面,后者可以来自本地的 HTML,也可以来自远程 URL。 在本例中,您将会装载本地的文件。 在您项目的根目录中创建一个 index.html 文件,并写入下面的内容:

html 复制代码
<!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>

在根目录下面新增 index.js,内容如下:

js 复制代码
const { app, BrowserWindow } = require('electron')

// createWindow() 函数将您的页面加载到新的 BrowserWindow 实例中
const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600
  })

  win.loadFile('index.html')
}

// 在应用准备就绪时调用函数
app.whenReady().then(() => {
    createWindow()
    // 如果没有窗口打开则打开一个窗口 (macOS)
    app.on('activate', () => {
        if (BrowserWindow.getAllWindows().length === 0) {
            createWindow()
        }
    })
})

// 关闭所有窗口时退出应用 (Windows & Linux)
// 监听 app 模块的 window-all-closed 事件,并调用 app.quit() 来退出您的应用程序。此方法不适用于 macOS
app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') app.quit()
})
  • app: 管理应用程序的事件生命周期。
  • BrowserWindow: 负责创建和管理应用窗口。

配置 package.json 的文件:

json 复制代码
{
  "scripts": {
    "main": "index.js",
    "start": "electron ."
  }
}

现在运行 npm run start 就可以启动项目了。

使用预加载脚本

Electron 的主进程是一个拥有着完全操作系统访问权限的 Node.js 环境。 除了 Electron 模组 之外,您也可以访问 Node.js 内置模块 和所有通过 npm 安装的包。 另一方面,出于安全原因,渲染进程默认跑在网页页面上,而并非 Node.js里。

BrowserWindow 的预加载脚本运行在具有 HTML DOM 和 Node.js、Electron API 的有限子集访问权限的环境中。

预加载脚本在渲染器加载网页之前注入。 如果你想为渲染器添加需要特殊权限的功能,可以通过 contextBridge 接口定义 全局对象。

在项目的根目录新建一个 preload.js 文件。该脚本通过 versions 这一全局变量,将 Electron 的 process.versions 对象暴露给渲染器。

js 复制代码
// preload.js
const { contextBridge } = require('electron')

contextBridge.exposeInMainWorld('versions', {
  node: () => process.versions.node,
  chrome: () => process.versions.chrome,
  electron: () => process.versions.electron
  // 除函数之外,我们也可以暴露变量
})

为了将脚本附在渲染进程上,在 BrowserWindow 构造器中使用 webPreferences.preload 传入脚本的路径。

js 复制代码
// index.js
const { app, BrowserWindow } = require('electron')
// 新增下面这行代码
const path = require('node:path')

const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600, 
    // 新增
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

  win.loadFile('index.html')
}

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

在项目的根目录新增一个 renderer.js 文件,内容如下:

js 复制代码
const information = document.getElementById('info')
information.innerText = `本应用正在使用 Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), 和 Electron (v${versions.electron()})`

然后把 renderer.js 脚本插入到 index.html 文件的 body 标签后面。

js 复制代码
<script src="./renderer.js"></script>

总结一下:我们在 createWindow 的时候把 preload.js 插入到了 html 文件中,通过 contextBridge 设置了全局对象,在渲染进程就能访问到我们设置的特殊全局属性 versions。

在进程之间通信(双向)

单向 IPC 消息从渲染器进程发送到主进程,您可以使用 ipcRenderer.send API 发送消息,然后使用 ipcMain.on API 接收。比较简单,请查看官网。

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

解决这一问题的方法是使用进程间通信 (IPC)。可以使用 Electron 的 ipcMain 模块和 ipcRenderer 模块来进行进程间通信。 为了从你的网页向主进程发送消息,你可以使用 ipcMain.handle 设置一个主进程处理程序(handler),然后在预处理脚本中暴露一个被称为 ipcRenderer.invoke 的函数来触发该处理程序(handler)。

我们将向渲染器添加一个叫做 ping() 的全局函数,该函数返回一个字符串 'pong'。

修改 preload.js 文件:

js 复制代码
// preload.js
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('versions', {
  node: () => process.versions.node,
  chrome: () => process.versions.chrome,
  electron: () => process.versions.electron,
  ping: () => ipcRenderer.invoke('ping') // 新增一个全局方法 versions.ping()
  // 除函数之外,我们也可以暴露变量
})

在主进程中设置 handle 监听器:

js 复制代码
// index.js
const { app, BrowserWindow, ipcMain } = require('electron/main')
const path = require('node:path')

const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })
  win.loadFile('index.html')
}
app.whenReady().then(() => {
    // 监听渲染进程触发的方法,并返回一个字符串
  ipcMain.handle('ping', () => 'pong')
  createWindow()
})

在 renderer.js 中新增下面的内容:

js 复制代码
// renderer.js
const func = async () => {
    // 调用 'ping' 方法,并等待返回值
  const response = await window.versions.ping()
  console.log(response) // 打印 'pong'
}

func()

我们已经知道如何使用进程间的通信,如此一来就能在渲染进程,点击按钮触发主进程的相关操作。根据官网案例,增加案例复杂程度,设计一个可以切换白天/黑暗模式的桌面软件。

代码仓库

主进程到渲染器进程

  1. 使用 webContents 模块发送消息
js 复制代码
// index.js 省略部分代码
const createWindow = () => {
    const win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            preload: path.join(__dirname, 'preload.js')
        }
    })

    // 使用 webContents.send() 来向渲染进程发送消息
    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')
    // 打开开发工具
    win.webContents.openDevTools()
}
  1. 通过预加载脚本暴露 ipcRenderer.on
js 复制代码
// preload.js
contextBridge.exposeInMainWorld('electronAPI', {
    onUpdateCounter: (callback) => ipcRenderer.on('update-counter', (_event, value) => callback(value)),
})
  1. 在渲染页面监听事件
js 复制代码
// renderer.js 省略部分代码
const counter = document.getElementById('counter')

window.electronAPI.onUpdateCounter((value) => {
  const oldValue = Number(counter.innerText)
  const newValue = oldValue + value
  counter.innerText = newValue.toString()
})
  1. 可选:返回一个回复

对于从主进程到渲染器进程的 IPC,没有与 ipcRenderer.invoke 等效的 API。 不过,您可以从 ipcRenderer.on 回调中将回复发送回主进程。

js 复制代码
// preload.js
contextBridge.exposeInMainWorld('electronAPI', {
    onUpdateCounter: (callback) => ipcRenderer.on('update-counter', (_event, value) => callback(value)),
    // 给主进程发送一个 counter-value 事件
    counterValue: (value) => ipcRenderer.send('counter-value', value)
})

在主进程中,监听 counter-value 事件并适当地处理它们。

js 复制代码
ipcMain.on('counter-value', (_event, value) => {
  console.log(value) // will print value to Node console
})

完成官方的案例,实现一个客户端菜单操作。

代码仓库同上

渲染进程之间进行通信

没有直接的方法可以使用 ipcMain 和 ipcRenderer 模块在 Electron 中的渲染器进程之间发送消息。 为此,您有两种选择:

  • 将主进程作为渲染器之间的消息代理。 这需要将消息从一个渲染器发送到主进程,然后主进程将消息转发到另一个渲染器。
  • 从主进程将一个 MessagePort 传递到两个渲染器。 这将允许在初始设置后渲染器之间直接进行通信。

第一种方式,相当于把主进程当作中间的桥梁,交互比较复杂。直接上代码 渲染进程之前进行通信演示

第二种方式,通过 MessageChannelMain 建立2个通道,在通过 webContents.postMessage 在主进程把对应的通道发送到渲染进程。preload.js 中我们可以监听 'port' 事件,通过 window.postMessage 将 port 端口发送到同源的页面上。同样的,同源的页面也可以用 window.onmessage 监听发送来的数据。

js 复制代码
// index.js 
// 省略部分代码
// 创建窗口
const mainWindow = new BrowserWindow({
    webPreferences: {
        contextIsolation: true, // Electron 12.0.0 及以上版本默认启用
        preload: path.join(__dirname, 'preloadMain.js')
    }
})
mainWindow.loadFile('index.html')
mainWindow.webContents.openDevTools()

const secondaryWindow = new BrowserWindow({
    webPreferences: {
        preload: path.join(__dirname, 'preloadSecondary.js')
    }
})
secondaryWindow.loadFile('second.html')
secondaryWindow.webContents.openDevTools()

// 建立通道
const { port1, port2 } = new MessageChannelMain()

// webContents准备就绪后,使用postMessage向每个webContents发送一个端口。
mainWindow.once('ready-to-show', () => {
    // 通知渲染进程,这里有一个 port 方法,可以与另外一个窗口通信
    mainWindow.webContents.postMessage('port', null, [port1])
})

secondaryWindow.once('ready-to-show', () => {
    secondaryWindow.webContents.postMessage('port', null, [port2])
})
js 复制代码
// preload.js
const { ipcRenderer } = require('electron')

const windowLoaded =  new Promise((relose)=> {
    window.onload = relose
})

ipcRenderer.on('port', async (event) => {
    await windowLoaded // 等待页面加载完成
    window.postMessage('channel-port', '*', event.ports)
})
html 复制代码
<!--渲染进程页面-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>Main Page</h1>
    <script>
        window.onmessage = (event) => {
            if (event.source === window && event.data === 'channel-port') {
                const [port] = event.ports;
                port.onmessage = (event) => { // 打印接收到的消息
                    console.log(event.data);
                }
                port.postMessage('i am main page'); // 测试发送消息
            }
        }
    </script>
</body>
</html>

完整代码,请查看 渲染进程之间直接通信

使用 Electron Forge 打包

Electron 的核心模块中没有捆绑任何用于打包或分发文件的工具。 如果您在开发模式下完成了一个 Electron 应用,需要使用额外的工具来打包应用程序 (也称为可分发文件) 并分发给用户 。 可分发文件可以是安装程序 (例如 Windows 上的 MSI) 或者绿色软件 (例如 macOS 上的 .app 文件)。

Electron Forge 是一个处理 Electron 应用程序打包与分发的一体化工具。 在工具底层,它将许多现有的 Electron 工具 (例如 @electron/packager、 @electron/osx-sign、electron-winstaller 等) 组合到一起,因此您不必费心处理不同系统的打包工作。

安装依赖

shell 复制代码
npm install --save-dev @electron-forge/cli @electron/fuses @electron-forge/plugin-fuses
npx electron-forge import

转换脚本完成后,Forge 会将一些脚本添加到您的 package.json 文件中。

json 复制代码
{
  "scripts": {
    "start": "electron-forge start",
    "package": "electron-forge package",
    "make": "electron-forge make"
  }
}

要创建可分发文件,请使用项目中的 make 脚本,该脚本最终运行了 electron-forge make 命令。

shell 复制代码
npm run make

该 make 命令包含两步:

它将首先运行 electron-forge package ,把您的应用程序 代码与 Electron 二进制包结合起来。 完成打包的代码将会被生成到一个特定的文件夹中。 然后它将使用这个文件夹为每个 maker 配置生成一个可分发文件。 在脚本运行后,您应该看到一个 out 文件夹,其中包括可分发文件与一个包含其源码的文件夹。

最后一步,我们需要对代码进行签名,请查看其他资料。

相关推荐
发现你走远了6 分钟前
『VUE』elementUI dialog的子组件created生命周期不刷新(详细图文注释)
javascript·vue.js·elementui
22508249077 分钟前
【无标题】
javascript·vue.js·elementui
音仔小瓜皮8 分钟前
【bug记录10】同一iOS webview页面中相同的两个svg图标出现部分显示或全部不显示的情况
前端·bug·html5·xhtml·webview
涔溪23 分钟前
css3弹性布局
前端·css·css3
Array[赵]36 分钟前
npm 最新国内淘宝镜像地址源 (旧版已不能用)
前端·npm·node.js
于慨37 分钟前
pnpm报错如Runing this command will add the dependency to the workspace root所示
前端·npm
李豆豆喵1 小时前
第29天:安全开发-JS应用&DOM树&加密编码库&断点调试&逆向分析&元素属性操作
开发语言·javascript·安全
田本初1 小时前
【NodeJS】Express写接口的整体流程
前端·javascript·node.js·express
知野小兔1 小时前
【Vue】Keep alive详解
前端·javascript·vue.js
好奇的菜鸟1 小时前
TypeScript中的接口(Interface):定义对象结构的强类型方式
前端·javascript·typescript