Electron

Posted by Kerwen Blog on December 12, 2023

Electron

简介

Electron 是一个可以用 JavaScript、HTML 和 CSS 构建桌面应用程序的库。这些应用程序能打包到 Mac、Windows 和 Linux 系统上运行,也能上架到 Mac 和 Windows 的 App Store。 意思就是说,你只要拥有前端开发的能力,也可以轻松开发跨平台的桌面应用.

Electron 有两种进程:主进程渲染进程。部分模块只能在两者之一上运行,而有些则无限制。主进程更多地充当幕后角色,而渲染进程则是应用程序的各个窗口。

主进程
主进程,通常是一个命名为 main.js 的文件,该文件是每个 Electron 应用的入口。一个 Electron 应用只能存在一个主进程。它控制了应用的生命周期(从打开到关闭)。它既能调用原生元素,也能创建新的(多个)渲染进程。另外,Node API 是内置其中的。

渲染进程(renderer)
渲染进程是应用的一个浏览器窗口。与主进程不同,它能存在多个并且相互独立(它也能是隐藏的)。

主窗口通常被命名为 index.html。它们就像典型的 HTML 文件,但 Electron 赋予了它们完整的 Node API。因此,这也是它与浏览器的区别。

进程之间的通讯 (IPC Inter-process communication)
想要在网页里调用主进程的功能,比如关闭窗口,最小化全屏等主线程才能控制的功能. Electron提供了通讯的机制,这就是IPC.

Hello World

  1. 首先创建一个文件夹并初始化 npm 包。

    1
    2
    
     mkdir my-electron-app && cd my-electron-app
     npm init
    
  2. entry point改为main.js.
  3. 然后,将 electron 包安装到应用的开发依赖中。

    1
    
     npm install --save-dev electron
    
  4. 在package.json配置文件中的scripts字段下增加一条start命令:

    1
    2
    3
    4
    5
    
     {
       "scripts": {
         "start": "electron ."
       }
     }
    
  5. 在项目的根目录下创建一个名为main.js的空文件。
  6. 在您的项目根目录下创建一个名为index.html的文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
     <!DOCTYPE html>
     <html>
       <head>
         <meta charset="UTF-8">
         <!-- https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP -->
         <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
         <title>你好!</title>
       </head>
       <body>
         <h1>你好!</h1>
         我们正在使用 Node.js <span id="node-version"></span>,
         Chromium <span id="chrome-version"></span>,
         和 Electron <span id="electron-version"></span>.
       </body>
     </html>
    
  7. 现在我们有了一个html页面,如果想将它加载进应用窗口中,需要两个Electron模块:

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

    在 main.js 文件头部将它们导入作为 CommonJS 模块:

    1
    
     const { app, BrowserWindow } = require('electron')
    
  8. 添加一个createWindow()方法来将index.html加载进一个新的BrowserWindow实例。

    1
    2
    3
    4
    5
    6
    7
    8
    
     const createWindow = () => {
       const win = new BrowserWindow({
         width: 800,
         height: 600
       })
    
       win.loadFile('index.html')
     }
    
  9. 调用createWindow()函数来打开您的窗口。在 Electron 中,只有在 app 模块的 ready 事件被激发后才能创建浏览器窗口。 您可以通过使用 app.whenReady() API来监听此事件。 在whenReady()成功后调用createWindow()。

    1
    2
    3
    
     app.whenReady().then(() => {
       createWindow()
     })
    
  10. 管理窗口的生命周期
    虽然你现在可以打开一个浏览器窗口,但你还需要一些额外的模板代码使其看起来更像是各平台原生的。 应用程序窗口在每个OS下有不同的行为,Electron将在app中实现这些约定的责任交给开发者们。
    在Windows和Linux上,关闭所有窗口通常会完全退出一个应用程序。
    为了实现这一点,你需要监听 app 模块的 ‘window-all-closed’ 事件。如果用户不是在 macOS(darwin) 上运行程序,则调用 app.quit()。

    1
    2
    3
    
    app.on('window-all-closed', () => {
      if (process.platform !== 'darwin') app.quit()
    })
    
  11. 通过预加载脚本从渲染器访问Node.js
    创建一个名为 preload.js 的新脚本如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    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])
      }
    })
    
  12. 修改main.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    const { app, BrowserWindow } = require('electron')
    // include the Node.js 'path' module at the top of your file
    const path = require('node:path')
    
    // modify your existing createWindow() function
    const createWindow = () => {
      const win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
          preload: path.join(__dirname, 'preload.js')
        }
      })
    
      win.loadFile('index.html')
    }
    // ...
    

打包

可以使用以下方式进行打包:

electron-forge
electron-builder
electron-packager

Preload

预加载(preload)脚本包含了那些执行于渲染器进程中,且先于网页内容开始加载的代码 。预加载脚本可以在 BrowserWindow 构造方法中的 webPreferences 选项里被附加到主进程。

1
2
3
4
5
6
7
8
const { BrowserWindow } = require('electron')
// ...
const win = new BrowserWindow({
  webPreferences: {
    preload: 'path/to/preload.js'
  }
})
// ...

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

1
2
3
4
5
const { contextBridge } = require('electron')

contextBridge.exposeInMainWorld('myAPI', {
  desktop: true
})

renderer.js

1
2
console.log(window.myAPI)
// => { desktop: true }

electron-forge

最快捷的打包方式是使用 Electron Forge。

  1. 在package.json中添加description,不然Build会失败
  2. 添加 Electron Forge 依赖

    1
    2
    
     npm install --save-dev @electron-forge/cli
     npx electron-forge import
    
  3. 使用 Forge 的 make 命令来创建可分发的应用程序:

    1
    
     npm run make
    

    Electron-forge 会创建 out 文件夹

electron-builder

待添加

在Electron中使用Angular

  1. 安装最新angular-cli

    1
    
     npm i -g @angular/cli
    
  2. 生成一个angular工程

    1
    
     ng new first-angular-electron-app
    
  3. 安装最新版electron

    1
    2
    
     cd first-angular-electron-app
     npm install --save-dev electron@latest
    
  4. 在项目根目录添加主进程 main.js文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    
     const { app, BrowserWindow , ipcMain } = require('electron');
     const  path  = require('path')
     const electron = require('electron')
    
     let win;
     const Menu = electron.Menu
     function createWindow () {
       // Hide menu
       Menu.setApplicationMenu(null)
       // 创建浏览器窗口。
       win = new BrowserWindow({
         width: 1000,
         height: 670 ,
       });
    
       // 然后加载应用的 index.html。
       win.loadURL(`file://${__dirname}/dist/first-angular-electron-app/browser/index.html`);
       // 打开开发者工具
       win.webContents.openDevTools();
    
    
       // 当 window 被关闭,这个事件会被触发。
       win.on('closed', () => {
         // 取消引用 window 对象,如果你的应用支持多窗口的话,
         // 通常会把多个 window 对象存放在一个数组里面,
         // 与此同时,你应该删除相应的元素。
         win = null;
       });
     }
    
     // Electron 会在初始化后并准备
     // 创建浏览器窗口时,调用这个函数。
     // 部分 API 在 ready 事件触发后才能使用。
     app.on('ready', createWindow);
    
     // 当全部窗口关闭时退出。
     app.on('window-all-closed', () => {
       // 在 macOS 上,除非用户用 Cmd + Q 确定地退出,
       // 否则绝大部分应用及其菜单栏会保持激活。
       if (process.platform !== 'darwin') {
         app.quit();
       }
     });
    
     app.on('activate', () => {
       // 在macOS上,当单击dock图标并且没有其他窗口打开时,
       // 通常在应用程序中重新创建一个窗口。
       if (win === null) {
         createWindow();
       }
     });
    
     // 在这个文件中,你可以续写应用剩下主进程代码。
     // 也可以拆分成几个文件,然后用 require 导入。
    
    
     // ipc通讯相关代码
     // 登录窗口最小化
     ipcMain.on('window-min', function() {
       win.minimize();
     });
     // 登录窗口最大化
     ipcMain.on('window-max', function() {
       if (win.isMaximized()) {
         win.restore();
       } else {
         win.maximize();
       }
     });
     ipcMain.on('window-close', function() {
       win.close();
     });
    
  5. package.json 文件修改
    添加main.js的位置与npm scripts electron命令

    1
    2
    3
    4
    5
    6
    
     {
       "scripts": {
         "electron": "ng build && electron .",
       },
         "main": "main.js",
     }
    
  6. 修改src/index.html

    1
    2
    3
    
     <base href="/">
     改为
     <base href="./">
    
  7. 运行命令行

    1
    
     npm run electron
    

Angular-electron

Angular-electron 这是一个结合 Angular 和 Electron 的项目。以此为基准环境,不需要从头构建。

使用

将代码库克隆到本地:

1
git clone https://github.com/maximegris/angular-electron.git

使用 npm 安装依赖项

1
npm install

使用 npm 安装 NodeJS 依赖项(由 Electron 主进程使用):

1
2
cd app/
npm install

angular-electron使用了两个package.json, 遵循 Electron Builder Two package.json Structure,以优化最终的 bundle 并且仍然能够使用 Angular 功能

编译运行

1
npm start

应用程序代码由app/main.ts管理。在运行npm start时,会同时在http://localhost:4200和 Electron 窗口运行

如果只想运行浏览器,使用以下命令

1
npm run ng:serve
命令 解释
npm run ng:serve 在 Web 浏览器中执行应用(dev mode)
npm run web:build build可直接在 Web 浏览器中使用的应用。生成的文件位于 /dist 文件夹中
npm run electron:local build应用程序并启动 electron
npm run electron:build 基于当前操作系统build应用程序

项目结构

文件夹 解释
app Electron 主进程文件夹
src Electron渲染器进程文件夹(Web/Angular)

导入第三方库

由于项目在两种模式(Web 和 Electron)下运行。因此必须以正确的方式导入依赖项。
有两种第三方库:

  • 使用 NodeJS 核心模块(crypto、fs、util…)。建议在两个package.json中都添加这个第三方库,以便使其在 Electron 的主进程(app folder)和 Electron 的渲染器进程(src folder)中都能工作。
  • Web相关(比如 bootstrap、material…)
    必须添加到 root文件夹下 package.jsondependencies

如果添加依赖时需要用到ng-add,则可能会更麻烦。因为项目无法使用默认的@angular-builders。比如,使用Angular-Material

添加Angular-Material

  1. 需要临时修改angular.json, 经编译器改为Angular默认的编译器

    @angular-builders/custom-webpack:browser => @angular-devkit/build-angular:browser
    @angular-builders/custom-webpack:dev-server => @angular-devkit/build-angular:dev-server

  2. 使用命令行添加 Angular material

    1
    
     ng add @angular/material
    
  3. 将自定义builder放回angular.json

    @angular-devkit/build-angular:browser => @angular-builders/custom-webpack:browser
    @angular-devkit/build-angular:dev-server => @angular-builders/custom-webpack:dev-server

调试

https://www.electronjs.org/zh/docs/latest/tutorial/tutorial-first-app#%E5%8F%AF%E9%80%89%E4%BD%BF%E7%94%A8-vs-code-%E8%B0%83%E8%AF%95

Reference

Quick Start
Angular for Desktop Applications with Electron
Creating a Desktop App with Electron and Angular
angular-electron
从零搭建 Angular7 + Electron 桌面应用
Two package.json Structure
electron-builder