最近打算做一个桌面应用程序, 考虑了几种不同的方案:
.NET
: 这是以前最熟悉的, 不过.NET
的框架也有好几个版本, 因为使用的 Winddows
版本不同, 最终用户还是可能需要安装不用的 .NET Framework
, 当然, .NET
现在已经忘得差不多了。
Flutter
: Flutter
现在已经可以支持各种不同的操作系统桌面开发, 但是它的路由
不怎么好用, 没有类似 Vue
的那个子路由, 想要更新界面中的局部内容, 操作比较麻烦, 输入数据的处理也不够方便。
Electron
: 可以配合Vue
等前端框架开发应用, 方便灵活, 前两天发现自己最爱用的UI框架 Vuetify
已经发布了 3.0.1
的正式版, 正式支持 Vue3
, 所以打算用这个来试一下。另外的一好处是, 要实现 Web
版本, 代码几乎不需要做什么修改。
使用 Vuetify
模板创建 Vue3
项目 Vuetify
带有一个创建项目的模板, 使用该模板创建好的项目已经完成了 Vuetify 及相关依赖的配置, 比手动方式更加方便。创建项目命令如下:
代码执行过程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 PS D:\sources\electron_repos> yarn create vuetify yarn create v1.22.5 [1/4] Resolving packages... [2/4] Fetching packages... [3/4] Linking dependencies... [4/4] Building fresh packages... success Installed "create-vuetify@1.0.4" with binaries: - create-vuetify [############] 12/12 Vuetify.js - Material Component Framework for Vue √ Project name: ... vite-vuetify-electron-app √ Use TypeScript? ... No / Yes √ Would you like to install dependencies with yarn, npm, or pnpm? » yarn ◌ Generating scaffold... ◌ Installing dependencies with yarn... yarn install v1.22.5 info No lockfile found. [1/4] Resolving packages... [2/4] Fetching packages... [3/4] Linking dependencies... [4/4] Building fresh packages... success Saved lockfile. Done in 3.35s. vite-vuetify-electron-app has been generated at D:\sources\electron_repos\vite-vuetify-electron-app Discord community: https://community.vuetifyjs.com Github: https://github.com/vuetifyjs/vuetify Support Vuetify: https://github.com/sponsors/johnleider Done in 34.09s.
进入项目文件夹, 运行项目, 效果如以下所示:
1 2 3 4 5 6 7 8 9 10 PS D:\sources\electron_repos> cd .\vite-vuetify-electron-app\ PS D:\sources\electron_repos\vite-vuetify-electron-app> yarn dev yarn run v1.22.5 warning package.json: No license field $ vite VITE v3.2.4 ready in 382 ms ➜ Local: http://127.0.0.1:3000/ ➜ Network: use --host to expose
使用 yarn create vuetify
创建的项目不包含路由
和状态管理
模块, 需要自行添加。
配置 Electron
安装 Electron
首先安装electron
至Vuetify
项目, 命令如下:
配置文件
在 vite.config.js
中添加 path
配置
1 2 3 4 5 import path from 'path' export default defineConfig ({ base : path.resolve (__dirname, './dist/' ), })
完整代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import vue from '@vitejs/plugin-vue' import vuetify from 'vite-plugin-vuetify' import { defineConfig } from 'vite' import { fileURLToPath, URL } from 'node:url' import path from 'path' export default defineConfig ({ base : path.resolve (__dirname, './dist/' ), plugins : [ vue (), vuetify ({ autoImport : true , }), ], define : { 'process.env' : {} }, resolve : { alias : { '@' : fileURLToPath (new URL ('./src' , import .meta .url )) }, extensions : [ '.js' , '.json' , '.jsx' , '.mjs' , '.ts' , '.tsx' , '.vue' , ], }, server : { port : 3000 , }, })
在项目根目录创建名为 index.js
的新文件, 编辑内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 const { app, BrowserWindow } = require ('electron' )const path = require ('path' )function createWindow ( ) { const mainWindow = new BrowserWindow ({ width : 800 , height : 600 , webPreferences : { preload : path.join (__dirname, 'preload.js' ) } }) mainWindow.loadFile ('dist/index.html' ) } app.whenReady ().then (() => { createWindow () app.on ('activate' , function ( ) { if (BrowserWindow .getAllWindows ().length === 0 ) createWindow () }) }) app.on ('window-all-closed' , function ( ) { if (process.platform !== 'darwin' ) app.quit () })
在项目根目录创建名为 preload.js
的新文件, 编辑内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 window .addEventListener ('DOMContentLoaded' , () => { const replaceText = (selector, text ) => { const element = document .getElementById (selector) if (element) element.innerText = text } for (const dependency of ['chrome' , 'node' , 'electron' ]) { replaceText (`${dependency} -version` , process.versions [dependency]) } })
编辑 package.json
为了确保能够运行相关electron
的命令,需要修改package.json
文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 { "name" : "vite-vuetify-electron-app" , "version" : "0.0.0" , "main" : "index.js" , "scripts" : { "dev" : "vite" , "build" : "vite build" , "preview" : "vite preview" , "electron:serve" : "electron ." } , "dependencies" : { "@mdi/font" : "7.0.96" , "roboto-fontface" : "*" , "vue" : "^3.2.38" , "vuetify" : "^3.0.0" , "webfontloader" : "^1.0.0" } , "devDependencies" : { "@vitejs/plugin-vue" : "^3.0.3" , "electron" : "^21.3.0" , "vite" : "^3.1.9" , "vite-plugin-vuetify" : "^1.0.0-alpha.12" } }
package.json
文件不能包含 注释
, 上面代码的注释只是为了方便标识修改的内容, 在实际开发的时候要把注释删除
, 否则项目运行会出现异常
运行项目 运行 Electron 项目时, 要先执行 yarn build
构建项目, 生成 dist
文件夹, 才能执行 yarn electron:serve
运行项目
先构建项目 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 PS D:\sources\electron_repos\vite-vuetify-electron-app> yarn build yarn run v1.22.5 warning package.json: No license field $ vite build (!) "base" option should start with a slash. (!) "base" option should end with a slash. vite v3.2.4 building for production... ✓ 154 modules transformed. dist/assets/materialdesignicons-webfont.861aea05.eot 1214.57 KiB dist/assets/materialdesignicons-webfont.e52d60f6.woff2 376.33 KiB dist/assets/materialdesignicons-webfont.48d3eec6.woff 548.61 KiB dist/assets/materialdesignicons-webfont.bd725a7a.ttf 1214.36 KiB dist/index.html 0.58 KiB dist/assets/webfontloader.b777d690.js 12.42 KiB / gzip: 4.98 KiB dist/assets/index.5048f0d0.js 113.46 KiB / gzip: 42.44 KiB dist/assets/index.3b4c9523.css 567.04 KiB / gzip: 81.33 KiB Done in 2.30s.
运行项目 1 2 3 4 PS D:\sources\electron_repos\vite-vuetify-electron-app> yarn electron:serve yarn run v1.22.5 warning package.json: No license field $ electron .
项目运行效果如下图所示:
动态模块热重载 按上面步骤建好的项目存在一个问题, 运行中的项目不能实时响应编辑修改后的代码, 所以为了方便开发调试, 需要添加动态模块热重载功能。
编辑index.js
将mainWindow.loadFile('dist/index.html')
更新为mainWindow.loadURL("http://localhost:3000")
, 更新后的文件如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 const { app, BrowserWindow } = require ('electron' )const path = require ('path' )const NODE_ENV = process.env .NODE_ENV function createWindow ( ) { const mainWindow = new BrowserWindow ({ width : 800 , height : 600 , webPreferences : { preload : path.join (__dirname, 'preload.js' ) } }) mainWindow.loadURL ("http://localhost:3000" ) if (NODE_ENV === "development" ) { mainWindow.webContents .openDevTools () } } app.whenReady ().then (() => { createWindow () app.on ('activate' , function ( ) { if (BrowserWindow .getAllWindows ().length === 0 ) createWindow () }) }) app.on ('window-all-closed' , function ( ) { if (process.platform !== 'darwin' ) app.quit () })
编辑vite.config.js
修改文件vite.config.js
的base
,修改后的文件如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import vue from '@vitejs/plugin-vue' import vuetify from 'vite-plugin-vuetify' import { defineConfig } from 'vite' import { fileURLToPath, URL } from 'node:url' import path from 'path' export default defineConfig ({ base : "./" , plugins : [ vue (), vuetify ({ autoImport : true , }), ], define : { 'process.env' : {} }, resolve : { alias : { '@' : fileURLToPath (new URL ('./src' , import .meta .url )) }, extensions : [ '.js' , '.json' , '.jsx' , '.mjs' , '.ts' , '.tsx' , '.vue' , ], }, server : { port : 3000 , }, })
同时开启vite
和electron
服务 为了使vite
和electron
正常运行,需要先运行vite
, 使得其开发服务器的url
可以正常访问,然后再开启electron
去加载url
。
此处需要安装两个库:
concurrently
:阻塞运行多个命令, -k
参数用来清除其它已经存在或者挂掉的进程
wait-on
:等待资源,此处用来等待url可访问
首先来安装。
1 yarn add -D concurrently wait-on
接着更新文件package.json
, scripts
新增两条命令:
1 2 3 4 "scripts" : { "electron" : "wait-on tcp:3000 && electron ." , "electron:serve" : "concurrently -k \"yarn dev\" \"yarn electron\"" } ,
更新后完整内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 { "name" : "vite-vuetify-electron-app" , "version" : "0.0.0" , "main" : "index.js" , "scripts" : { "dev" : "vite" , "build" : "vite build" , "preview" : "vite preview" , "electron" : "wait-on tcp:3000 && electron ." , "electron:serve" : "concurrently -k \"yarn dev\" \"yarn electron\"" } , "dependencies" : { "@mdi/font" : "7.0.96" , "roboto-fontface" : "*" , "vue" : "^3.2.38" , "vuetify" : "^3.0.0" , "webfontloader" : "^1.0.0" } , "devDependencies" : { "@vitejs/plugin-vue" : "^3.0.3" , "electron" : "^21.3.0" , "vite" : "^3.1.9" , "vite-plugin-vuetify" : "^1.0.0-alpha.12" } }
运行 现已添加两条命令:
yarn electron
为等待tcp
协议3000
端口可访问,然后执行electron
yarn electron:serve
为阻塞执行开发服务器运行和yarn electron
命令
运行项目只要执行命令yarn electron:serve
即可,当修改项目文件时,桌面应用也将自动更新。
运行项目执行过程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 PS D:\sources\electron_repos\vite-vuetify-electron-app> yarn electron:serve yarn run v1.22.5 warning package.json: No license field $ concurrently -k "yarn dev" "yarn electron" warning package.json: No license field warning package.json: No license field $ wait-on tcp:3000 && electron . $ vite [0] [0] VITE v3.2.4 ready in 451 ms [0] [0] ➜ Local: http://127.0.0.1:3000/ [0] ➜ Network: use --host to expose
打包 之前为了方便开发过程中的调试设置的热重载
, 在打包后还是加载http://localhost:3000
是无法运行的,因此, 此处需要先用vite
打包好,然后使用electron-builder
加载vite
打包后的文件进行打包。
为了代码能够根据不同环境在运行时加载http://localhost:3000
, 在打包时加载文件, 此处需要使用环境变量
来切换生产
和开发
环境。
环境变量 此处使用环境变量NODE_ENV
来切换生产
和开发
环境,生产环境为NODE_ENV=production
, 开发环境为NODE_ENV=development
,若有其它如release
等环境可在此基础上拓展。
创建electron
文件夹 在项目根目录下创建文件夹electron
, 将index.js
和preload.js
文件移动进来。其结构如下所示:
1 2 3 4 5 6 . ├── README.md ├── electron │ ├── index.js │ └── preload.js ...
编辑electron/index.js
该文件主要是需要根据环境变量切换electron
加载的内容,修改内容如下:
1 2 3 4 5 mainWindow.loadURL ( NODE_ENV === 'development' ? 'http://localhost:3000' :`file://${path.join(__dirname, '../dist/index.html' )} ` );
编辑后的完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 const { app, BrowserWindow } = require ('electron' )const path = require ('path' )const NODE_ENV = process.env .NODE_ENV function createWindow ( ) { const mainWindow = new BrowserWindow ({ width : 800 , height : 600 , webPreferences : { preload : path.join (__dirname, 'preload.js' ) } }) mainWindow.loadURL ( NODE_ENV === 'development' ? 'http://localhost:3000' :`file://${path.join(__dirname, '../dist/index.html' )} ` ); if (NODE_ENV === "development" ) { mainWindow.webContents .openDevTools () } } app.whenReady ().then (() => { createWindow () app.on ('activate' , function ( ) { if (BrowserWindow .getAllWindows ().length === 0 ) createWindow () }) }) app.on ('window-all-closed' , function ( ) { if (process.platform !== 'darwin' ) app.quit () })
编辑package.json
首先修改main
属性,将main: index.js
改为main: electron/index.js
。
1 2 3 4 5 6 { "name" : "kuari" , "version" : "0.0.0" , "main" : "electron/index.js" , ... }
接着, 添加build
属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 "build" : { "appId" : "com.your-website.your-app" , "productName" : "ElectronApp" , "copyright" : "Copyright © 2021 <your-name>" , "mac" : { "category" : "public.app-category.utilities" }, "nsis" : { "oneClick" : false , "allowToChangeInstallationDirectory" : true }, "files" : [ "dist/**/*" , "electron/**/*" ], "directories" : { "buildResources" : "assets" , "output" : "dist_electron" } }
更新scripts
属性 先安装两个库:
cross-env
: 该库让开发者只需要注重环境变量的设置,而无需担心平台设置
electron-builder: electron
打包库
1 yarn add -D cross-env electron-builder
更新后的scripts
如下:
1 2 3 4 5 6 7 8 { "dev" : "vite" , "build" : "vite build" , "serve" : "vite preview" , "electron" : "wait-on tcp:3000 && cross-env NODE_ENV=development electron ." , "electron:serve" : "concurrently -k \"yarn dev\" \"yarn electron\"" , "electron:build" : "vite build && electron-builder" }
更新后完整的packages.json
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 { "name" : "vuetify-electron-app" , "version" : "0.0.0" , "main" : "electron/index.js" , "scripts" : { "dev" : "vite" , "build" : "vite build" , "preview" : "vite preview" , "electron" : "wait-on tcp:3000 && cross-env NODE_ENV=development electron ." , "electron:serve" : "concurrently -k \"yarn dev\" \"yarn electron\"" , "electron:build" : "vite build && electron-builder" } , "dependencies" : { "@mdi/font" : "7.0.96" , "leancloud-storage" : "^4.13.4" , "pinia" : "^2.0.24" , "roboto-fontface" : "*" , "vue" : "^3.2.38" , "vue-router" : "^4.1.6" , "vuetify" : "^3.0.1" , "webfontloader" : "^1.0.0" } , "devDependencies" : { "@vitejs/plugin-vue" : "^3.0.3" , "concurrently" : "^7.5.0" , "cross-env" : "^7.0.3" , "electron" : "^21.3.0" , "electron-builder" : "^23.6.0" , "vite" : "^3.1.9" , "vite-plugin-vuetify" : "^1.0.0-alpha.12" , "wait-on" : "^6.0.1" } , "build" : { "appId" : "hujiyi.github.io" , "productName" : "Teaching Plan" , "copyright" : "Copyright © 2022 laohoo" , "mac" : { "category" : "public.app-category.utilities" } , "nsis" : { "oneClick" : false , "allowToChangeInstallationDirectory" : true } , "files" : [ "dist/**/*" , "electron/**/*" ] , "directories" : { "buildResources" : "assets" , "output" : "dist_electron" } } }
打包 直接执行打包命令即可开始打包。
打包完成之后,会多出两个文件夹dist
和dist_electron
,其文件结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 . ├── README.md ├── dist │ ├── assets │ ├── favicon.ico │ └── index.html ├── dist_electron │ ├── MyApp-0.0.0-mac.zip │ ├── MyApp-0.0.0-mac.zip.blockmap │ ├── MyApp-0.0.0.dmg │ ├── MyApp-0.0.0.dmg.blockmap │ ├── builder-debug.yml │ ├── builder-effective-config.yaml │ └── mac ...
错误修改 在第一次运行 Electron
项目的时候, 有可能会遇到类似包含有 Electron failed to install correctly, please delete node_modules/electron and try installing again;
信息的错误提示。
这个问题是因为在install
的时候node_modules/electron/
中文件的丢失造成程序无法执行。缺少了path.txt
和dist
文件夹。
可以通过 electron-fix
来修改解决
安装electron-fix
, 执行命令: npm install electron-fix -g
进行修改, 执行命令: electron-fix start
执行 npm run fix
完成以上三个步骤之后, 再运行 Electron
项目就没有问题了。