阿里云主机折上折
  • 微信号
Current Site:Index > Vue3 and Electron integration

Vue3 and Electron integration

Author:Chuan Chen 阅读数:37108人阅读 分类: Vue.js

Advantages of Vue3 and Electron Integration

Vue3's Composition API naturally aligns with Electron's main/renderer process architecture. Vue3's reactivity system performs stably in the Electron environment, especially when using the <script setup> syntax, where code organization becomes clearer. Electron's Node.js integration allows Vue components to directly call system APIs, such as file operations:

// Using Node.js modules directly in Vue components
import fs from 'fs'
import { ref } from 'vue'

const fileContent = ref('')

function readFile(path) {
  fileContent.value = fs.readFileSync(path, 'utf-8')
}

Project Initialization Configuration

Using Vite to create the project structure is faster than traditional webpack:

npm create vite@latest electron-vue-app --template vue-ts
cd electron-vue-app
npm install electron electron-builder --save-dev

Key configurations in vite.config.ts to handle Electron-specific requirements:

export default defineConfig({
  base: './', // Must use relative paths
  build: {
    outDir: 'dist',
    emptyOutDir: false // Prevents electron-builder from cleaning
  }
})

Main and Renderer Process Communication

Typical structure of the main process file electron/main.ts:

import { app, BrowserWindow, ipcMain } from 'electron'

let win: BrowserWindow | null = null

app.whenReady().then(() => {
  win = new BrowserWindow({
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false
    }
  })

  if (process.env.VITE_DEV_SERVER_URL) {
    win.loadURL(process.env.VITE_DEV_SERVER_URL)
  } else {
    win.loadFile('dist/index.html')
  }
})

ipcMain.handle('get-system-info', () => {
  return {
    platform: process.platform,
    version: process.getSystemVersion()
  }
})

Example of renderer process invocation:

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ipcRenderer } from 'electron'

const systemInfo = ref<any>(null)

onMounted(async () => {
  systemInfo.value = await ipcRenderer.invoke('get-system-info')
})
</script>

Handling Development and Production Environments

The Vite development server needs to work in tandem with the Electron main process. Add scripts to package.json:

{
  "scripts": {
    "dev": "concurrently -k \"vite\" \"wait-on http://localhost:5173 && electron .\"",
    "build": "vite build && electron-builder"
  }
}

Additional dependencies required:

npm install concurrently wait-on --save-dev

Native API Integration Example

Complete example of implementing a file selection dialog:

Add handler in the main process:

import { dialog } from 'electron'

ipcMain.handle('open-file-dialog', async () => {
  const result = await dialog.showOpenDialog({
    properties: ['openFile']
  })
  return result.filePaths[0]
})

Vue component invocation:

<template>
  <button @click="selectFile">Select File</button>
  <p>Selected: {{ selectedFile }}</p>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { ipcRenderer } from 'electron'

const selectedFile = ref('')

const selectFile = async () => {
  selectedFile.value = await ipcRenderer.invoke('open-file-dialog')
}
</script>

Packaging and Distribution Configuration

Basic electron-builder configuration:

{
  "build": {
    "appId": "com.example.electronvue",
    "win": {
      "target": "nsis",
      "icon": "build/icon.ico"
    },
    "mac": {
      "target": "dmg",
      "icon": "build/icon.icns"
    },
    "linux": {
      "target": "AppImage"
    }
  }
}

Recommended file structure:

├── build/
│   ├── icon.icns
│   ├── icon.ico
│   └── background.png
├── electron/
│   ├── main.ts
│   └── preload.ts
├── src/
│   └── /* Standard Vue project structure */
└── vite.config.ts

Security Best Practices

Example of a preload script with context isolation enabled:

electron/preload.ts:

import { contextBridge, ipcRenderer } from 'electron'

contextBridge.exposeInMainWorld('electronAPI', {
  openFileDialog: () => ipcRenderer.invoke('open-file-dialog')
})

Modify main process configuration:

new BrowserWindow({
  webPreferences: {
    preload: path.join(__dirname, 'preload.js'),
    contextIsolation: true, // Enable isolation
    nodeIntegration: false // Disable direct Node integration
  }
})

Secure invocation in Vue components:

declare global {
  interface Window {
    electronAPI: {
      openFileDialog: () => Promise<string>
    }
  }
}

const selectFile = async () => {
  selectedFile.value = await window.electronAPI.openFileDialog()
}

Debugging Techniques

Main process debugging configuration:

{
  "scripts": {
    "debug": "electron --inspect=5858 ."
  }
}

For renderer process debugging, enable DevTools when creating BrowserWindow:

win = new BrowserWindow({
  webPreferences: { devTools: true }
})
win.webContents.openDevTools()

Vite plugin integration example:

import electron from 'vite-plugin-electron'

export default defineConfig({
  plugins: [
    electron({
      entry: 'electron/main.ts',
      onstart(options) {
        options.startup()
      }
    })
  ]
})

Performance Optimization Strategies

Using Web Workers for CPU-intensive tasks:

// worker.js
self.onmessage = (e) => {
  const result = heavyCalculation(e.data)
  self.postMessage(result)
}

// In Vue component
const worker = new Worker('worker.js')
worker.postMessage(inputData)
worker.onmessage = (e) => {
  processedData.value = e.data
}

Memory management when using multiple windows in the main process:

const secondaryWindows = new Set()

function createSecondaryWindow() {
  const win = new BrowserWindow(/*...*/)
  secondaryWindows.add(win)
  win.on('closed', () => secondaryWindows.delete(win))
}

Native Menu Integration

Complete example of creating an application menu:

import { Menu } from 'electron'

const template = [
  {
    label: 'File',
    submenu: [
      {
        label: 'Open',
        click: () => win.webContents.send('menu-open-file')
      },
      { type: 'separator' },
      { role: 'quit' }
    ]
  }
]

const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)

Vue component receiving menu events:

<script setup>
import { ipcRenderer } from 'electron'

ipcRenderer.on('menu-open-file', async () => {
  // Call file selection logic
})
</script>

Auto-Update Implementation

Main process update logic:

import { autoUpdater } from 'electron-updater'

autoUpdater.autoDownload = false

autoUpdater.on('update-available', () => {
  win.webContents.send('update-available')
})

ipcMain.handle('download-update', () => {
  autoUpdater.downloadUpdate()
})

Renderer process UI interaction:

<template>
  <div v-if="updateStatus">
    {{ updateStatus }}
    <button @click="downloadUpdate">Download Update</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { ipcRenderer } from 'electron'

const updateStatus = ref('')

ipcRenderer.on('update-available', () => {
  updateStatus.value = 'New version available'
})

const downloadUpdate = () => {
  ipcRenderer.invoke('download-update')
}
</script>

Native Notification Integration

Complete implementation of system notifications:

import { Notification } from 'electron'

function showNotification(title: string, body: string) {
  new Notification({ title, body }).show()
}

ipcMain.handle('show-notification', (_, title, body) => {
  showNotification(title, body)
})

Vue component encapsulation:

<script setup lang="ts">
function showNativeNotification(title: string, options?: NotificationOptions) {
  if ('Notification' in window) {
    new Notification(title, options)
  } else {
    ipcRenderer.invoke('show-notification', title, options.body)
  }
}
</script>

Multi-Window Management

Example of a window manager implementation:

class WindowManager {
  private windows = new Map<string, BrowserWindow>()

  createWindow(id: string, options: Electron.BrowserWindowConstructorOptions) {
    if (this.windows.has(id)) {
      this.windows.get(id)?.focus()
      return
    }

    const win = new BrowserWindow(options)
    this.windows.set(id, win)

    win.on('closed', () => {
      this.windows.delete(id)
    })
  }
}

Opening new windows in Vue components:

<script setup>
import { ipcRenderer } from 'electron'

function openSettingsWindow() {
  ipcRenderer.send('create-window', 'settings', {
    width: 800,
    height: 600,
    modal: true
  })
}
</script>

Local Database Integration

Complete SQLite integration solution:

npm install better-sqlite3 --save

Main process database service:

import Database from 'better-sqlite3'

const db = new Database('app.db')

ipcMain.handle('query-db', (_, sql, params) => {
  try {
    const stmt = db.prepare(sql)
    return params ? stmt.all(params) : stmt.all()
  } catch (err) {
    console.error(err)
    return { error: err.message }
  }
})

Executing queries in Vue components:

<script setup>
import { ref } from 'vue'
import { ipcRenderer } from 'electron'

const queryResults = ref([])

async function fetchData() {
  queryResults.value = await ipcRenderer.invoke(
    'query-db',
    'SELECT * FROM users WHERE active = ?',
    [1]
  )
}
</script>

Cross-Platform Style Adaptation

Styling solution for platform differences:

<template>
  <div :class="[platform, isMaximized && 'maximized']">
    <!-- Content -->
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { ipcRenderer } from 'electron'

const platform = ref('')
const isMaximized = ref(false)

onMounted(() => {
  platform.value = await ipcRenderer.invoke('get-platform')
  
  ipcRenderer.on('window-maximized', () => {
    isMaximized.value = true
  })
  
  ipcRenderer.on('window-unmaximized', () => {
    isMaximized.value = false
  })
})
</script>

<style>
.win32 {
  font-family: 'Segoe UI', sans-serif;
}

.darwin {
  font-family: -apple-system, sans-serif;
}

.maximized {
  padding: 12px;
}
</style>

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.