写点什么

基于 Vite 搭建 Electron+Vue3 的开发环境

  • 2020-12-11
  • 本文字数:3626 字

    阅读完需:约 12 分钟

基于 Vite 搭建 Electron+Vue3 的开发环境

目前社区两大 Vue+Electron 的脚手架:electron-vue 和 vue-cli-plugin-electron-builder。都有这样那样的问题,且都还不支持 Vue3,然而 Vue3 已是大势所趋,Vite 势必也将成为官方 Vue 脚手架,下图是尤雨溪在开发好 Vite 之后与 webpack 之父的对话:



所以开发一个 Vite+Vue3+Electron 的脚手架的需求日趋强烈。


我前段时间做了一个,但是发现了一些与 Vite 有关的问题,比如:Vite 会把开发环境的 process 对象吃掉的问题。


这对于 web 项目来说问题不大,但对于我们的 Electron 项目来说,就影响很大了。


今天我就把这个思路和实现方式的关键代码发出来供大家参考,同时也希望 Vue 社区的贡献者们,能注意到这个问题(给 Vue 官方的各个项目提 issue 真的是太难了,Electron 官方项目在这方面就做的很好,很 open、很包容)。


环境


先用 Vite 创建一个 Vue3 的工程,这就是你的实际项目工程。接着安装几个 Electron 相关的依赖,最终我的工程下的依赖情况如下:


"@vue/compiler-sfc": "^3.0.0",    "vite": "^1.0.0-rc.9",    "vue": "^3.0.2",    "vue-router": "^4.0.0-rc.1",    "electron": "^11.0.2",    "electron-builder": "^22.9.1",    "electron-updater": "^4.3.5",    "postcss-scss": "^3.0.2", "sass": "^1.27.0",
复制代码


注意:这些依赖全部安装在 devDependencies 下


各个库的版本发文时应该是最新的了,不过如果有更新的版本,你完全可以用,没影响。工程的目录结构大概是如下这样:



接着在 package.json 中,增加两个命令:


"scripts": {    "start": "node ./script/dev.js",    "release": "node ./script/release.js"  },
复制代码


同时在 script 目录下创建相应的文件,接着我们就开始撰写者两个文件的代码了


调试脚本


通过 Vite 启动 Web 项目


调试脚本首先要做的工作就是启动 Vue 项目,让它跑在 http://localhost 下,这样我们修改渲染进程的代码时,会通过 Vite 的热更新机制实时反馈到界面上。


Vite 除了提供 cli 的指令启动项目外,也提供了 API,我这里就是直接调它的 API 来启动项目的,关键代码如下:


let vite = require("vite")  createServer () {    return new Promise((resolve, reject) => {      let options = {        root:process.cwd(),        enableEsbuild: true     };      this.server = vite.createServer(options);      this.server.on("error", (e) => this.serverOnErr(e));      this.server.on("data", (e) => console.log(e.toString()));      this.server.listen(this.serverPort, () => {        console.log(`http://localhost:${this.serverPort}`);        resolve();      });    });  },
复制代码


其中 this.serverPort 是绑定在当前对象上的一个变量,意义是指定 vite 项目启动时使用的端口号。启动成功后 http server 对象绑定到当前对象的 server 变量上,如果启动过程中报错,则很有可能是端口占用,将执行如下逻辑:


serverOnErr (err) {    if (err.code === "EADDRINUSE") {      console.log(        `Port ${this.viteServerPort} is in use, trying another one...`      );      setTimeout(() => {        this.server.close();        this.serverPort += 1;        this.server.listen(this.viteServerPort);      }, 100);    } else {      console.error(chalk.red(`[vite] server error:`));      console.error(err);    }  },
复制代码


这段逻辑就是递增端口号,再次尝试启动 http server。


设置环境变量


往往每个开发人员的环境变量都是不一样的,有的开发人员需要连开发服务器 A,有的开发人员需要连开发服务器 B,而且开发环境的环境变量、测试环境、生产环境的环境变量也不一样,所以我把环境变量设置到几个单独的文件中,方便区分不同的环境,也方便 gitignore,避免不同开发人员的环境变量互相冲突。


开发环境的环境变量保存在 src/script/dev.env.js 中:


let env = require("./dev.env.js")
复制代码


生产环境的环境变量则为 release.env.js。这个文件的代码非常简单,如下:


module.exports = {  APP_VERSION: require("../package.json").version,  ENV_NOW: "dev",  PROTOBUF_SERVER: "******.com",  SENTRY_SERVICE: "https://******.com/34",  ELECTRON_DISABLE_SECURITY_WARNINGS: true}
复制代码


需要注意的是:ELECTRON_DISABLE_SECURITY_WARNINGS。这个环境变量是为了屏蔽 Electron 开发者调试工具那一大堆警告的(你如果开发过 Electron 应用,你应该知道我说的是什么),APP_VERSION 是从项目的 package.json 中取的版本号,你当然可以不设置这个环境变量,通过 Electron 的 API 获取版本号:


app.getVersion() // 主进程可用
复制代码


但通过 ElectronAPI 获取到的版本号,在开发环境下,是 Electron.exe 的版本号,不是你的项目的版本号,打包编译后,这个问题是不存在的。


ENV_NOW 是当前的环境,开发环境下它的值为 dev,打包编译后的生产环境它的值应为 product,因为现在我们是讲如何构建开发环境,引用的是 dev.env.js,等下一篇文章讲如何构建编译环境时,引用的就是 release.env.js 了。


编译主进程代码


Vite 之所以快,有一个很重要的原因是它使用了 esbuild 模块来编译代码,这里我们也使用 esbuild 来编译我们的主进程的代码。前面说了主进程是放在 src/main/ 目录下的,这里我使用的是 TypeScript 开发,入口程序是 app.ts,你完全可以使用 Js 开发,文件名也随你自定义:


buildMain () {    let outfile = path.join(this.bundledDir, "entry.js");    let entryFilePath = path.join(process.cwd(), "src/main/app.ts");    // 这个方法得到的结果:{outputFiles: [ { contents: [Uint8Array], path: '<stdout>' } ]}    esbuild.buildSync({      entryPoints: [entryFilePath],      outfile,      minify: false,      bundle: true,      platform: "node",      sourcemap: false,      external: ["electron"],    });    env.WEB_PORT = this.serverPort;    let envScript = `process.env={...process.env,...${JSON.stringify(env)}};`    let js = `${envScript}${os.EOL}${fs.readFileSync(outfile)}`;    fs.writeFileSync(outfile, js)  },
复制代码


esbuild 会自动查找 app.ts 引用的其他代码,还有 treeshaking 机制保证你不会把无用的代码打包到输出目录。我把 sourcemap 关掉了,因为调试主进程很困难,基本都是手动 console.log 信息调试的,朋友们有好的建议请赐教一下。platform 要指定成 node,要不然 esbuild 会尝试帮你去找 node.js 内置的包,肯定找不到,就报错了。


同理,还要把 electron 设置成 external,在上一节设置的环境变量的基础上,我们又增加了一个 WEB_PORT 的环境变量,Electron 启动后,要根据这个变量去加载 localhost 的页面,这个变量是应用启动时确定的,是动态的,所以没办法设置到 dev.env.js 中,输出代码前,我们把环境变量的值也附加在输出代码中了。


这样 Electron 进程启动时,会先设置好环境变量,再执行具体的业务代码(我们当然也可以通过其他方式设置环境变量,但这样做主要是为了和生产环境保持一致,看到下一篇文章你就会知道了),最终生成的代码会被输出到这个目录下面:


bundledDir: path.join(process.cwd(), "release/bundled")
复制代码


稍后我们启动 Electron 时,也会让 Electron 加载这个目录下的入口程序。


启动 Electron


Electron 的 node module 并没有提供 API 给开发者调用以启动进程,所以我们只能通过 node 的 child_process 模块来启动 Electron 的进程,代码如下:


createElectronProcess () {    this.electronProcess = spawn(      require("electron").toString(),      [path.join(this.bundledDir, "entry.js")],      {        cwd: process.cwd(),        env,      }    );    this.electronProcess.on("close", () => {      this.server.close();      process.exit();    });    this.electronProcess.stdout.on("data", (data) => {      data = data.toString();      console.log(data);    });  },
复制代码


require(“electron”).toString() 得到的是 Electron 的可执行文件的路径:


  • Windows 环境下为:node_modules\electron\dist\electron.exe

  • Mac 环境下为:node_modules/electron/dist/Electron.app/Contents/MacOS/Electron


path.join(this.bundledDir, “entry.js”) 为 Electron 进程指定了入口程序文件的地址,cwd: process.cwd() 是为 Electron 指定当前工作目录(此处又为 Electron 指定了一次环境变量,其实不指定也没关系),当 Electron 进程退出时,我们也关闭了 Vite 创建的 http server。


主进程加载渲染进程页面


此处最关键的逻辑就是这一句:


if (process.env.ENV_NOW === "dev") {      await win.loadURL(`http://localhost:${process.env.WEB_PORT}/`);    }
复制代码


process.env.WEB_PORT 就是我们上文中设置的 WEB_PORT 变量。


这个逻辑当然还有 else 分支,那是下一篇博文的内容了。


2020-12-11 09:224321

评论 1 条评论

发布
用户头像
有链接地址吗?
2021-01-04 11:41
回复
没有更多了
发现更多内容

在 React 中模拟输入

yuanyxh

前端 js react context

中国信通院可信人工智能基础平台(AI Infra)第八批评估工作正式启动

中国信通院AI Infra工作组

基于LangChain手工测试用例转App自动化测试生成工具

测吧(北京)科技有限公司

测试

专访阿里云:AI 时代服务器操作系统洗牌在即,生态合作重构未来

OpenAnolis小助手

开源 AI 操作系统 龙蜥社区 龙蜥操作系统大会

豆包MarsCode IDE 搭建 VitePress 博客并使用 GitHub 部署

豆包MarsCode

人工智能 程序员 AI 开发者 计算机

高效打造跨平台桌面应用:Electron加载服务器端JS

快乐非自愿限量之名

跨平台 Electron

从源码角度,深度解读 MySQL 优化器的 GROUP BY 优化策略

华为云开发者联盟

数据库

二维码固定资产管理系统,中小微企业的合适之选

软件大师兄

低代码 无代码 无代码平台 草料二维码

【IoTDB 线上小课 07】多类写入接口,快速易懂的“说明书”!

Apache IoTDB

一文详解低代码开发如何成为学校低成本数字化转型的新引擎

快乐非自愿限量之名

低代码 数字化

Java SE 23 新增特性

EquatorCoco

Java Python

inBuilder低代码平台新特性推荐-第二十四期

inBuilder低代码平台

低代码 表单设计

一文搞定WeakHashMap

不在线第一只蜗牛

Java

支持 128TB 超大存储,GaussDB (for MySQL) 如何轻松应对海量数据挑战

华为云开发者联盟

数据库

阿里巴巴拍立淘API返回值:商家优化商品信息的参考

技术冰糖葫芦

API Gateway API 接口 API 测试 pinduoduo API

openGemini 发布人才培养计划,与顶尖的数据库专家并肩成长

华为云开发者联盟

#开源

繁星·数智思享会:以流程为中心的数字化转型战役已打响

望繁信科技

数字化转型 流程管理 流程挖掘 流程资产 流程智能

恭喜!龙蜥赛题队获得 2024 大学生操作系统能力大赛 OS 功能挑战赛道三等奖

OpenAnolis小助手

开源 操作系统 龙蜥社区

CDN加速是什么?CDN加速的原理是什么?

Ogcloud

CDN 网络加速 CDN加速 CDN技术 CDN网络加速

探索大模型和 Multi-Agent 在运维领域的实践

华为云开发者联盟

AIGC

商家运营优化:基于京东API返回值的商品管理策略

技术冰糖葫芦

API Gateway API 接口 API 测试 pinduoduo API

万界星空科技饮料行业生产管理MES系统解决方案

万界星空科技

工业互联网 mes 万界星空科技 饮料行业 食品饮料

面试官:GROUP BY和DISTINCT有什么区别?

王磊

Vue3.5中解构props,让父子组件通信更加丝滑

快乐非自愿限量之名

Vue 前端

2024新动态:低代码开发占领新常态市场

EquatorCoco

低代码

个人GPU云服务器是什么

Ogcloud

gpu 云主机 云服务器 GPU云服务器 云服务器租用

低代码开发:助力制造业数字化高质量发展

不在线第一只蜗牛

低代码

Cloud Kernel SIG 月度动态:ANCK 新增多家厂商硬件新特性支持

OpenAnolis小助手

开源 操作系统 龙蜥社区 龙蜥sig

Nexpose 6.6.270 发布下载,新增功能概览

sysin

漏洞扫描 Nexpose

解决 AI 算法开发和存储难题,华为云 DTSE 助力文华云技术架构升级

华为云开发者联盟

基于LangChain手工测试用例转App自动化测试生成工具

霍格沃兹测试开发学社

基于 Vite 搭建 Electron+Vue3 的开发环境_大前端_liulun_InfoQ精选文章