electron+vue3项目搭建记录
1. 使用electron-vite新建项目
执行项目创建命令:
pnpm create @quick-start/electron
目创建完成后,执行以下命令:
cd electron-app
pnpm install
pnpm dev
运行效果如下:
2. 配置其他开发依赖
2.1 支持Sass/Scss
官方说明:CSS 预处理器
pnpm add -D sass
安装后,就可以直接使用以上对应的CSS预处理语言了,可以通过 <style lang="sass">
(或其他预处理器)自动开启,非常方便。
2.2 引入Element Plus
Element Plus是一款非常优秀的基于Vue3的UI库,在Vue项目开发中使用非常广泛。
安装Element Plus,执行:
pnpm add element-plus
引入Element Plus,修改src/renderer/main.ts:
//import './assets/main.css'
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
然后修改src/renderer/App.vue来验证下Element Plus:
<script setup></script>
<template>
<div class="App">
Electron-Vite-Vue-App
<el-button type="primary">Primary</el-button>
</div>
</template>
<style scoped></style>
效果:
可以看到Element Plus的el-button组件正常显示出来了。
设置Element Plus为中文语言,修改src/renderer/main.js:
//import './assets/main.css'
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus, {
locale: zhCn
})
app.mount('#app')
3. 项目目录与规范
3.1 项目目录
项目目录结构可根据项目实际灵活制定,重点是renderer目录的结构设计,主要分为公用模块目录、组件模块目录、页面模块目录、路由配置目录等几个部分,让项目结构更加清晰合理。
├─ /.vscode
├─ /build <-- build编译过程性输出目录
├─ /dist <-- 最终build的输出目录
├─ /node_modules
├─ /out <-- dev和build编译过程性输出目录
├─ /resources <-- 主进程和preload的公共资源目录
├─ /src
| ├─ /main <-- 主进程开发目录
| ├─ /preload <-- preload开发目录
| ├─ /renderer <-- 渲染进程开发目录
| | ├─ /src <-- 渲染进程源码目录
| | | ├─ /api <-- api目录
| | | | └─ index.js <-- api库
| | | ├─ /assets <-- 静态资源目录
| | | ├─ /components <-- 公共模块组件目录
| | | | ├─ /header <-- 头部导航模块(示例)
| | | | | └─ header.vue <-- header主文件
| | | | └─ ... <-- 其他模块
| | | ├─ /views <-- 页面组件目录
| | | | ├─ /home <-- home页目录
| | | | | └─ home.vue <-- home主文件
| | | | ├─ /login <-- login页目录
| | | | | └─ login.vue <-- login主文件
| | | | └─ ... <-- 其他页面
| | | ├─ /router <-- 路由配置目录
| | | | └─ index.js <-- 路由配置文件
| | | ├─ App.vue <-- 项目页面入口文件
| | | ├─ main.js <-- 渲染进程入口文件
| | └─ index.html <-- 渲染进程HTML模板
├─ .editorconfig <-- IDE配置文件
├─ .eslintignore <-- 忽略eslint检查的配置文件
├─ .eslintrc.cjs <-- eslint配置文件
├─ .gitignore
├─ .npmrc <-- npm镜像源配置文件
├─ .prettierignore <-- 忽略prettier代码格式化的配置文件
├─ .prettierrc.yaml <-- prettier代码格式化配置文件
├─ dev-app-update.yml
├─ electron-builder.yml <-- build配置文件
├─ electron.vite.config.js <-- electron-vite配置文件
├─ package.json
├─ README.md
└─ pnpm-lock.yml
3.2 代码及命名规范约定
参考:
4. 应用配置
4.1 设置应用图标
准备一张图标图片文件(icon.png),建议尺寸:512x512。最小尺寸不能低于256x256。把图标文件(icon.png)放到resources目录下。然后修改src/main/index.js:
const mainWindow = new BrowserWindow({
width: 900,
height: 670,
show: false,
autoHideMenuBar: true,
//...(process.platform === 'linux' ? { icon } : {}),
icon,
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false
}
})
说明:
- resources目录是默认的公共目录,主要用于主进程(main)和预加载脚本(preload)。
- 公共目录中的所有资源都不会复制到输出目录。所以在build的时候,公共目录应该一起打包。
- 推荐使用 ?asset 后缀导入公共资源。
更多关于electron-vite资源处理的说明请参阅:https://cn.electron-vite.org/guide/assets.html
4.2 设置build版应用icon
4.1 已经准备好了应用图标文件,只需要进行electron-builder配置即可。修改electron-builder.yml:
...(略)
win:
executableName: electron-vite-vue-app
icon: resources/icon.png
...(略)
mac:
...(略)
notarize: false+
icon: resources/icon.png
...(略) linux:
...(略)
category: Utility+
icon: resources/icon.png
nsis的安装包图标也是可以在electron-builder.yml里配置:
nsis:
artifactName: ${name}-${version}-setup.${ext}
shortcutName: ${productName}
uninstallDisplayName: ${productName}
createDesktopShortcut: always
allowToChangeInstallationDirectory: true
oneClick: false
+ installerIcon: resources/icon.ico
+ uninstallerIcon: resources/icon.ico
其他应用信息配置修改应用的appID、名称、作者等信息,同样修改electron-builder.yml文件中对应的信息即可,不再赘述。
4.3 取消跨域限制
默认情况下,Electron的渲染进程发起API请求也是有跨域问题的,解决办法就是取消跨域限制。
修改src/main/index.js,加入:
// 禁用同源策略,允许跨域请求
webSecurity: false
结果:
const mainWindow = new BrowserWindow({
width: 900,
height: 670,
show: false,
autoHideMenuBar: true,
//...(process.platform === 'linux' ? { icon } : {}),
icon,
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
// 禁用同源策略,允许跨域请求
webSecurity: false
}
})
4.4 禁止build环境的DevTools
大部分情况下,发布的软件还能打开DevTools容易造成代码泄露。可以进行以下配置,禁止build环境的DevTools。
修改src/main/index.js,加入:
// 禁止build环境使用DevTool+
devTools: is.dev ? true : false
效果:
const mainWindow = new BrowserWindow({
width: 900,
height: 670,
show: false,
autoHideMenuBar: true,
//...(process.platform === 'linux' ? { icon } : {}),
icon,
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
// 禁用同源策略,允许跨域请求
webSecurity: false,
// 禁止build环境使用DevTool
devTools: is.dev ? true : false
}
})
5.build项目
当项目开发完成,要build成最终的安装包。下面详细介绍下如何打包。
执行build命令,build的输出文件位于项目根目录下的dist目录中。
强烈建议在对应的平台环境中build相应的版本,避免出现各种很难解决的问题耽误时间。
构建windows版本:
pnpm run build:win
多平台构建官方说明:https://cn.electron-vite.org/guide/source-code-protection.html#多平台构建
build后的目录结构:
windows平台生成的dist目录:
/dist
├─ /.icon-ico <-- 图标文件目录
├─ /win-unpacked <-- 免安装的绿色版目录
├─ builder-debug.yml <-- 调试配置文件
├─ builder-effective-config.yaml <-- 生效的构建配置文件
├─ electron-vite-vue-app-1.0.0-setup.exe <-- 安装包
├─ electron-vite-vue-app-1.0.0-setup.exe.blockmap <-- 安装包的块映射文件
└─ latest.yml <-- 更新配置文件
允许windows用户更改安装目录:
electron-builder使用的nsis制作安装包。默认情况下,用户运行安装文件,就直接安装到C:\Users\你的用户名\AppData\Local\Programs\目录下了。 很多情况下,用户更希望能更改安装目录。 修改项目根目录electron-builder.yml:
nsis:
artifactName: ${name}-${version}-setup.${ext}
shortcutName: ${productName}
uninstallDisplayName: ${productName}
createDesktopShortcut: always
+ allowToChangeInstallationDirectory: true
+ oneClick: false
其他:
源代码保护
在未做任何保护措施的情况下,Electron应用很容易被解包,进而被修改代码破解商业化限制、重新打包,再重新分发破解版。从electron-vite 1.0.9版本开始,提供了源代码保护功能,采用的是目前最好的V8字节码解决方案。V8字节码是V8引擎在解析和编译JavaScript后产物的序列化形式,它通常用于浏览器内的性能优化。所以如果通过V8字节码运行代码,不仅能够起到代码保护作用,还对性能有一定的提升。 开启源代码保护很简单。修改electron.vite.config.ts:
import { resolve } from 'path'M
import { defineConfig, externalizeDepsPlugin, bytecodePlugin } from 'electron-vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
main: {
plugins: [externalizeDepsPlugin(), bytecodePlugin()]
},
preload: {
plugins: [externalizeDepsPlugin(), bytecodePlugin()]
},
...(略)
禁止同个Electron程序多开
目前build出来的项目,每次启动都会新开一个程序窗口,而之前已经运行的程序窗口并不会终止。这就容易造成程序多开。大多数情况下是不允许用户同时启动多个同样程序的。解决办法如下。
+ import { app, shell, BrowserWindow, ipcMain, dialog } from 'electron'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
+ function createWindow(): BrowserWindow {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 900,
height: 670,
show: false,
autoHideMenuBar: true,
//...(process.platform === 'linux' ? { icon } : {}),
icon,
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
// 禁用同源策略,允许跨域请求
webSecurity: false,
// 禁止build环境使用DevTool
devTools: is.dev ? true : false
}
})
mainWindow.on('ready-to-show', () => {
mainWindow.show()
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
+ return mainWindow
}
+ // 程序单例模式
+ let Window: BrowserWindow
+ const gotTheLock = app.requestSingleInstanceLock()
+ if (!gotTheLock) {
+ // 如果已经有同样的该程序正在运行,则不启动
+ app.quit()
+ } else {
+ // 如果检测到有同样的该程序正在试图启动...
+ app.on('second-instance', () => {
+ if (Window) {
+ // 弹出系统提示对话框
+ dialog.showMessageBox({
+ message: '此程序已经正在运行'
+ })
+ // 如果该程序窗口处于最小化状态,则恢复窗口
+ if (Window.isMinimized()) Window.restore()
+ // 将该程序窗口置为当前聚焦态
+ Window.focus()
+ }
+ })
+
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
// Set app user model id for windows
electronApp.setAppUserModelId('com.electron')
// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
// IPC test
ipcMain.on('ping', () => console.log('pong'))
Window = createWindow()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
+ }
// In this file you can include the rest of your app"s specific main process
// code. You can also put them in separate files and require them here.
经过以上修改,当重复启动该程序时,则会直接呼出已经在运行的该程序,并禁止重复启动(多开),而且会弹出系统对话框,提示“此程序已经正在运行”。