如何开发 Vite 3 插件构建 Electron 开发环境?(文末附视频讲解)

news2024/11/20 6:21:38

开发新版本 Vue 项目推荐你使用 Vite 脚手架构建开发环境,然而 Vite 脚手架更倾向于构建纯 Web 页面,而不是桌面应用,因此开发者要做很多额外的配置和开发工作才能把 Electron 引入到 Vue 项目中,这也是很多开发者都基于开源工具来构建 Electron+Vue 的开发环境的原因。

但这样做有两个问题:第一个是这些开源工具封装了很多技术细节,导致开发者想要修改某项配置非常不方便;另一个是这些开源工具的实现方式我认为也并不是很好。

所以,我还是建议你尽量自己写代码构建 Electron+Vue 的开发环境,这样可以让自己更从容地控制整个项目。

具体应该怎么做呢?接下来我将带你按如下几个步骤构建一个 Vite+Electron 的开发环境:

创建项目

首先通过命令行创建一个 Vue 项目:

npm create vite@latest electron-jue-jin -- --template vue-ts

接着安装 Electron 开发依赖:

npm install electron -D

安装完成后,你的项目根目录下的 package.json 文件应该与下面大体类似:

{
  "name": "electron-jue-jin",
  "private": true,
  "version": "0.0.1",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc --noEmit && vite build",
    "preview": "vite preview"
  },
  "dependencies": {},
  "devDependencies": {
    "vue": "^3.2.37",
    "@vitejs/plugin-vue": "^3.0.0",
    "electron": "^19.0.8",
    "typescript": "^4.6.4",
    "vite": "^3.0.0",
    "vue-tsc": "^0.38.4"
  }
}

注意:这里我们把 vue 从 dependencies 配置节移至了 devDependencies 配置节。这是因为在 Vite 编译项目的时候,Vue 库会被编译到输出目录下,输出目录下的内容是完整的,没必要把 Vue 标记为生产依赖;而且在我们将来制作安装包的时候,还要用到这个 package.json 文件,它的生产依赖里不应该有没用的东西,所以我们在这里做了一些调整。

读者集中反馈的问题:如果你的package.json里有type:module的配置项,那么你应该把它删掉。
package.json里的type定义了这个项目所有.js文件的处理方式。 如果type的值为module,那么所有.js文件将被当做ES Modules对待(我们不能让脚手架这么做)。如果type的值为commonjs,那么所有.js文件将被当做CommonJS模块对待 如果没有设置type,那么它的默认值为commonjs。不管type字段的值是什么,.mjs文件总是会被当做ES Modules对待,.cjs文件总是会当做CommonJS对待。

到这里,我们就创建了一个基本的 Vue+TypeScript 的项目,接下来我们就为这个项目引入 Electron 模块。

创建主进程代码

创建好项目之后,我们创建主进程的入口程序:src\main\mainEntry.ts

这个入口程序的代码很简单,如下所示:

//src\main\mainEntry.ts
import { app, BrowserWindow } from "electron";

let mainWindow: BrowserWindow;

app.whenReady().then(() => {
  mainWindow = new BrowserWindow({});
  mainWindow.loadURL(process.argv[2]);
});

在这段代码里,我们在 app ready 之后创建了一个简单的 BrowserWindow 对象。app 是 Electron 的全局对象,用于控制整个应用程序的生命周期。在 Electron 初始化完成后,app 对象的 ready 事件被触发,这里我们使用 app.whenReady() 这个 Promise 方法来等待 ready 事件的发生。

mainWindow 被设置成一个全局变量,这样可以避免主窗口被 JavaScript 的垃圾回收器回收掉。另外,窗口的所有配置都使用了默认的配置。

这个窗口加载了一个 Url 路径,这个路径是以命令行参数的方式传递给应用程序的,而且是命令行的第三个参数。

app 和 BrowserWindow 都是 Electron 的内置模块,这些内置模块是通过 ES Module 的形式导入进来的,我们知道 Electron 的内置模块都是通过 CJS Module 的形式导出的,这里之所以可以用 ES Module 导入,是因为我们接下来做的主进程编译工作帮我们完成了相关的转化工作。

开发环境 Vite 插件

主进程的代码写好之后,只有编译过之后才能被 Electron 加载,我们是通过 Vite 插件的形式来完成这个编译工作和加载工作的,如下代码所示:

//plugins\devPlugin.ts
import { ViteDevServer } from "vite";
export let devPlugin = () => {
  return {
    name: "dev-plugin",
    configureServer(server: ViteDevServer) {
      require("esbuild").buildSync({
        entryPoints: ["./src/main/mainEntry.ts"],
        bundle: true,
        platform: "node",
        outfile: "./dist/mainEntry.js",
        external: ["electron"],
      });
      server.httpServer.once("listening", () => {
        let { spawn } = require("child_process");
        let addressInfo = server.httpServer.address();
        let httpAddress = `http://${addressInfo.address}:${addressInfo.port}`;
        let electronProcess = spawn(require("electron").toString(), ["./dist/mainEntry.js", httpAddress], {
          cwd: process.cwd(),
          stdio: "inherit",
        });
        electronProcess.on("close", () => {
          server.close();
          process.exit();
        });
      });
    },
  };
};

这是一个简单的 Vite 插件,在这个插件中我们注册了一个名为 configureServer 的钩子,当 Vite 为我们启动 Http 服务的时候,configureServer钩子会被执行

这个钩子的输入参数为一个类型为 ViteDevServer 的对象 server,这个对象持有一个 http.Server 类型的属性 httpServer,这个属性就代表着我们调试 Vue 页面的 http 服务,一般情况下地址为:http://127.0.0.1:5173/

我们可以通过监听 server.httpServer 的 listening 事件来判断 httpServer 是否已经成功启动。如果已经成功启动了,那么就启动 Electron 应用,并给它传递两个命令行参数,第一个参数是主进程代码编译后的文件路径,第二个参数是 Vue 页面的 http 地址,这里就是 http://127.0.0.1:5173/

为什么这里传递了两个命令行参数,而主进程的代码接收第三个参数(process.argv[2])当作 http 页面的地址呢?因为默认情况下 electron.exe 的文件路径将作为第一个参数。也就是我们通过 require("electron") 获得的字符串。

这个路径一般是:node_modules\electron\dist\electron.exe,如果这个路径下没有对应的文件,说明你的 Electron 模块没有安装好。

我们是通过 Node.js child_process 模块的 spawn 方法启动 electron 子进程的,除了两个命令行参数外,还传递了一个配置对象。

这个对象的 cwd 属性用于设置当前的工作目录,process.cwd() 返回的值就是当前项目的根目录。stdio 用于设置 electron 进程的控制台输出,这里设置 inherit 可以让 electron 子进程的控制台输出数据同步到主进程的控制台。这样我们在主进程中 console.log 的内容就可以在 VSCode 的控制台上看到了。

当 electron 子进程退出的时候,我们要关闭 Vite 的 http 服务,并且控制父进程退出,准备下一次启动。

http 服务启动之前,我们使用 esbuild 模块完成了主进程 TypeScript 代码的编译工作,这个模块是 Vite 自带的,所以我们不需要额外安装,可以直接使用。

主进程的入口文件是通过 entryPoints 配置属性设置的,编译完成后的输出文件是通过 outfile 属性配置的。

编译平台 platform 设置为 node,排除的模块 external 设置为 electron正是这两个设置使我们在主进程代码中可以通过 import 的方式导入 electron 内置的模块。非但如此,Node 的内置模块也可以通过 import 的方式引入。

这个 Vite 插件的代码编写好后,在 vite.config.ts 文件中引入一下就可以使用了,如下代码所示:

// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { devPlugin } from "./plugins/devPlugin";

export default defineConfig({
  plugins: [devPlugin(), vue()],
});

现在执行命令 npm run dev,你会看到 Electron 应用加载了 Vue 的首页,如下图所示:

关闭窗口,主进程和子进程也会跟着退出。修改一下 Vue 组件里的内容,窗口内显示的内容也会跟着变化,说明热更新机制在起作用。

渲染进程集成内置模块

现在主进程内可以自由的使用 Electron 和 Node.js 的内置模块了,但渲染进程还不行,接下去我们就为渲染进程集成这些内置模块。

首先我们修改一下主进程的代码,打开渲染进程的一些开关,允许渲染进程使用 Node.js 的内置模块,如下代码所示:

// src\main\mainEntry.ts
import { app, BrowserWindow } from "electron";
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "true";
let mainWindow: BrowserWindow;

app.whenReady().then(() => {
  let config = {
    webPreferences: {
      nodeIntegration: true,
      webSecurity: false,
      allowRunningInsecureContent: true,
      contextIsolation: false,
      webviewTag: true,
      spellcheck: false,
      disableHtmlFullscreenWindowResize: true,
    },
  };
  mainWindow = new BrowserWindow(config);
  mainWindow.webContents.openDevTools({ mode: "undocked" });
  mainWindow.loadURL(process.argv[2]);
});

在这段代码中,有以下几点需要注意:

  1. ELECTRON_DISABLE_SECURITY_WARNINGS 用于设置渲染进程开发者调试工具的警告,这里设置为 true 就不会再显示任何警告了。

如果渲染进程的代码可以访问 Node.js 的内置模块,而且渲染进程加载的页面(或脚本)是第三方开发的,那么恶意第三方就有可能使用 Node.js 的内置模块伤害最终用户。这就是为什么这里要有这些警告的原因。如果你的应用不会加载任何第三方的页面或脚本。那么就不用担心这些安全问题啦。

  1. nodeIntegration配置项的作用是把 Node.js 环境集成到渲染进程中,contextIsolation配置项的作用是在同一个 JavaScript 上下文中使用 Electron API。其他配置项与本文主旨无关,大家感兴趣的话可以自己翻阅官方文档。

  2. webContentsopenDevTools方法用于打开开发者调试工具

完成这些工作后我们就可以在开发者调试工具中访问 Node.js 和 Electron 的内置模块了。

设置 Vite 模块别名与模块解析钩子

虽然我们可以在开发者调试工具中使用 Node.js 和 Electron 的内置模块,但现在还不能在 Vue 的页面内使用这些模块。

这是因为 Vite 主动屏蔽了这些内置的模块,如果开发者强行引入它们,那么大概率会得到如下报错:

Module "xxxx" has been externalized for browser compatibility and cannot be accessed in client code.

接下去我们就介绍如何让 Vite 加载 Electron 的内置模块和 Node.js 的内置模块。

首先我们为工程安装一个 Vite 组件:vite-plugin-optimizer。

npm i vite-plugin-optimizer -D

然后修改 vite.config.ts 的代码,让 Vite 加载这个插件,如下代码所示:

// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { devPlugin, getReplacer } from "./plugins/devPlugin";
import optimizer from "vite-plugin-optimizer";

export default defineConfig({
  plugins: [optimizer(getReplacer()), devPlugin(), vue()],
});

vite-plugin-optimizer 插件会为你创建一个临时目录:node_modules.vite-plugin-optimizer

然后把类似 const fs = require('fs'); export { fs as default } 这样的代码写入这个目录下的 fs.js 文件中。

渲染进程执行到:import fs from "fs" 时,就会请求这个目录下的 fs.js 文件,这样就达到了在渲染进程中引入 Node 内置模块的目的。

getReplacer 方法是我们为 vite-plugin-optimizer 插件提供的内置模块列表。代码如下所示:

// plugins\devPlugin.ts
export let getReplacer = () => {
  let externalModels = ["os", "fs", "path", "events", "child_process", "crypto", "http", "buffer", "url", "better-sqlite3", "knex"];
  let result = {};
  for (let item of externalModels) {
    result[item] = () => ({
      find: new RegExp(`^${item}$`),
      code: `const ${item} = require('${item}');export { ${item} as default }`,
    });
  }
  result["electron"] = () => {
    let electronModules = ["clipboard", "ipcRenderer", "nativeImage", "shell", "webFrame"].join(",");
    return {
      find: new RegExp(`^electron$`),
      code: `const {${electronModules}} = require('electron');export {${electronModules}}`,
    };
  };
  return result;
};

我们在这个方法中把一些常用的 Node 模块和 electron 的内置模块提供给了 vite-plugin-optimizer 插件,以后想要增加新的内置模块只要修改这个方法即可。而且 vite-plugin-optimizer 插件不仅用于开发环境,编译 Vue 项目时,它也会参与工作 。

再次运行你的应用,看看现在渲染进程是否可以正确加载内置模块了呢?你可以通过如下代码在 Vue 组件中做这项测试:

//src\App.vue
import fs from "fs";
import { ipcRenderer } from "electron";
import { onMounted } from "vue";
onMounted(() => {
  console.log(fs.writeFileSync);
  console.log(ipcRenderer);
});

不出意外的话,开发者调试工具将会输出如下内容:

总结

现在我们迈出了万里长征的第一步,构建好了 Vue3+Vite3+Electron 的开发环境 ,而且完成这项工作并不依赖于市面上任何一个现成的构建工具,这个开发环境是我们自己动手一点一点搭起来的,以后我们想增加或者修改一项功能,都可以很从容地自己动手处理。

非但如此,我们还通过本讲内容向你介绍了 Vite 插件的开发技巧和如何创建一个简单的 Electron 应用等知识。下一讲我们将在本节课的基础上,进一步介绍如何使用 Vite 插件制作 Electron 应用的安装包。

代码

本节示例代码请通过如下地址自行下载:

源码仓储

视频:Electron + Vue 3 桌面应用开发 - 刘晓伦liulun - 掘金小册可快速上手的 Electron 与 Vue 3 实战指南。「Electron + Vue 3 桌面应用开发」由刘晓伦liulun撰写,1253人购买https://s.juejin.cn/ds/kmKu8wh/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/163099.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

用户多兴趣建模MIND

1. 概述 在工业界,一个完整的推荐系统中通常包括两个阶段,分别为召回阶段和排序阶段。在召回阶段,根据用户的兴趣从海量的商品中去检索出用户(User)可能感兴趣的候选商品( Item),满…

JavaEE进阶第二课:Spring创建与使用

上一篇我们介绍了Spring的概念,知道了Spring是众多工具方法的IoC容器。 但是纸上谈兵终觉浅,这一篇文章就来介绍Spring创建与使用, 注之后我们对对象的称呼就叫Bean 1.1Spring项目的创建与配置 1.创建maven项目,语言选java&…

【1819. 序列中不同最大公约数的数目】

来源:力扣(LeetCode) 描述: 给你一个由正整数组成的数组 nums 。 数字序列的 最大公约数 定义为序列中所有整数的共有约数中的最大整数。 例如,序列 [4,6,16] 的最大公约数是 2 。 数组的一个 子序列 本质是一个序…

Python(16):Numpy之array数组的数值计算

目录 0. 相关文章链接 1. 创建Array数组 2. 基本数值计算 2.1. numpy中的函数 2.2. 数组中的函数 3. 指定维度进行计算 3.1. numpy中的函数 3.2. 数组中的函数 4. 复杂计算 4.1. 统计乘机 4.2. 获取对应值的索引位置 4.3. 求平均值 4.4. 求标准差 4.5. 求方差 4…

【MFEN:轻量级多尺度特征提取:SR网络】

MFEN: Lightweight multi-scale feature extraction super-resolution network in embedded system (MFEN:嵌入式轻量级多尺度特征提取超分辨率网络) 深度卷积神经网络(CNN)在超分辨率(SR)方面…

基于java springboot+mybatis爱游旅行平台前台+后台设计实现

基于java springbootmybatis爱游旅行平台前台后台设计实现 博主介绍:5年java开发经验,专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获…

PCI、PCI-X、PCI-E、PCI-E Card、Mini PCI-E、M.2、Add-in Card 这些概念你搞清楚了吗

搞硬件或通信的“攻城狮”们,免不了要和各种通信协议及接口打交道。比如,我们经常接触PCI、PCI-X、PCI-E、PCI-E Card、Mini PCI-E、M.2(NGFF)、Add-in Card这些概念,作为“攻城狮”队伍中的一员,你搞清楚它们之间的关系了吗&…

Linux第一个小程序-进度条

目录 \r&&\n 行缓冲区概念 倒计时程序 进度条代码 \r&&\n 回车概念换行概念 \n[rootVM-12-17-centos lesson8]# touch test.c [rootVM-12-17-centos lesson8]# touoch Makefile bash: touoch: command not found [rootVM-12-17-centos lesson8]# touch Mak…

Python:每日一题之完全二叉树的权值

题目描述 给定一棵包含 N 个节点的完全二叉树,树上每个节点都有一个权值,按从 上到下、从左到右的顺序依次是 A1​,A2​,⋅⋅⋅AN​,如下图所示: 现在小张要把相同深度的节点的权值加在一起,他想知道哪个深度的节点 权…

【Linux操作系统】Linux进程状态和两个特殊进程

文章目录一.一套普适性的进程状态理论1.运行2.阻塞3.挂起二.一套具体的Linux进程状态1.R-运行2.S-睡眠3.T-暂停5.t-被追踪三.僵尸进程和孤儿进程1.僵尸进程2.孤儿进程一.一套普适性的进程状态理论 1.运行 由于CPU数量相对于进程数量来说少之又少,所以CPU维护了一个运行队列,方…

Synchronized底层原理系列之Synchronized的偏向锁实现原理

作者简介:专注于研究Linux内核、Hotspot虚拟机、汇编语言、JDK源码、各大中间件源码等等喜欢的话,可以三连关注~上篇文章已经对Synchronized关键字做了初步的介绍,从字节码层面介绍了Synchronized关键字,最终字节码层面就是monito…

【Linux】 iptables 入门简介

文章目录前言作用持久化和恢复执行的顺序前言 简单地说,iptables是Linux的防火墙程序。它将使用表监控进出服务器的流量。这些表包含称为链的规则集,这些规则将过滤传入和传出数据包。 作用 当数据包与规则匹配的时候,会为其指定一个目标&a…

基于幂等表思想的幂等实践

一、为什么需要幂等 分布式场景下,多个业务系统间实现强一致的协议是极其困难的。一个最简单和可实现的假设就是保证最终一致性,这要求服务端在处理一个重复的请求时需要给出相同的回应,同时不会对持久化数据产生副作用(即多次操…

【Linux】Linux下调试器gdb的使用

👑作者主页:安 度 因 🏠学习社区:StackFrame 📖专栏链接:Linux 文章目录一、前言二、铺垫三、指令集和使用1、指令集2、演示四、结语如果无聊的话,就来逛逛 我的博客栈 吧! 🌹 一、前…

通信原理与MATLAB(十三):AMI的编解码

目录1.AMI的的编解码原理1.1 AMI编码原理1.2 AMI解码原理2.AMI编解码的代码3.AMI编解码结果图4.AMI的误码率曲线4.1 原理4.2 AMI的误码率曲线代码4.3 误码率曲线图1.AMI的的编解码原理 1.1 AMI编码原理 如下图所示,AMI的编码原理:将原始码元的1转换成1,0转换成-1。…

快过年了,用Python康康哪一家足浴店可以带朋友去玩.....

人生苦短,我用Python 首先肯定是去正经足浴店, 毕竟一年出差也不少, 大家都很辛苦, 好不容易放假了, 约上好兄弟一起去放松放松~ 所需环境 python 3.8 解释器pycharm 编辑器 所需模块 requests 数据来源分析 …

Silane-PEG-NH2 氨基聚乙二醇硅烷 NH2-PEG-Silane结构式

英文名称:Silane-PEG-NH2 Silane-PEG-Amine 中文名称:硅烷-聚乙二醇-氨基 分子量:1k,2k,3.4k,5k,10k,20k。。。 存储条件:-20C,避光,避湿 用 途…

2022年度总结,迎接2023

目录 我和CSDN的2022 初次见面: 你我的成长: 博客: 比赛: 我和CSDN的2023 我和CSDN的2022 初次见面: CSDN你好啊!我跟你的初次见面在于2022年4月2日!!! 这这半年内…

【算法5.1】背包问题 - 01背包 (至多最大价值、至少最小价值)

目录 至少模板和至多模板的两大区别 1、至多模板 2、至少模板 2. 01背包 - 至多模板 - 体积至多j,总价值最大 1、朴素做法 - 二维dp 2、优化 - 一维dp 4700. 何以包邮? - 至少模板 - 价值至少j,总价值最小 至少模板和至多模板的两大区…

list容器与vector容器的区别

vector与list都是STL中非常重要的序列式容器,它们都存放在namespace std命名空间中,由于俩个容器的底层结构不同,导致其特性不同 一、底层实现结构不同 vector本质是一段动态连续的顺序表,而list底层是一个双向循环链表 二、访…