前面的文章讲解的知识点涵盖:electron项目创建,vue3+vite项目创建,vue+electron项目简单集成,我们也在前面明言,这种集成方案是很原始的,只能做最简单的项目,或者自己做个玩具,真正项目中并不建议。
如果您对electron+vue3已经有一定程度的研究,可以直接跳到附件章节,查看本篇文章讲解的全部代码。
前面为了入门更平滑,所以讲解的都是最主要的干货,并未涉及开发中必不可少的一些工具,这一节先把现阶段会用到的工具简单介绍下
下载:
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等文件的变动。
里面“.”“,”“–”等符号较多,建议直接复制粘贴。
配置完成后,重新运行项目:
发现软件自动重新运行,并且页面相应改变:
老生常谈,技术选型是根据项目需要以及个人喜好,如果只是想快速体验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": {}
},
这是打包的一个最基础的配置,简单讲解:
如果你成功下载了electron-builder,并按照上述参数配置了package.json,那么这时候已经可以直接打包electron项目了。
在scripts标签中完善打包命令:
"dist": "electron-builder",
"dista": "vite build && electron-builder"
我们运行这两个命令,发现不管哪个命令其实都没有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打包编译做一些配置。
首先我们要理解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的一些操作写一个命令行工具集之类的,写一个本地文档管理工具之类的……
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
}
})
{
"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"
}
}
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()
})