再次从零开始捣鼓一个Electron应用——项目配置

上一篇介绍如何从新搭建了一个项目,本篇重点说明一下在开发过程中遇到的一些问题,以及如何解决。鲁迅曾说过,世界上没有两个相同的程序。本系列也不会介绍代码如何编写,只是将开发过程中遇到的重点的问题记录一下,避免以后出现相同的问题。

Vue Devtools 无法使用(未解决)

安装DevTools的代码在 background.ts 文件中,installExtension() 方法会尝试从应用商店下载插件。但是由于已知原因,经常无法下载,导致无法正常安装调试插件。

if (isDevelopment && !process.env.IS_TEST) {
  // Install Vue Devtools
  try {
    // await session.defaultSession.loadExtension(path.join(__dirname, '../vue-devtools'))
    await installExtension(VUEJS_DEVTOOLS)
  } catch (e) {
    console.error('Vue Devtools failed to install:', e.toString())
  }
}

尝试过将插件解压到本地进行加载的方式,也无法使用。最终只有将上述代码注释掉。

进程通信

在electron中分为 渲染进程主进程 。渲染进程负责展示界面,有着跟浏览器一样的沙盒系统,无法直接接触原生资源。主进程负责操作原生数据,启动渲染进程展示界面。这两个进程之间通常需要传输数据。

一般方法可以在渲染进程中使用require('electron').remote获取到主进程对象,从而直接操作方法同步获取数据。但是这种方法有安全问题,现在已经默认关闭不推荐使用,打开需要在background.ts文件中创建Window的时候指定参数打开:

mainWindow = new BrowserWindow({
    webPreferences: {
      nodeIntegration: true,
      enableRemoteModule: true, // 增加这个配置,打开remote对象
    },
  });

现在比较推荐的方法是使用ipc进行通信,主进程和渲染进程之间通过事件传递信息操作数据。

// 主进程中
const ipcMain = require('electron').ipcMain;
// 注册事件
ipcMain.on('async-event', function(event, payload) {
  console.log(payload);
  event.sender.send('async-response', 'world'); // 回应异步消息
});

ipcMain.on('sync-event', function(event, payload) {
  console.log(arg);
  event.returnValue = 'world'; // 回应同步消息
});
// 渲染进程
const ipcRenderer = require('electron').ipcRenderer;
console.log(ipcRenderer.sendSync('sync-event', 'hello')); // 同步消息
// 注册事件
ipcRenderer.on('async-response', function(event, payload) { // 异步回应事件
  console.log(payload);
});
ipcRenderer.send('async-event', 'hello'); // 异步消息

使用事件的好处是可以分离主进程和渲染进程之间的逻辑,而无需关注真正的代码和方法名称。

ipc通信的精简

electron一般只需要存在主进程中,但是上面的方法在使用require('electron')的时候,会将整个electron模块引入到渲染进程中,而在渲染进程中往往只需要访问 ipcRenderer 即可。

可以使用一个前置脚本 preload.tsipcRenderer 导出并挂载到window对象里。

preload.ts

import { ipcRenderer } from 'electron'

window.ipcRenderer = ipcRenderer

然后在vue.config.js文件里挂载preload脚本:

{
  // ...
  pluginOptions: {
    electronBuilder: {
      preload: resolve('src/preload.ts')
    }
  }
  // ...
}

在background.ts脚本中也要指定preload脚本:

// Create the browser window.
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {

      // Required for Spectron testing
      enableRemoteModule: true,

      // Use pluginOptions.nodeIntegration, leave this alone
      // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
      nodeIntegration: (process.env.ELECTRON_NODE_INTEGRATION as unknown) as boolean,
      preload: path.join(__dirname, 'preload.js') // 指定preload.js脚本
    }
  })

如果你使用的typescript,还需要在window对象上补充声明ipcRenderer的类型。

global-window.d.ts

import { IpcRenderer } from 'electron'

declare global {
  interface Window {
    ipcRenderer: IpcRenderer;
  }
}

无法编译本地模块

如果项目依赖的包包含了操作系统相关的本地绑定模块(如fsevents.node),那么在构建打包的时候会出现以下异常:

error  in ./node_modules/fsevents/fsevents.node

Module parse failed: Unexpected character '�' (1:0)

这是因为打包构建的时候没有找打合适的处理器来处理.node后缀的文件,但事实上这类文件本身不需要处理。就可以通过vue.config.js配置项,将这类模块进行排除。

// vue.config.js
module.exports = {
  pluginOptions: {
    electronBuilder: {
      // List native deps here if they don't work
      externals: ['chokidar', 'fs', 'hexo-fs'],
      // If you are using Yarn Workspaces, you may have multiple node_modules folders
      // List them all here so that VCP Electron Builder can find them
      nodeModulesPath: ['../../node_modules', './node_modules']
    }
  }
}

参考内容