四、在Electron中使用Vue

4.1 安装与使用Vue

安装Vue是非常简单的,一条命令即可。

npm install -g @vue/cli

注意:这儿vue是全局包。也就是说vue会是全局一个命令。

创建一个vue的demo也容易,也是一个命令即可搞定。

vue create demo4

选择几个基本问题即可,通常一路回车即可。如果懒得按回车,也可以直接使用-d参数,全部采用默认值。

vue create -d demo4

这一次的文件就有点多了。不过都是Vue框架的,我们就不做解释了。到这一步,咱们已经使用vue的脚手架把一个最简单的demo创建好了。它是可以正常运行的,只需执行命令:

npm run serve

然后,在浏览器中打开对应的网址即可。默认一般是http://localhost:8080/

image-20240602222934484

4.2 添加Electron支持

与之前的electron工程不同,我们不再使用npm install electron --save-dev这个命令了。而是改用vue的插件:

vue add electron-builder

不是不能使用原来的命令,而是使用electron-builder更有性价比。正如其名electron-builder是一个electron的构建工具包。使用它可以很容易的把一个electron打包成一个安装程序或者运行程序。关于electron-builder的具体使用方法,可以参考其官网文档

如果你使用的是Linux系统,执行完上面的命令以后,需要修改src/background.js,把其中的await installExtension(VUEJS3_DEVTOOLS)这一行中的await删掉即可。估计是一个兼容性BUG,这个指令会导致程序一直等待在此处。于是,无法弹出Electron的窗口。

接下来,只需执行命令:

npm run electron:serve

就可以将vue在electron窗口中运行起来了。如下图所示:

image-20240602223022393

4.3 使用Electron

按照常规想法,如果想在vue工程中使用Electron,首先想到应该是,在App.vue文件的script中新增相关的代码。例如:

<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js App"/>

  <span id="msg"></span>!
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

let {ipcRenderer} = require("electron");
ipcRenderer.send("msg2main", "this is a message from render");
ipcRenderer.on("msg2render", (event, msg)=>{
    console.log("Received a msg in render: ", msg)
    document.querySelector("#msg").innerText += "\n"+ msg;
})

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

其中,第11~16行就是我们在demo3中写的一段代码。结果你就会发现,无法编译通过,报错如下:

image-20240603230533889

这是因为vue的编译器并不知道Electron的存在。事实上它也不需要知道。但是,我们怎么解决这个问题呢?如果你接下来尝试把这段代码放到其他地方,包括独立的js文件中,就会发现无论如何都无法正确运行。除非放到index.html里面。我们知道,在vue的index.html里面都是全局的。显然,我们不应该把代码写在这个文件中。话说回来,即使写在这儿,在vue里面还是无法使用。难道这个就无解了吗?当然不是。这就需要用到Electron的一个非常重要的特性——上下文隔离。

这是Electron提供的一个安全机制。它允许你在预加载脚本中使用node.js和Electron的功能模块,但是,无法在网页中直接使用。比如,我们前面的尝试在App.vue中试图访问node.js和electron的尝试都是不允许的。同时,它有可以开放一部分功能给到网页中使用。我们可以在预加载 脚本中定义一些函数或者变量。然后,就可以在页面上面使用它们了。所以,页面中使用的功能,一定是开发者允许页面使用的功能。而不是一股脑的把所有的能力都暴露给网页。

所以,接下来,我们定义一个名为preload.js的脚本文件。内容如下:

import { contextBridge, ipcRenderer } from "electron";

contextBridge.exposeInMainWorld("electron", {
  sendMsg: (msg) => {
    ipcRenderer.send("send_msg", msg);
  }
});

ipcRenderer.on("send_msg_ack", (event, value) => {
  let txt = document.querySelector("#text");
  console.log("value:", value, txt.innerHTML);
  txt.innerHTML += value + "\n";
})

在这个脚本中,我们做了两件事件。

  1. 给网页提供了一个名为sendMsg的函数。它的功能很简单,就是使用ipcRenderer发送一个消息给主进程。
  2. 定义了一个消息处理函数,当收到消息send_msg_ack后,把收到的数据显示到text这个文本框中。

接下来,修改App.vue的代码:

<template>
  <div class="content">
    <button @click="sendMsg">发送消息</button>
    <br>
    <textarea id="text" style="width: 99%;height: 600px;"></textarea>
  </div>
</template>

<script>

export default {
  name: 'App',
  methods: {
    sendMsg(){
      window.electron.sendMsg("this is a message");
    }
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

注意:在vue中,我们可以使用window.electron.xxx来访问预加载脚本中导出的变量或者函数。

最后,修改background.js的内容为:

'use strict'

import { app, protocol, BrowserWindow, ipcMain } from 'electron'

    ... ...

let path = require('path');

    ... ...

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

      // Use pluginOptions.nodeIntegration, leave this alone
      // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info

      nodeIntegration: false,
      contextIsolation: true,
      preload: path.join(__dirname, "preload.js")
    }
  })

  ... ...

var index = 0;
ipcMain.on("send_msg", (event, msg)=>{
  console.log("recv msg: ", msg);
  index+=1;
  event.reply("send_msg_ack", "this is a ack" + index);
})

留意,第21~23行,我们关闭了nodeIntegration,开启了contextIsolation,同时,给窗口提供了一个预加载脚本preload.js。其他的代码就比较简单了,不再做解释说明。

接下来,运行命令npm run electron:serve,就可以得到下面的画面了:

image-20240604002137719

4.4 打包

要想把写好的程序发布出去,以便用户可以下载安装。那就必须对程序进行打包。打包是个系统相关的事情。在不同的系统中,打包的格式、方式和方法完全不同。

在Windows下,主要支持nsis, nsis-web (Web installer), portable (portable app without installation), appx, msi, msi-wrapped, squirrel, 7z, zip, tar.xz, tar.lz, tar.gz, tar.bz2, dir等各种安装包格式。其中,msi是微软自家提供的安装包格式,全称Microsoft Windows Installer。nsis是一款开源的安装包格式,portable是基于nsis制作的绿色自解压的双击即可运行的程序。appx是Windows10以上版本系统应用商店支持的安装包格式,这种格式需要申请开发者账号并签名才可以。squirrel也是一种开源的安装包格式,它不仅仅是一个安装包,还包含了一组工具和库,可以很方便的在VS中制作安装包,不过这种安装包已经很久没有更新了。其他的都是一些压缩包,解压后双击运行即可。

在Linux下主要支持AppImage, flatpak, snap, deb, rpm, freebsd, pacman, p5p, apk, 7z, zip, tar.xz, tar.lz, tar.gz, tar.bz2, dir等各种安装包格式。其中,deb是Debian系发行版使用的安装包格式,rpm是Redhat系发行版所使用的安装包格式。pacman是Arch系使用的安装包格式。对于freebsdp5papk不甚了解,可能是FreeBSD、Solaris和安卓系统的安装包格式。AppImage是一种应用程序镜像文件,它使用了容器技术,把程序及其运行时需要的库打包成一个镜像,这样在用户设备上运行时就不会再出现缺少依赖库的问题了。 flatpaksnapAppImage相似,都是为了解决包依赖问题而设计的,设计细节上略有不同而已。最大的不同是二者一个是redhat设计的,另一个是ubuntu设计的。不过,二者都需要安装运行时库才能运行。相较而言AppImage更加通用,推荐使用。其余几种是压缩文件,跟Windows下一样,正常解压后即可双击运行。

在MacOS下主要支持default, dmg, mas, mas-dev, pkg, 7z, zip, tar.xz, tar.lz, tar.gz, tar.bz2, dir等各种安装包格式。default是开源项目Squirrel.Mac提供的一种开源的安装包格式。dmgpkg是比较常见的MacOS下的安装包格式。masmas-dev是苹果应用商店使用的安装包格式。其余都是压缩文件,跟Windows、Linux下一样,解压后双击运行即可。需要留意的是,MacOS下的安装文件是需要签名的,不签名无法进行分发。

默认情况下,不需要做任何改动,简单执行下面的命令即可完成打包:

npm run electron:build

打包好的文件默认会放到dist_electron目录下面。如果你仅仅是写了一个最简单的demo程序,这样打包后是可以可以正常使用的。但是,我们不可能仅仅使用最近简单的demo。通常,我们会新增一些自己的文件,比如资源文件、依赖库等等。这就需要我们设置一些打包的参数。

例如,在demo4.1中,我们有一个名为preload.js的脚本文件。Electron-builder是不知道这个文件的存在的。如果我们不调整配置文件,打包后的安装包中就不包含这个文件。

打包程序的配置文件是vue.config.js。注意,这个配置文件是Vue CLI Plugin Electron Builder这个插件提供的,详细的信息说明见官方文档。但是,其官方文档没有介绍具体的配置项。这个部分又在Electron-builder的官方当中。也就是说,配置的使用需要按照Vue CLI Plugin Electron Builder的格式,配置Electron-builder

vue.config.js的范例如下:

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  pluginOptions: {
    electronBuilder: {
      preload: ["src/preload.js"],
      builderOptions: {
        asar: false,
        extraResources:  [{
          from: "./src/logo.png",
          to: "./app"
        }],
        linux: {
          target: ["AppImage", "deb", "tar.gz"]
        },
        win: {
          target: ["portable", "zip"]
        },
        macos: {
          target: ["dmg"]
        }
      }
    },
  }
})

其中,

  • preload是用来告诉Electron-Builder预加载脚本是哪个,注意,预加载脚本可以有多个。

  • builderOptions是用来配置编译参数的。

    • asar:打包后的文件是否使用asar进行安全加固。

    • extraResources:是一些额外的资源文件或目录。

    • linuxwinmacos则是三个桌面系统专有的各种配置。

      需要注意,在不同的系统中,还有各种更加细节的配置项,细节太多了,此处不再一一展开讨论。建议仔细阅读官方的文档。