【AICon】AI 基础设施、LLM运维、大模型训练与推理,一场会议,全方位涵盖! >>> 了解详情
写点什么

基于 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:224062

评论 1 条评论

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

关关难过关关过!2022年BAT面试通关秘籍:面前规划+面试题集+简历优化+面经分享等!

收到请回复

Java 云计算 开源 架构 编程语言

高并发之缓存

源字节1号

软件开发

面试造火箭!连续轰炸50问,我却靠这些"java复习宝典"一一攻克!

收到请回复

Java 云计算 开源 架构 编程语言

华为云快成长直播ERP专场,以数据驱动企业智慧变革

科技怪咖

Java后端每日学点系列?线程知否,List懂否,垃圾回收器晓得否

知识浅谈

线程 垃圾回收器 9月月更

恍然大悟,才知道什么是真正的思维导图!

博文视点Broadview

为什么阿里人成长速度极快?看完他们 Java 架构进化笔记,值得学习

Java-fenn

Java 程序员 java面试 Java学习 Java面试题

“大厂”角力移动办公系统市场,钉钉和企微向左、WorkPlus向右

WorkPlus

想成为数据科学家,哪些技能你必须具备?

雨果

阿里云张新涛:连接产业上下游,构建XR协作生态

阿里云弹性计算

交互 XR 视觉计算 沉浸式体验

关于用户 email 邮件地址是否允许有加号的问题

Jerry Wang

typescript 正则表达式 邮件 9月月更 输入校验

如何设计企业级数据埋点采集方案?

字节跳动数据平台

数据分析 用户增长 埋点 数据应用 埋点设计

HUAWEI DevEco Testing注入攻击测试:以攻为守,守护OpenHarmony终端安全

OpenHarmony开发者

OpenHarmony

华为云快成长直播间大数据&AI专场,加速经济物联网智能化提升

科技怪咖

推荐|海泰国密通信安全解决方案 助力用户实现安全合规

电子信息发烧客

“双减”一年,如何让教育回归本质?

旺链科技

区块链 产业区块链 企业号九月金秋榜 教培行业

亿级日志队列回放性能测试初探

FunTester

从零到一构建完整知识体系,阿里最新SpringBoot原理最佳实践真香

程序员小毕

Java spring 源码 面试 SpringBoot 2

【指针内功修炼】字符指针 + 指针数组 + 数组指针 + 指针参数(一)

Albert Edison

C语言 二维数组 9月月更 指针数组 数组指针

阿里大佬力荐6篇实战文档:JVM+多线程+Kafka+Redis+Nginx+MySQL,你确定不看?

收到请回复

Java 云计算 开源 架构 编程语言

数据库的视图该怎么用?

阿柠xn

MySQL 数据库 视图 9月月更

使用 FSM 管理 osm-edge 服务网格入口流量

Flomesh

Service Mesh 服务网格

智能湖仓架构实践:利用 Amazon Redshift 的流式摄取构建实时数仓

亚马逊云科技 (Amazon Web Services)

头大了,Mysql写入数据十几秒后被自动删除了

南城FE

MySQL 前端 nodejs

变革加速,博睿数据赋能“中国智造”转型升级

博睿数据

可观测性 智能运维 博睿数据

手把手教大家编译 flowable 源码

江南一点雨

Java workflow flowable

算法基础(二)| 高精度算法详解

timerring

算法 9月月更

大数据ELK(一):集中式日志协议栈Elastic Stack简介

Lansonli

ELK 9月月更

Github点击破百万!这部《从零开始学架构》神书就此霸榜

Java-fenn

Java 程序员 java面试 Java书籍 Java面试题

预约直播 | 大规模稀疏模型演进与DeepRec

阿里云大数据AI技术

开源项目 AI技术 模型稀疏训练

漏洞修复实用指南

SEAL安全

开源 漏洞 安全漏洞 漏洞修复 开源漏洞

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