Electron进程模型

446 阅读3分钟

一、主进程(main.ts)

主进程在 Node.js 环境中运行,在主进程中可以使用 require 模块和所有 Node.js API

main/main.ts

import path from 'path';
import { app, BrowserWindow, shell, ipcMain } from 'electron';
import MenuBuilder from './menu';

let mainWindow: BrowserWindow | null = null;

//进程间通信
ipcMain.on('app.quit', () => {
  app.quit()
});

//创建窗口
const createWindow = async () => {

  mainWindow = new BrowserWindow({
    frame: true, //有边框窗口
    width: 1280,
    height: 720,
    webPreferences: {
      //指定预加载脚本的路径
      preload: path.join(__dirname, 'preload.js'),
      webSecurity: false,
      //在渲染进程中使用node的API时,需要设为true
      // nodeIntegration: true,
      //关闭语境隔离
      // contextIsolation: false,
    },
  });

  mainWindow.loadURL('https://github.com');

  //渲染进程第一次完成绘制时,如果窗口还没有被显示,触发该事件
  mainWindow.on('ready-to-show', () => {
    if (!mainWindow) {
      throw new Error('"mainWindow" is not defined');
    }
    if (process.env.START_MINIMIZED) {
      mainWindow.minimize();
    } else {
      mainWindow.show();
      mainWindow.focus();
    }
  });

  //窗口关闭时触发
  mainWindow.on('closed', () => {
    mainWindow = null;
  });

  //自定义菜单
  const menuBuilder = new MenuBuilder(mainWindow);
  menuBuilder.buildMenu();

  //进程间通信
  mainWindow.webContents.on('new-window', (event, url) => {
    //阻止默认的跳链接行为
    event.preventDefault();
    //在用户的默认浏览器中打开url
    shell.openExternal(url);
  });
};

//所有窗口关闭时触发
app.on('window-all-closed', () => {
  //用户不在 macOS ( darwin) 上,就触发app.quit()
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

//browser window 只能在app的ready事件(初始化)被触发后创建
app
  .whenReady()
  .then(() => {
    createWindow();
    //应用被激活时触发
    app.on('activate', () => {
      //没有窗口时创建窗口
      if (mainWindow === null) createWindow();
    });
  })
  .catch(console.log);

二、预加载(preload.js)

预加载脚本包含了要在渲染进程中执行,但比网页内容先开始加载的代码。

  • 在主进程中,createWindow时,需要通过webPreferences指定preload.js的路径
  • 预加载脚本与浏览器(渲染器)共享同一个全局window对象,并且可以访问Node的API
  • 语境隔离(Context Isolation):默认开启,意味着预加载脚本与渲染器的主要运行环境是隔离开来的,以避免 预加载脚本可访问的高级权限API 被泄漏到网页内容代码中。
  • 在预加载脚本中,通过contextBridge将要使用的node API暴露给全局 window 对象。进而在渲染进程中使用这些node API。

三、渲染进程(页面.js)

默认情况下,在渲染进程中无法直接使用node的API。

如果要在渲染进程中使用node的API(比如使用fs、require):

  • 方法一:在主进程createWindow时,配置nodeIntegration: true后,渲染进程可以使用node API。 页面.js
let fs = require('fs')

let btn = document.querySelector('button')
let i = 0;
btn.onclick = function(){
  i++;
  fs.writeFile(`fileName${i}.txt`,'点击按钮后写入的内容',(err) => {
    if(err){
      console.log('err:',err)
    }else{
      console.log('写入完毕')
    }
  })
}
  • 方法二:通过preload.js

    contextIsolation默认为true的情况下:需要使用contextBridge

  1. preload.js中 通过contextBridge将要使用的node API暴露给全局 window 对象
const { contextBridge } = require('electron')

contextBridge.exposeInMainWorld('apiName', {
desktop: true
})
  1. 在渲染进程render.js中通过window对象调对应node API来使用
console.log(window.apiName) // { desktop: true }

举例如下:

main/preload.js

const { contextBridge } = require('electron');
const uploadCameraImage = require('./uploadCameraImage').uploadCameraImage;

contextBridge.exposeInMainWorld('uploadCameraImage', uploadCameraImage);

页面.js

  function formatImage(path) {
  //因为window为全局对象,此时无需导入uploadCameraImage
    let imageData = window.uploadCameraImage(path);
    if (imageData) {
      ...
    }
  }

main/uploadCameraImage.js

const fs = require('fs');
const mineType = require('mime-types');

function uploadCameraImage(newpath) {
  //let imageData = fs.readFileSync(cameraImagePath);
  let imageData = fs.readFileSync(newpath);
  let base64 = imageData.toString('base64');
  let base64format = 'data:' + mineType.lookup(newpath) + ';base64,' + base64;
  let blobdata = convertBase64UrlToBlob(base64format);
  // const blobnewurl = window.URL.createObjectURL(blobdata)
  return blobdata;
}

module.exports = {
  uploadCameraImage,
    ...
};

contextIsolation设置为flase的情况下:无需使用contextBridge

preload.js

// 上下文隔离禁用的情况下使用预加载
window.myAPI = {
    doAThing: () => {}
}

页面.js

// 在渲染器进程使用导出的 API
window.myAPI.doAThing()