Skip to content

electron+vue3项目搭建记录

1. 使用electron-vite新建项目

执行项目创建命令:

sh
pnpm create @quick-start/electron

目创建完成后,执行以下命令:

sh
  cd electron-app
  pnpm install
  pnpm dev

运行效果如下:

2. 配置其他开发依赖

2.1 支持Sass/Scss

官方说明:CSS 预处理器

sh
pnpm add -D sass

安装后,就可以直接使用以上对应的CSS预处理语言了,可以通过 <style lang="sass">(或其他预处理器)自动开启,非常方便。

2.2 引入Element Plus

Element Plus是一款非常优秀的基于Vue3的UI库,在Vue项目开发中使用非常广泛。

安装Element Plus,执行:

c
pnpm add element-plus

引入Element Plus,修改src/renderer/main.ts:

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:

c
    <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:

ts
//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目录的结构设计,主要分为公用模块目录、组件模块目录、页面模块目录、路由配置目录等几个部分,让项目结构更加清晰合理。

lua
├─ /.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:

ts
  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
    }
  })

说明:

  1. resources目录是默认的公共目录,主要用于主进程(main)和预加载脚本(preload)。
  2. 公共目录中的所有资源都不会复制到输出目录。所以在build的时候,公共目录应该一起打包。
  3. 推荐使用 ?asset 后缀导入公共资源。

更多关于electron-vite资源处理的说明请参阅:https://cn.electron-vite.org/guide/assets.html

4.2 设置build版应用icon

4.1 已经准备好了应用图标文件,只需要进行electron-builder配置即可。修改electron-builder.yml:

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里配置:

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,加入:

c
      // 禁用同源策略,允许跨域请求
      webSecurity: false

结果:

c
  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,加入:

ts
// 禁止build环境使用DevTool+           
devTools: is.dev ? true : false

效果:

ts
 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版本:

sh
pnpm run  build:win

多平台构建官方说明:https://cn.electron-vite.org/guide/source-code-protection.html#多平台构建

build后的目录结构:

windows平台生成的dist目录:

lua
/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:

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:

js
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出来的项目,每次启动都会新开一个程序窗口,而之前已经运行的程序窗口并不会终止。这就容易造成程序多开。大多数情况下是不允许用户同时启动多个同样程序的。解决办法如下。

ts
+ 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.

经过以上修改,当重复启动该程序时,则会直接呼出已经在运行的该程序,并禁止重复启动(多开),而且会弹出系统对话框,提示“此程序已经正在运行”。

参考链接

  1. https://juejin.cn/post/7287091140875403316?searchId=20250122194625F8B964320AADE4C4240B#heading-11
  2. https://github.com/Yuezi32/electron-vite-vue-app-2023autumn