vue3+electron开发桌面软件入门与实战(2)——创建electron+vue3框架打包集成

  • Post author:
  • Post category:vue




系列文章目录


  1. vue3+electron开发桌面软件入门与实战(0)——创建electron应用

  2. vue3+electron开发桌面软件入门与实战(1)——创建electron+vue3主体项目
  3. vue3+electron开发桌面软件入门与实战(2)——创建electron+vue3项目级集成
  4. 后续章节请查看专栏




复习

前面的文章讲解的知识点涵盖:electron项目创建,vue3+vite项目创建,vue+electron项目简单集成,我们也在前面明言,这种集成方案是很原始的,只能做最简单的项目,或者自己做个玩具,真正项目中并不建议。

如果您对electron+vue3已经有一定程度的研究,可以直接跳到附件章节,查看本篇文章讲解的全部代码。




一、完善一些必备的工具

前面为了入门更平滑,所以讲解的都是最主要的干货,并未涉及开发中必不可少的一些工具,这一节先把现阶段会用到的工具简单介绍下



1.热更新工具——nodemon


下载:

 npm i nodemon -D

nodemon一般只在开发时使用,当文件发生改变时,就自动重启进程。当然实现热更新的工具很多,nodemon能不能支持vue+electron的所有场景,还有待考验,如果大家搭建了自己的项目demo,可以替换掉它,一步步优化。

下载后检查下package.json的devDependencies属性,看一下有没有nodemon参数,有的话说明下载成功。

重新更改scripts标签的启动命令,监控文件改变,代码如下:

"start": "nodemon --exec electron . --watch ./ --ext .js,.html,.css,.vue",

这个参数的含义是:用nodemon启动electron,并监控js、html、css、vue等文件的变动。

里面“.”“,”“–”等符号较多,建议直接复制粘贴。

配置完成后,重新运行项目:

  1. yarn dev
  2. yarn start

    项目运行后,我们随便更改HelloWorld.vue的代码,然后ctrl+s保存:

在这里插入图片描述

发现软件自动重新运行,并且页面相应改变:

在这里插入图片描述



2.打包工具——electron-builder

老生常谈,技术选型是根据项目需要以及个人喜好,如果只是想快速体验electron打包或者对electron-forge打包工具情有独钟,没必要一定和我选一样的。如果使用electron-forge打包,参考官网教程就行,简单易懂,本文使用electron-builder打包。


下载:

npm i electron-builder -D

如果网络一般,这个工具大概率是会有部分插件下载失败的,尴尬的是我写这篇文章时做的示例项目一步成功了。所以没办法在这里贴上对应的资源,只能对大概率出错的问题写个解决措施:

如果报错:

⨯ Get “https://github.com/electron-userland/electron-builder-binaries/releases/download/nsis-resources-3.4.1/nsis-resources-3.4.1.7z”: read tcp 172.19.68.67:63202->20.205.243.166:443: wsarecv: An existing connection was forcibly closed by the remote host.

这插件报错信息相当良心,直接把离线下载地址给贴出来了,在终端直接点击那个链接就能下载对应的插件。下载的是个压缩包,放到自己电脑里electron-builder的安装位置,然后解压就行。我的地址如下:

C:\Users\Administrator\AppData\Local\electron-builder\Cache\nsis

在这里插入图片描述

如果类似的插件下载失败,也是这种解决思路。

linux环境下,cache地址略有不同,公司内外网环境问题,我实在不想再搞一遍linux了,大家参考windows自行研究吧。如果有时间,本系列可能在最后专门写一篇记录linux打包方案的文章,毕竟国产化躲不过。

配置electron-builder:

简单项目,不要搞那么多配置文件,就在vite提供的最外层package.json里面配置electron-builder就够用了。

先在scriptes标签里面增加命令:

 "dist": "electron-builder",

先运行vue项目:yarn dev,再打包electron:yarn dist,如果终端不报错,并且在项目的dist文件夹中出现下图,说明打包成功:

在这里插入图片描述

这一层的这个带setup的exe执行文件是安装包,发给别人双击就能把我们的软件安装到对方电脑。我们先不考虑它,打开win-unpacked文件夹,里面有一个electron-vue-basiccc.exe,这是直接运行的执行文件,双击直接运行软件。

有心的同学会发现,这样打包还是先运行的vue的开发命令,再运行的electron的打包命令,打包后的vue还是以开发模式再运行。所以我们需要更加细致的打包配置。



二、打包配置

在package.json文件中增加配置:

"build": {
    "productName": "electron-vue",
    "appId": "electron-vue",
    "asar": true,
    "directories": {
      "output": "release/${version}"
    },
    "files": [
      "dist",
      "electron"
    ],
    "nsis": {
      "oneClick": false,
      "allowToChangeInstallationDirectory": true
    },
    "mac": {
      "category": "your.app.category.type"
    },
    "win": {
      "icon": "./electron/log.ico",
      "target": [
        {
          "target": "nsis",
          "arch": [
            "ia32"
          ]
        }
      ]
    },
    "linux": {}
  },

这是打包的一个最基础的配置,简单讲解:

  • productName:项目名称,最终打出的包叫什么名,就是这里决定的
  • appId:软件的ID,现在不管做app、小程序都需要一个软件id,用来上架各平台。
  • asar:我们打包的路径“\win-unpacked\resources”文件下,有一个app文件夹,里面放着我们所有的源码,可以让每一个人看到,这显然并不太好,asar属性就是把这个app给打个包,让普通用户看不到里面的信息,类似个压缩包,但是很好破解,算是防君子不防小人吧!默认就是true,但我们研究打包方案的时候,需要把它设置成false来看看里面的文件结构。
  • directories:打包后的目标文件,默认是dist,这个建议更改。vue打包后的文件默认也是dist,两者冲突,所以把electron的打包目标文件设置为out。
  • files:electron-builder默认会打包项目里的所有文件,其实对我们有用的就两个方面,一个是vue打包编译后的文件,一个是electron的核心代码,所以我们只需要最终打包dist和electron两个文件夹。
  • nsis:软件安装包的交互行为,这个属性有点多,大家还是自行查询吧。
  • win.icon:打包后的软件图标,windows需要ico格式,当然放个png也能解析,建议按照规范配置。网上很多在线转ico格式的网站,30秒去转一个就行。这个图片大小有要求,目前是要求最小256*256,先更改大小,再转ico图片。
  • win.target.arch:建议设置ia32,适配windows大部分版本。
  • mac版本的没试,某些企业禁止苹果,你懂的。linux做过简单的尝试,在银河麒麟系统能跑通,确定这套技术栈适配国产化环境,忘了做笔记了……



三、完善参数配置:



1.scripts参数

如果你成功下载了electron-builder,并按照上述参数配置了package.json,那么这时候已经可以直接打包electron项目了。

在scripts标签中完善打包命令:

    "dist": "electron-builder",
    "dista": "vite build && electron-builder"
  • dist:打包electron项目的命令
  • dista:打包vue项目然后再打包electron项目的命令。正常情况下,powershell应该是不支持&&命令符的,不知道为什么打包的时候可以,dev运行模式就不可以。这个有大佬清楚麻烦告诉我一下。

我们运行这两个命令,发现不管哪个命令其实都没有vue页面。

为了能很清楚地定位问题,我们在main.js的createWindow方法里,写如下代码:

 let contents = win.webContents
 contents.openDevTools()

这两行代码的意思是在创建窗口的时候,打开浏览器的F12调试工具。

这时候运行dista,大概率会报错:

remove D:\cnde\electron-vue-basiccc\release\0.0.0\win-ia32-unpacked\chrome_100_percent.pak: Access is denied.

github.com/develar/go-fs-util.EnsureEmptyDir

这是因为上面我们打开的软件没有关闭,打包时重新写入文件会失败。关闭桌面上打开的electron软件,重新运行。

然后会发现body里没有任何文件,很正常,因为我们main.js引入的页面入口是vue编译部署的地址,打包时是没有这个服务地址的。

在这里插入图片描述

刚开始打包的时候,可能还会报错与github连接超时,这是网络问题,多运行几次dist打包命令,切切网络。

我们看上图,发现body标签是空的,这说明electron在加载vue的入口文件时,没有找到相应的文件。这也是正常的,如果同学按照我的系列教程看到这里,可以回想下,我们以前是在main.js中写入了一句“win.loadURL(‘http://127.0.0.1:5173/’)”来引入vue项目的。现在vue项目也是打包后嵌入到electron中,自然也就没有这样的代理服务器地址了。

所以我们需要通过路径查找,找到正确的vue打包编译后的index.html地址。

项目打包后,查找app资源下的electron和dist,分别是electron打包后的资源和vue打包后的资源,分析main.js和index.html之间的相对路径。

在这里插入图片描述


通过分析相对路径,我们需要在main.js中写如下代码:

process.env.DIST = join(__dirname, '../../')
const indexHtml = join(process.env.DIST, 'dist/index.html')

然后将win.loadURL(‘http://127.0.0.1:5173/’)修改为:

  win.loadFile(indexHtml)

main.js的整体代码如下:

const { app, BrowserWindow } = require('electron')
const {join} = require("path");
process.env.DIST = join(__dirname, '../../')
const indexHtml = join(process.env.DIST, 'dist/index.html')
const createWindow = () => {
    const win = new BrowserWindow({
        width: 800,
        height: 600
    })
    let contents = win.webContents

    contents.openDevTools()

    // win.loadURL('http://127.0.0.1:5173/')
    win.loadFile(indexHtml)

}
app.whenReady().then(() => {
    createWindow()
    app.on('activate', () => {
        if (BrowserWindow.getAllWindows().length === 0) createWindow()
    })
})
app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') app.quit()
})

然后打包,body里面应该就能渲染vue的首页index.html了。但是渲染得到的index.html还是一个空页面,这是因为我们还需要对vue打包编译做一些配置。



2.vite.config.js参数

首先我们要理解vue的打包编译,vue打包后,我们会看到vue编译的文件目录很简单,其实就是一个纯静态的网页,只有一个html文件:index.html,js和css引入到index.html中,而我们部署的时候,也是用服务器映射到这个index.html上,所以才能完成网页的部署。

vue的开发者模式中,也是在本地构建了一个服务器代理。

如果各位同学有兴趣,可以看看通过electron引入前端的资源地址,资源的路径其实是file协议的绝对地址,这是因为electron中并不存在类似Nginx、tomcat这类的服务器。这就要求通过vite.config.js的配置,让vue项目中的静态资源能够被正确识别。

vite.config.js完整参数如下:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import {resolve} from 'path'

export default defineConfig({
  plugins: [vue()],
  base:'./',
  manifest:true,  //配置后才能让编译后的vue路径被正确识别
  resolve: {
    alias: {
      '@': resolve(__dirname, './src')
    }
  },
  optimizeDeps: {
    exclude: ['electron'], // 告诉 Vite 排除预构建 electron,不然会出现 __diranme is not defined
  }
})

其中最关键的配置就是base和mainifest属性,这两个属性是vue项目能正确识别静态资源的关键。vite配置属于前端知识点了,不是咱们electron系列的讲解重点,有兴趣的同学可以自行了解下。

vue3的index.html页面默认是以相对路径加载js资源的,如下:

<script type="module" src="./src/main.js"></script>

如果我们改成

<script type="module" src="/src/main.js"></script>

以绝对路径加载资源,那么就不用特意配置vite.config.js了。这里推荐使用vue3默认生成的代码,就是使用相对路径加载js资源。




总结

按照今天的教程走一遍,应该已经能够打包一个最简单的vue3+electron项目,是真正地把vue的打包编译资源嵌入到electron打包的壳中。

本文主要讲解了electron的热更新功能实现、vue3+electron打包。

因为作者是先在公司项目中做了一大半,才回头来写从零开始的教程。所以不能以手头上现有项目作为demo来截图,只能重新创立了一个项目,这样边实现边截图边写文章,说实话,占用业余时间确实挺累,但是鉴于网络上关于vue3+electron的规范教程确实很少,所以即使知道这种文章肯定不如写个介绍小程序、css动画之类的博人眼球,还是想挤挤时间,记录下来。

如果有人因为我的文章节省了一天时间,口口相传,也是无尽的福报!

现在这个项目是一个及其简单的demo,后面我会用一篇文章,先讲解把这个demo稍微完善一些,讲一讲electron的进程通信,electron其实就没什么可着重讲的了,不论什么工具或者语言,都是单词+语法,单词=api,语法=api调用方式,这些不用特别记录,官网就是最好的文档。

今天叨叨的有点多,不过以后可能没这个机会了。如果同学们对vue3特别熟,再后面的文章就都可以不用看了,因为我在后面会重点记录怎么从零手撸vue3生态工具,router、pinia、axios这些。然后会记录一个windows系统级右键实现文件上传功能,后面再有相关文章可能就是具体的业务了,比如我想做个密码备忘录,想把git的一些操作、npm的一些操作写一个命令行工具集之类的,写一个本地文档管理工具之类的……



附件

  1. vite.config.js:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import {resolve} from 'path'


export default defineConfig({
  // server:{
  //   hmr:true
  // },
  plugins: [vue()],
  base:'./',
  manifest:true,  //配置后才能让编译后的vue路径被正确识别
  resolve: {
    alias: {
      '@': resolve(__dirname, './src')
    }
  },
  optimizeDeps: {
    exclude: ['electron'], // 告诉 Vite 排除预构建 electron,不然会出现 __diranme is not defined
  }
})

  1. package.json:
{
  "name": "electron-vue-basiccc",
  "private": true,
  "version": "0.0.0",
  "type": "commonjs",
  "main": "electron/main/main.js",
  "description": "electron+vite+vue3+electon-builder demo",
  "author": "中二少年学编程",
  "license": "MIT",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "start": "nodemon --exec electron . --watch ./ --ext .js,.html,.css,.vue",
    "dist": "electron-builder",
    "dista": "vite build && electron-builder"
  },
  "build": {
    "productName": "electron-vue",
    "appId": "electron-vue",
    "asar": false,
    "directories": {
      "output": "release/${version}"
    },
    "files": [
      "dist",
      "electron"
    ],
    "nsis": {
      "oneClick": false,
      "allowToChangeInstallationDirectory": true
    },
    "mac": {
      "category": "your.app.category.type"
    },
    "win": {
      "icon": "./electron/log.ico",
      "target": [
        {
          "target": "nsis",
          "arch": [
            "ia32"
          ]
        }
      ]
    },
    "linux": {}
  },
  "dependencies": {
    "vue": "^3.2.37"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^3.1.0",
    "electron": "^21.1.0",
    "electron-builder": "^23.6.0",
    "nodemon": "^2.0.20",
    "vite": "^3.1.0"
  }
}

  1. main.js
const { app, BrowserWindow } = require('electron')
const {join} = require("path");
process.env.DIST = join(__dirname, '../../')
const indexHtml = join(process.env.DIST, 'dist/index.html')
const createWindow = () => {
    const win = new BrowserWindow({
        width: 800,
        height: 600
    })
    let contents = win.webContents

    contents.openDevTools()

    // win.loadURL('http://127.0.0.1:5173/')
    win.loadFile(indexHtml)

}
app.whenReady().then(() => {
    createWindow()
    app.on('activate', () => {
        if (BrowserWindow.getAllWindows().length === 0) createWindow()
    })
})
app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') app.quit()
})



版权声明:本文为zjsj_lize原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。