Skip to content

electron-vite 笔记

https://cn-evite.netlify.app/guide/debugging.html

前后通讯

ts
// 主进程发
mainWindow.webContents.send('dm-message-to-client', JSON.stringify(msg))
// 渲染进程收
window.electron.ipcRenderer.on('dm-message-to-client',(msg)=>{
    console.log(msg)
})

// 渲染进程发
window.electron.ipcRenderer.send('openPage', window.encodeURIComponent(appUrl))
// 主进程收
ipcMain.on('openPage', (msg)=>{
    console.log(msg)
})

注意主进程给渲染进程发消息要在渲染进程准备就绪

ts
mainWindow.webContents.on('did-finish-load', () => {
    mainWindow.webContents.send('version', version)
    // 将 port 告诉渲染进程
    mainWindow.webContents.send('port', port)
  })

消息发送时最好是字符串,不然可能开发环境好好的,打包后不行了

注册快捷键

主进程入口

ts
import { app, BrowserWindow, globalShortcut, dialog } from 'electron'
globalShortcut.register('F12', () => {
    BrowserWindow.getAllWindows().forEach((item) => {
      item.webContents.openDevTools()
    })
  })
  globalShortcut.register('F5', () => {
    // 当前激活的窗口刷新
    BrowserWindow.getAllWindows().forEach((item) => {
      if (item.isFocused()) {
        item.reload()
      }
    })
  })

多窗口

https://cn-evite.netlify.app/guide/dev.html#多窗口应用程序

以上是官方的多窗口配置

个人为了简介 引入 vue-router 来实现多窗口管理 createWindow.ts

ts
import { BrowserWindow, Menu } from 'electron'
import { join } from 'path'
import { is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
export default function (options) {
  // shell.openExternal(arg)
  // 打开一个新的浏览器窗口
  const win = new BrowserWindow({
    width: options.width || 400,
    height: options.height || 800,
    autoHideMenuBar: true,
    ...(process.platform === 'linux' ? { icon } : {}),
    webPreferences: {
      preload: join(__dirname, '../preload/index.js'),
      nodeIntegration: true,
      contextIsolation: false,
      sandbox: false,
      devTools: true
    }
  })
  // win.webContents.setWindowOpenHandler((details) => {
  //   shell.openExternal(details.url)
  //   return { action: 'deny' }
  // })
  // 添加右键菜单
  win.webContents.on('context-menu', (_e, params) => {
    const { x, y } = params
    Menu.buildFromTemplate([
      {
        label: '刷新',
        click: () => {
          win.webContents.reload()
        }
      },
      {
        label: 'Inspect element',
        click: () => {
          win.webContents.inspectElement(x, y)
        }
      }
    ]).popup({ window: win })
  })
  // win.webContents.openDevTools()

  win.on('ready-to-show', () => {
    win.show()
  })
  if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
    win.loadURL(process.env['ELECTRON_RENDERER_URL'] + `/index.html#${options.hash}`)
  } else {
    win.loadFile(join(__dirname, `../renderer/index.html`), options)
  }

  return win
}

渲染进程入口main.ts

ts
import { createApp } from 'vue'
import App from './App.vue'

import Home from './app/Home.vue'
import Iframe from './Iframe.vue'
import * as VueRouter from 'vue-router'
const routes = [
  { path: '/', component: Home },
  { path: '/about', component: Iframe }
]

// 3. 创建路由实例并传递 `routes` 配置
// 你可以在这里输入更多的配置,但我们在这里
// 暂时保持简单
const router = VueRouter.createRouter({
  // 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。
  history: VueRouter.createWebHashHistory(),
  routes // `routes: routes` 的缩写
})

createApp(App).use(router).mount('#app')

App.vue 入口

vue
<script setup lang="ts">
import { useRouter } from 'vue-router'
const $router = useRouter()
const $route = $router.currentRoute.value.fullPath
console.log($route)
</script>

<template>
  <router-view></router-view>
</template>

<style lang="less">
@import './assets/css/styles.less';
</style>

创建新窗口

ts
const win = createWindow({
      hash: `about?appUrl=${arg}`
    })

这么做会导致一个问题就是后续想通过 win.webContents.send 发送消息时,是没办法区分窗口. 因为是个 spa 应用,属于同一个窗口,同一个渲染进程

获取所有窗口 发消息

ts
import { ipcMain, BrowserWindow } from 'electron'
BrowserWindow.getAllWindows().forEach((window) => {
    window.webContents.send('dm-message-to-client', msg)
})

控制打开 webview 的大小

https://developer.mozilla.org/zh-CN/docs/Web/API/Window/open

ts
window.open(url,'_blank','width=400,height=800')

打开第三方应用

安装 node 模块 open npm i open

ts
open.openApp('QQ')

启动协议

https://www.electron.build/configuration/configuration#overridable-per-platform-options

electron-builder.yml 配置

yaml
protocols:
  name: banban-barrage
  schemes:
   - banban-barrage
  role: Editor

主进程入口

ts
/// mac linux
if (process.defaultApp) {
  if (process.argv.length >= 2) {
    app.setAsDefaultProtocolClient('banban-barrage', process.execPath, [resolve(process.argv[1])])
  }
} else {
  app.setAsDefaultProtocolClient('banban-barrage')
}
app.on('open-url', (_event, _url) => {
  dialog.showErrorBox('欢迎回来', `导向自: ${_url}`)
})
/// win
const gotTheLock = app.requestSingleInstanceLock()

if (!gotTheLock) {
  app.quit()
} else {
  app.on('second-instance', (_event, commandLine: any, _workingDirectory) => {
    // 用户正在尝试运行第二个实例,我们需要让焦点指向我们的窗口
    if (mainWindow) {
      if (mainWindow.isMinimized()) mainWindow.restore()
      mainWindow.focus()
    }
    // the commandLine is array of strings in which last element is deep link url
    // the url str ends with /
    dialog.showErrorBox('Welcome Back', `You arrived from: ${commandLine.pop().slice(0, -1)}`)
  })
}

打包后使用安装程序安装.dmgsetup.exe

安装后在浏览器输入 banban-barrage://open 就可以调起应用

打包后调试

https://github.com/pd4d10/debugtron

配合 playwright

ts
const browser = await chromium.launch(
      {
        channel: 'chrome',
        headless: false,
        args: ['--disable-blink-features=AutomationControlled']
      }
    )
    const page = await browser.newPage()
    await page.on('websocket', (ws) => {
      console.log(`WebSocket opened: ${ws.url()}>`)
      ws.on('framereceived', (event) => {
        // DmByte({ msg: event.payload, platform: platform }, (msg) => {
        //   IpcMainListener.sendMessageToClient(msg)
        // })
      })
      ws.on('close', () => console.log('WebSocket closed'))
    })
    await page.goto(url, { timeout: 0 })

playwright 去掉无痕模式

ts
const browser = await chromium.launchPersistentContext(
      path.resolve(process.execPath, '../Resources/userData'),
      {
        channel: 'chrome',
        headless: false,
        args: ['--disable-blink-features=AutomationControlled']
      }
    )
ts
console.log('__dirname', __dirname)
console.log('execPath', process.execPath)
console.log('cwd', process.cwd())

node下获取当前程序的当前路径,确保路径正确打包后才能启动浏览器

设置托盘图片

文档 https://www.electronjs.org/zh/docs/latest/api/tray

示例

ts
import { app, BrowserWindow, globalShortcut, Menu, Tray } from 'electron'
import icon from '../../resources/16x16.png?asset'

// app.ready
 // ================== 托盘 ===============
  const tray = new Tray(icon)
  tray.setToolTip('互动助手')
  const contextMenu = Menu.buildFromTemplate([
    {
      label: '互动助手',
      click: () => {
        // 是否是mac
        if (!mainWindow || mainWindow.isDestroyed()) {
          mainWindow = createMainWindow({ width: 600, hash: '' })
          IpcMainListener.$listeners(mainWindow, expressWs)
        } else {
          mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show()
        }
      }
    },
    {
      label: '退出',
      click: () => {
        mainWindow.destroy()
        app.exit(0)
        app.quit()
      }
    }
  ])
  tray.setContextMenu(contextMenu)
  tray.setTitle('1')
  app.dock.setBadge('1')

  tray.on('click', () => {})

开机自启

ts
app.setLoginItemSettings({
  openAtLogin: true,
  path: process.execPath,
  args: []
})

调试技巧: 1.dev开发模式下,可以使用debugger,断点调试奇怪问题 2.在打包前,先运行preview,模式打包后的情况,有问题,容易发现 3.打包后有问题,可以使用--trace-warnings 运行看看错误 4.通常preview正常,而打包后不正常,大概率就是有些包没有打包进去,这时候就要检查依赖安装是否正确,也有可能是pnpm问题(如果使用的话) 这几步走下来,遇到的问题都会很快找到