electron教程

介绍

Electron 基于 Chromium + Node.js

基本目录结构:

-index.html
-main.js
-render.js
-preload.js
-package.json

electron中主要分为 主进程和渲染进程。

  • 主进程:main.js, 可在package.json中配置,相当于后台服务
  • 渲染进程:每个窗口对应 1 个渲染进程,前端网页。
  • 渲染脚本:render.js,是给渲染进程准备的js,用于与主进程通信,渲染数据等。一个窗口可以加载自己的渲染脚本,也可以多个窗口共享同一个渲染脚本。
  • preload.js: 用于main与render之间的通信。两者之间也能直接通信,不安全

进程数量对应情况:

  • 主进程:固定1个
  • 渲染进程:每个窗口 1 个默认。每个渲染进程都有 Chromium 的渲染能力,所以可以理解为 3 个内核实例。Chromium 内部还有 GPU 进程和网络进程,它们可能是共享的
  • GPU进程/网络进程等:不同窗口共享或独立,Chromium 内部使用

调用加载顺序:

1.package.json中配置指定main.js文件
入口相当于main函数:app.whenReady().then(createWindow);
main函数中创建BrowserWindow,注册 ipcMain 事件,加载index.html等操作

2.preload.js在browserwindow创建时,加载index.html之前执行,可访问 Node API 和 ipcRenderer,然后通过 contextBridge 向页面暴露有限 API。

3.渲染进程:index.html被加载渲染进程就启动,html+js(render.js)在渲染进程的DOM中执行。启用了 preload,页面脚本可以通过 window.api(或你暴露的对象)调用主进程通信接口。

main与render通信

1.send和on

render调用main

render.js中:
const { ipcRenderer } = require('electron')
ipcRenderer.send('toMain', 'Hello from renderer')

main.js中:
const { ipcMain } = require('electron')
ipcMain.on('toMain', (event, message) => {
  console.log('Received message from renderer:', message)
  // 回复 renderer 进程
  event.reply('fromMain', 'Hello from main')
})

main调用render

// main.js
const { BrowserWindow } = require('electron')
// 拿到窗口实例
const win = new BrowserWindow({
  webPreferences: {
    preload: path.join(__dirname, 'preload.js'),
    contextIsolation: true
  }
})
// 发送消息到 renderer
win.webContents.send('fromMain', 'Hello Renderer!')

// renderer.js
const { ipcRenderer } = require('electron')
// 监听来自 main 的消息
ipcRenderer.on('fromMain', (event, msg) => {
  console.log('收到主进程消息:', msg)
})

2.invoke/handle

render调main

//main.js
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
app.whenReady().then(() => {
  const win = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true
    }
  })
  win.loadFile('index.html')
  // 注册一个可被 renderer 调用的接口
  ipcMain.handle('getVersion', async (event) => {
    return app.getVersion()
  })
})

//render.js
async () => {
  const result = await ipcRenderer.invoke('getAppPath')
  console.log("主进程返回:", result)
  alert(result)
}

main调render

//render.js
 const { ipcRenderer } = require('electron')
  // 渲染进程注册可被调用的方法
  ipcRenderer.handle('sayHello', async (event, name) => {
    console.log("Main 调用了 Renderer:", name)
    return `Hello, ${name}, from Renderer!`
  })

  //main.js
  app.whenReady().then(() => {
  win = new BrowserWindow({
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false
    }
  })

  win.loadFile('index.html')

  // 2 秒后调用渲染进程的方法
  setTimeout(async () => {
    const result = await win.webContents.invoke('sayHello', 'Main Process')
    console.log('Renderer 返回:', result)
  }, 2000)
})

3.preload中间层

官方推荐 contextIsolation: true + preload.js 的做法。

简言之:render到main使用preload封装后暴露的方式,main到render依然在preload中封装(send/on形式)通信

双向通信demo

main.js

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

app.whenReady().then(() => {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false
    }
  })

  win.loadFile('index.html')

  // Renderer 调用 Main
  ipcMain.handle('getAppVersion', async () => {
    return app.getVersion()
  })

  // Main 主动推送给 Renderer
  setTimeout(() => {
    win.webContents.send('notifyRenderer', 'Hello Renderer!')
  }, 2000)
})

preload.js

const { contextBridge, ipcRenderer } = require('electron')

// 安全暴露 API 给 Renderer
contextBridge.exposeInMainWorld('api', {
  // Renderer 调用 Main
  getAppVersion: () => ipcRenderer.invoke('getAppVersion'),

  // Main 主动推送事件订阅
  onNotify: (callback) => ipcRenderer.on('notifyRenderer', (event, message) => {
    callback(message)
  }),

  // Renderer 发送消息给 Main
  sendMessage: (channel, message) => {
    const validChannels = ['toMain']
    if (validChannels.includes(channel)) {
      ipcRenderer.send(channel, message)
    }
  }
})

index.html

<!DOCTYPE html>
<html>
  <body>
    <h1>Electron Preload 双向通信</h1>
    <button id="versionBtn">获取 App 版本</button>
    <button id="sendBtn">发送消息给 Main</button>

    <script>
      // 调用 Main
      document.getElementById('versionBtn').addEventListener('click', async () => {
        const version = await window.api.getAppVersion()
        alert('App Version: ' + version)
      })

      // 订阅 Main 的主动推送
      window.api.onNotify((msg) => {
        console.log('收到主进程消息:', msg)
      })

      // 发送消息给 Main
      document.getElementById('sendBtn').addEventListener('click', () => {
        window.api.sendMessage('toMain', 'Hello Main!')
      })
    </script>
  </body>
</html>