Skip to content

electron-vite 快速开始

代码存储仓库

https://gitee.com/fe521/electron-vite-start

环境安装

1. VSCode

官网:https://code.visualstudio.com/download

alt text

2. NodeJS

官网:https://nodejs.org/zh-cn/download/prebuilt-installer

点击这个下载地址进行下载:https://nodejs.org/dist/v20.16.0/node-v20.16.0-x64.msi

alt text

electron-vite 初始化项目

中文官网:https://cn.electron-vite.org/

快速开始:https://cn.electron-vite.org/guide/#搭建第一个-electron-vite-项目

1. npm create 创建项目

bash
npm create @quick-start/electron@latest

2. 安装项目依赖

bash
npm install --registry=https://registry.npmmirror.com

3. 启动项目

bash
npm run dev

4. 修改 electron-builder 配置文件

electron-builder.yml 改为 electron-builder.config.js

js
module.exports = {
  appId: "com.electron.app",
  productName: "pc-app",
  directories: {
    buildResources: "build",
  },
  files: [
    "!**/.vscode/*",
    "!src/*",
    "!electron.vite.config.{js,ts,mjs,cjs}",
    "!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}",
    "!{.env,.env.*,.npmrc,pnpm-lock.yaml}",
  ],
  asarUnpack: ["resources/**"],
  win: {
    executableName: "pc-app",
    nsis: {
      artifactName: "${name}-${version}-setup.${ext}",
      shortcutName: "${productName}",
      uninstallDisplayName: "${productName}",
      createDesktopShortcut: "always",
    },
  },
  mac: {
    entitlementsInherit: "build/entitlements.mac.plist",
    extendInfo: {
      NSCameraUsageDescription:
        "Application requests access to the device's camera.",
      NSMicrophoneUsageDescription:
        "Application requests access to the device's microphone.",
      NSDocumentsFolderUsageDescription:
        "Application requests access to the user's Documents folder.",
      NSDownloadsFolderUsageDescription:
        "Application requests access to the user's Downloads folder.",
    },
    notarize: false,
  },
  dmg: {
    artifactName: "${name}-${version}.${ext}",
  },
  linux: {
    target: ["AppImage", "snap", "deb"],
    maintainer: "electronjs.org",
    category: "Utility",
  },
  appImage: {
    artifactName: "${name}-${version}.${ext}",
  },
  npmRebuild: false,
  publish: {
    provider: "generic",
    url: "https://example.com/auto-updates",
  },
  electronDownload: {
    mirror: "https://npmmirror.com/mirrors/electron/",
  },
};

以上代码会有这个问题

powershell
  ⨯ Invalid configuration object. electron-builder 24.13.3 has been initialized using a configuration object that does not match the API schema.
 - configuration.win has an unknown property 'nsis'. These properties are valid:
   object { additionalCertificateFile?, appId?, artifactName?, asar?, asarUnpack?, certificateFile?, certificatePassword?, certificateSha1?, certificateSubjectName?, compression?, cscKeyPassword?, cscLink?, defaultArch?, detectUpdateChannel?, electronLanguages?, electronUpdaterCompatibility?, executableName?, extraFiles?, extraResources?, fileAssociations?, files?, forceCodeSigning?, generateUpdatesFilesForAllChannels?, icon?, legalTrademarks?, protocols?, publish?, publisherName?, releaseInfo?, requestedExecutionLevel?, rfc3161TimeStampServer?, sign?, signAndEditExecutable?, signDlls?, signExts?, signingHashAlgorithms?, target?, timeStampServer?, verifyUpdateCodeSignature? }
     How to fix:
     1. Open https://www.electron.build/configuration/win
     2. Search the option name on the page (or type in into Search to find across the docs).
       * Not found? The option was deprecated or not exists (check spelling).
       * Found? Check that the option in the appropriate place. e.g. "title" only in the "dmg", not in the root.
  failedTask=build stackTrace=ValidationError: Invalid configuration object. electron-builder 24.13.3 has been initialized using a configuration object that does not match the API schema.
 - configuration.win has an unknown property 'nsis'. These properties are valid:
   object { additionalCertificateFile?, appId?, artifactName?, asar?, asarUnpack?, certificateFile?, certificatePassword?, certificateSha1?, certificateSubjectName?, compression?, cscKeyPassword?, cscLink?, defaultArch?, detectUpdateChannel?, electronLanguages?, electronUpdaterCompatibility?, executableName?, extraFiles?, extraResources?, fileAssociations?, files?, forceCodeSigning?, generateUpdatesFilesForAllChannels?, icon?, legalTrademarks?, protocols?, publish?, publisherName?, releaseInfo?, requestedExecutionLevel?, rfc3161TimeStampServer?, sign?, signAndEditExecutable?, signDlls?, signExts?, signingHashAlgorithms?, target?, timeStampServer?, verifyUpdateCodeSignature? }
     How to fix:
     1. Open https://www.electron.build/configuration/win
     2. Search the option name on the page (or type in into Search to find across the docs).
       * Not found? The option was deprecated or not exists (check spelling).
       * Found? Check that the option in the appropriate place. e.g. "title" only in the "dmg", not in the root.

alt text

修复打包配置错误问题,移除多余的语言包、增加 extraResources 配置

js
const path = require("path");
const isWin32 = process.platform === "win32";

module.exports = {
  appId: "com.electron.app",
  productName: "pc-app",
  directories: {
    buildResources: "build",
  },
  files: [
    "!**/.vscode/*",
    "!src/*",
    "!electron.vite.config.{js,ts,mjs,cjs}",
    "!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}",
    "!{.env,.env.*,.npmrc,pnpm-lock.yaml}",
  ],
  artifactName: "pc-app-${os}-${arch}-${version}.${ext}",
  asarUnpack: ["resources/**"],
  extraResources: [
    {
      from: "./line",
      to: "line",
    },
  ],
  electronLanguages: isWin32
    ? ["en", "zh-TW", "zh-CN", "en-US", "en-GB"]
    : ["en", "zh_TW", "zh_CN", "en_US", "en_GB"],
  win: {
    icon: path.join(__dirname, "./build/icon.ico"),
    target: [
      {
        target: "nsis",
        arch: ["x64"],
      },
    ],
  },
  nsis: {
    oneClick: false, // 设置为 false 以提供安装类型选择界面,允许用户选择是否创建桌面图标,允许用户选择安装路径
    perMachine: true, // 设置为 true 将使安装程序默认为所有用户安装应用,这需要管理员权限
    allowToChangeInstallationDirectory: true, // 如果设置为 true,安装程序将允许用户更改安装目录
    allowElevation: true, // 一般情况下,此字段不会被直接使用,权限提升主要依赖于 perMachine 的设定。当perMachine为true,安装程序会请求管理员权限
    deleteAppDataOnUninstall: true, // 如果设置为 true,卸载程序将删除AppData中的所有程序数据
    createDesktopShortcut: true, // 如果设置为 true,安装程序将在桌面上创建程序快捷方式
    createStartMenuShortcut: true, // 如果设置为 true,安装程序将在开始菜单中创建程序快捷方式
    shortcutName: "pc-app", // 设置在桌面和开始菜单中创建的快捷方式的名称
  },
  mac: {
    entitlementsInherit: "build/entitlements.mac.plist",
    extendInfo: {
      NSCameraUsageDescription:
        "Application requests access to the device's camera.",
      NSMicrophoneUsageDescription:
        "Application requests access to the device's microphone.",
      NSDocumentsFolderUsageDescription:
        "Application requests access to the user's Documents folder.",
      NSDownloadsFolderUsageDescription:
        "Application requests access to the user's Downloads folder.",
    },
    notarize: false,
  },
  dmg: {
    artifactName: "${name}-${version}.${ext}",
  },
  linux: {
    target: ["AppImage", "snap", "deb"],
    maintainer: "electronjs.org",
    category: "Utility",
  },
  appImage: {
    artifactName: "${name}-${version}.${ext}",
  },
  npmRebuild: false,
  publish: {
    provider: "generic",
    url: "https://example.com/auto-updates",
  },
  electronDownload: {
    mirror: "https://npmmirror.com/mirrors/electron/",
  },
};

package.json 指定 electron-builder --config=electron-builder.config.js 文件打包

json
{
  "scripts": {
    "format": "prettier --write .",
    "lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
    "preview": "electron-vite preview",
    "start": "npm run dev",
    "dev": "nodemon --watch src/ --ext js,vue,html,css,scss --exec electron-vite dev",
    "build": "electron-vite build",
    "postinstall": "electron-builder install-app-deps",
    "build:unpack": "npm run build && electron-builder --config=electron-builder.config.js --dir",
    "build:win": "npm run build && electron-builder --config=electron-builder.config.js --win",
    "build:mac": "npm run build && electron-builder --config=electron-builder.config.js --mac",
    "build:linux": "npm run build && electron-builder --config=electron-builder.config.js --linux"
  }
}

INFO

指定配置文件 https://www.electron.build/cli

在使用 electron-builder 时,你可以通过指定配置文件的方式来使用不同的配置。默认情况下,electron-builder 会寻找项目根目录下的 electron-builder.yml, electron-builder.json, 或 electron-builder.config.js 文件作为配置文件。

如果你希望显式指定配置文件,你可以在命令行中使用 --config 选项。例如,假设你将配置文件保存为 build-config.js,你可以在命令行中这样指定:

bash
electron-builder --config build-config.js

这种方式允许你在构建项目时灵活选择和使用不同的配置文件。你可以将配置文件以 YAML、JSON 或 JavaScript 的格式保存,只需确保通过 --config 选项指定正确的路径和文件名即可。

5. .prettierrc.yaml 改成 .prettierrc.js

.prettierrc.js 内容如下

js
module.exports = {
  // 使用单引号代替双引号
  singleQuote: true,
  // 在每行代码末尾添加分号
  semi: true,
  // 设置代码的最大打印宽度为100个字符
  printWidth: 100,
  // 在多行对象或数组的最后一项后面添加逗号
  trailingComma: "all",
  // 在对象大括号内不添加空格
  bracketSpacing: false,
};

6. .eslintrc.cjs 修改成如下:

要让 ESLint 支持 Prettier 的配置,你需要将 Prettier 的规则集集成到 ESLint 中。以下是更新后的 ESLint 配置,包含对 Prettier 配置的支持:

javascript
/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution");

module.exports = {
  extends: [
    "eslint:recommended",
    "plugin:vue/vue3-recommended",
    "@electron-toolkit",
    "@vue/eslint-config-prettier",
    "prettier", // 确保 ESLint 支持并遵循 Prettier 的规则
  ],
  rules: {
    "vue/require-default-prop": "off",
    "vue/multi-word-component-names": "off",
  },
  // ESLint 配置中添加对 Prettier 的自定义规则
  prettier: {
    singleQuote: true, // 使用单引号代替双引号
    semi: true, // 在每行代码末尾添加分号
    printWidth: 100, // 设置代码的最大打印宽度为100个字符
    trailingComma: "all", // 在多行对象或数组的最后一项后面添加逗号
    // 在对象大括号内不添加空格
    bracketSpacing: false,
  },
};

主要更改包括:

  • extends 中添加 'prettier' 以确保 ESLint 配置支持并遵循 Prettier 的规则。
  • prettier 配置中添加自定义规则以确保代码风格一致。

这个配置文件确保 ESLint 和 Prettier 的规则不会冲突,并且可以保持代码风格的一致性。

7. 格式化代码

powershell
npm run format
powershell
PS C:\Users\Administrator\Desktop\pc-app> npm run format

> pc-app@1.0.0 format
> prettier --write .

.eslintrc.cjs 30ms (unchanged)
.prettierrc.js 1ms (unchanged)
.vscode/extensions.json 1ms (unchanged)
.vscode/launch.json 3ms (unchanged)
.vscode/settings.json 1ms (unchanged)
electron-builder.config.js 3ms (unchanged)
electron.vite.config.mjs 3ms (unchanged)
package-lock.json 53ms (unchanged)
package.json 1ms (unchanged)
README.md 16ms (unchanged)
src/main/index.js 10ms (unchanged)
src/preload/index.js 2ms (unchanged)
src/renderer/index.html 14ms (unchanged)
src/renderer/src/App.vue 7ms (unchanged)
src/renderer/src/assets/base.css 18ms (unchanged)
src/renderer/src/assets/main.css 8ms (unchanged)
src/renderer/src/components/Versions.vue 4ms (unchanged)
src/renderer/src/main.js 1ms (unchanged)
PS C:\Users\Administrator\Desktop\pc-app>

8. 打包 Windows 安装包

powershell
npm run build:win
powershell
PS C:\Users\Administrator\Desktop\pc-app> npm run build:win

> pc-app@1.0.0 build:win
> npm run build && electron-builder --win


> pc-app@1.0.0 build
> electron-vite build

vite v5.4.1 building SSR bundle for production...
2 modules transformed.
out/main/index.js  1.48 kB
✓ built in 42ms
vite v5.4.1 building SSR bundle for production...
1 modules transformed.
out/preload/index.js  0.42 kB
✓ built in 5ms
vite v5.4.1 building for production...
11 modules transformed.
../../out/renderer/index.html                      0.55 kB
../../out/renderer/assets/electron-DtwWEc_u.svg    5.82 kB
../../out/renderer/assets/index-D5G5Cj71.css       6.55 kB
../../out/renderer/assets/index-Be6tFnq3.js      162.31 kB
✓ built in 288ms
  • electron-builder  version=24.13.3 os=10.0.22631
  • writing effective config  file=dist\builder-effective-config.yaml
  • packaging       platform=win32 arch=x64 electron=31.4.0 appOutDir=dist\win-unpacked
  • downloading     url=https://npmmirror.com/mirrors/electron/31.4.0/electron-v31.4.0-win32-x64.zip size=111 MB parts=8
  • downloaded      url=https://npmmirror.com/mirrors/electron/31.4.0/electron-v31.4.0-win32-x64.zip duration=9.824s
  • building        target=nsis file=dist\pc-app Setup 1.0.0.exe archs=x64 oneClick=true perMachine=false
  • building block map  blockMapFile=dist\pc-app Setup 1.0.0.exe.blockmap

electron-vite 自动热更新

主进程目前是无法热更新的,渲染进程如果是 Vue 内部的代码更新了则可以热更新。

所以,如果需要主进程修改后自动重新关闭、打开 app,可以使用此方案。

electron-vite vue 无法自动热更新解决方案

1. 安装 nodemon

bash
npm install nodemon -D --registry=https://registry.npmmirror.com

2. 添加 nodemon 监听的命令

package.json

json
{
  "scripts": {
    "format": "prettier --write .",
    "lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
    "start": "electron-vite preview",
    "dev": "nodemon --watch src/ --ext js,vue,html,css,scss --exec electron-vite dev",
    "build": "electron-vite build",
    "postinstall": "electron-builder install-app-deps",
    "build:unpack": "npm run build && electron-builder --config --dir",
    "build:win": "npm run build && electron-builder --win",
    "build:mac": "npm run build && electron-builder --mac",
    "build:linux": "npm run build && electron-builder --linux"
  }
}

3. 重新运行

bash
npm run dev

electron-vite 多窗口

实现效果如下:

alt text

1. 修改 electron.vite.config.mjs

electron.vite.config.mjs 文件修改点如下

js
import { resolve } from "path";
import { defineConfig, externalizeDepsPlugin } from "electron-vite";
import vue from "@vitejs/plugin-vue";

export default defineConfig({
  main: {
    plugins: [externalizeDepsPlugin()],
  },
  preload: {
    build: {
      rollupOptions: {
        input: {
          index: resolve(__dirname, "src/preload/index.js"),
          about: resolve(__dirname, "src/preload/about.js"),
        },
      },
    },
    plugins: [externalizeDepsPlugin()],
  },
  renderer: {
    build: {
      rollupOptions: {
        input: {
          index: resolve(__dirname, "src/renderer/index.html"),
          about: resolve(__dirname, "src/renderer/about.html"),
        },
      },
    },
    resolve: {
      alias: {
        "@renderer": resolve("src/renderer/src"),
      },
    },
    plugins: [vue()],
  },
});

2. src/renderer/修改如下

1、增加 about.html 文件

src/renderer/about.html
src/renderer/about/main.js
src/renderer/about/App.vue
html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>About</title>
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
    />
  </head>

  <body>
    <div id="app"></div>
    <script type="module" src="./about/main.js"></script>
  </body>
</html>

2、修改 src/renderer/index.html 文件

src/renderer/src/ 重命名为 src/renderer/index/

src/renderer/index.html
src/renderer/index/main.js
src/renderer/index/App.vue
html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Electron</title>
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
    />
  </head>

  <body>
    <div id="app"></div>
    <script type="module" src="./index/main.js"></script>
  </body>
</html>

3. 增加 src/preload/about.js

js
import { contextBridge } from "electron";
import { electronAPI } from "@electron-toolkit/preload";

// Custom APIs for renderer
const api = {};

// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
  try {
    contextBridge.exposeInMainWorld("electron", electronAPI);
    contextBridge.exposeInMainWorld("api", api);
  } catch (error) {
    console.error(error);
  }
} else {
  window.electron = electronAPI;
  window.api = api;
}

4. src/main/index.js 增加 createAboutWindow 函数

Details
js
import { app, shell, BrowserWindow, ipcMain } from "electron";
import { join } from "path";
import { electronApp, optimizer, is } from "@electron-toolkit/utils";
import icon from "../../resources/icon.png?asset";

function createWindow() {
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width: 900,
    height: 670,
    show: false,
    autoHideMenuBar: true,
    ...(process.platform === "linux" ? { icon } : {}),
    webPreferences: {
      preload: join(__dirname, "../preload/index.js"),
      sandbox: 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"]) {
    console.log(
      "process.env['ELECTRON_RENDERER_URL']",
      process.env["ELECTRON_RENDERER_URL"] + "/index.html"
    );
    mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]);
  } else {
    mainWindow.loadFile(join(__dirname, "../renderer/index.html"));
  }
}
function createAboutWindow() {
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width: 312,
    height: 422,
    show: false,
    autoHideMenuBar: true,
    ...(process.platform === "linux" ? { icon } : {}),
    webPreferences: {
      preload: join(__dirname, "../preload/about.js"),
      sandbox: 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"]) {
    console.log(
      "process.env['ELECTRON_RENDERER_URL']",
      process.env["ELECTRON_RENDERER_URL"] + "/about.html"
    );
    mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"] + "/about.html");
  } else {
    mainWindow.loadFile(join(__dirname, "../renderer/about.html"));
  }
}

// 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"));

  createWindow();

  createAboutWindow();

  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.

electron-vite 自定义 titlebar

1. 修改窗口为无边框

src/index/main.js

js
function createWindow() {
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width: 900,
    height: 670,
    show: false,
    frame: false,
    autoHideMenuBar: true,
    ...(process.platform === "linux" ? { icon } : {}),
    webPreferences: {
      preload: join(__dirname, "../preload/index.js"),
      sandbox: false,
    },
  });
  // ...
}

2. 创建 titlebar 处理

提示:

特别注意这里:https://www.electronjs.org/zh/docs/latest/tutorial/window-customization#window-controls-overlay

VSCode 的最小化、缩放、最大化按钮是通过 titleBarStyletitleBarOverlay 设置的

js
const { BrowserWindow } = require("electron");
const win = new BrowserWindow({
  titleBarStyle: "hidden",
  titleBarOverlay: {
    color: "#2f3241",
    symbolColor: "#74b1be",
    height: 60,
  },
});

titleBarOverlay 被设置后,只显示 最小化, 最大化/还原,关闭窗口的按钮

(1)、src/main/titlebar.js

Details
js
import { app, ipcMain, BrowserWindow } from "electron";
app.whenReady().then(() => {
  ipcMain.on("minimize", (event) => {
    const window = BrowserWindow.fromWebContents(event.sender);
    window.minimize();
  });

  ipcMain.on("maximize", (event) => {
    const window = BrowserWindow.fromWebContents(event.sender);
    if (window.isMaximized()) {
      window.unmaximize();
    } else {
      window.maximize();
    }
  });

  ipcMain.on("close", (event) => {
    const window = BrowserWindow.fromWebContents(event.sender);
    window.close();

    // 检查如果所有窗口都关闭了,退出应用
    //   if (BrowserWindow.getAllWindows().length === 0) {
    //     app.quit();
    //   }
  });
});

(2)、src/main/index.js 引入

js
import { app, shell, BrowserWindow, ipcMain } from "electron";
import { join } from "path";
import { electronApp, optimizer, is } from "@electron-toolkit/utils";
import icon from "../../resources/icon.png?asset";
import "./titlebar";

3. src/preload/index.js

js
import { ipcRenderer, contextBridge } from "electron";
import { electronAPI } from "@electron-toolkit/preload";

// Custom APIs for renderer
const api = {
  minimize: () => ipcRenderer.send("minimize"),
  maximize: () => ipcRenderer.send("maximize"),
  close: () => ipcRenderer.send("close"),
};

// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
  try {
    contextBridge.exposeInMainWorld("electron", electronAPI);
    contextBridge.exposeInMainWorld("api", api);
  } catch (error) {
    console.error(error);
  }
} else {
  window.electron = electronAPI;
  window.api = api;
}

4.编写 src/renderer/index/components/Titlebar.vue 组件

Details
vue
<template>
  <div class="titlebar">
    <div class="title"></div>
    <div class="controls">
      <button @click="minimize">-</button>
      <button @click="maximize">□</button>
      <button @click="close" class="close-btn">×</button>
    </div>
  </div>
</template>

<script setup>
function minimize() {
  window.api.minimize();
}

function maximize() {
  window.api.maximize();
}

function close() {
  window.api.close();
}
</script>

<style scoped>
.titlebar {
  -webkit-app-region: drag;
  display: flex;
  justify-content: space-between;
  align-items: center;
  background-color: #eff4f9;
  height: 30px;
}

.title {
  font-size: 14px;
  font-weight: bold;
}

.controls {
  -webkit-app-region: no-drag;
  display: flex;
}

.controls button {
  width: 50px;
  box-sizing: border-box;
  height: 30px;
  border: none;
  background: transparent;
  font-size: 20px;
  cursor: pointer;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all ease 0.1s;
  outline: none;
}

.controls button:hover {
  background-color: #e1e1e1;
}
.controls button.close-btn:hover {
  background-color: #c42b1c;
  color: #fff;
}
</style>

5. src/renderer/index/App.vue 引入 Titlebar.vue

Details
vue
<template>
  <div>
    <Titlebar />
  </div>
</template>

<script setup>
import Titlebar from "./components/Titlebar.vue";
</script>

<style scoped></style>

electron-vite 添加 @electron/remote 模块

⚠️ 警告!

此模块有许多微妙的陷阱。几乎总有比使用此模块更好的方法来完成你的任务。例如,ipcRenderer.invoke 可以满足许多常见的用例。

具体 @electron/remote 模块的使用条件和方法请查阅 https://www.npmjs.com/package/@electron/remote

以下仅是 electron-vite 中的使用方法

1. 安装@electron/remote模块

bash
npm i @electron/remote --registry=https://registry.npmmirror.com

2. src/main/index.js 添加以下高亮行代码修改

js
import { app, shell, BrowserWindow, ipcMain, session } from "electron";
import { join } from "path";
import { electronApp, optimizer, is } from "@electron-toolkit/utils";
import icon from "../../resources/icon.png?asset";
import "./titlebar";

require("@electron/remote/main").initialize();

function createWindow() {
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width: 900,
    height: 670,
    show: false,
    frame: false,
    autoHideMenuBar: true,
    ...(process.platform === "linux" ? { icon } : {}),
    webPreferences: {
      preload: join(__dirname, "../preload/index.js"),
      sandbox: false,
      webviewTag: true,
      webSecurity: false,

      nodeIntegration: true,
      contextIsolation: false,
    },
  });

  require("@electron/remote/main").enable(mainWindow.webContents);

  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"]) {
    console.log(
      "process.env['ELECTRON_RENDERER_URL']",
      process.env["ELECTRON_RENDERER_URL"] + "/index.html"
    );
    mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]);
  } else {
    mainWindow.loadFile(join(__dirname, "../renderer/index.html"));
  }

  if (is.dev) {
    mainWindow.webContents.openDevTools();
  }
}

3. 渲染进程 @electron/remote 引入

src/main/preload/index.js 添加 require('@electron/remote');代码引入

js
import {ipcRenderer, contextBridge} from 'electron';
import {electronAPI} from '@electron-toolkit/preload';

require('@electron/remote');

// Custom APIs for renderer
const api = {
  minimize: () => ipcRenderer.send('minimize'),
  maximize: () => ipcRenderer.send('maximize'),
  close: () => ipcRenderer.send('close'),
  loadPlugin: () => ipcRenderer.invoke('load-plugin'),
  getPreloadJsPath: () => ipcRenderer.invoke('get-preload-js-path'),
  isDev: () => ipcRenderer.invoke('isDev'),
};

// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
  try {
    contextBridge.exposeInMainWorld('electron', electronAPI);
    contextBridge.exposeInMainWorld('api', api);
  } catch (error) {
    console.error(error);
  }
} else {
  window.electron = electronAPI;
  window.api = api;
}

electron-vite 生成图标 electron-icon-builder

https://www.npmjs.com/package/electron-icon-builder

安装图标转换模块

bash
npm install electron-icon-builder -D --registry=https://registry.npmmirror.com

pcakge.json 添加命令 electron-icon-builder

json
{
  "scripts": {
    "logo": "electron-icon-builder --input=./public/logo.png --output=./public/"
  }
}

转换生成图标

powershell
npm run logo

electron-vite 加载本地资源

electron 加载本地资源例子

electron-vite 源码保护

官方文档:https://cn.electron-vite.org/guide/source-code-protection

1. 如何配置

js
import { defineConfig, bytecodePlugin } from "electron-vite";

export default defineConfig({
  main: {
    plugins: [bytecodePlugin()],
  },
  preload: {
    plugins: [bytecodePlugin()],
  },
  renderer: {
    // ...
  },
});

提示

bytecodePlugin 仅适用于生产阶段构建并且只支持主进程和预加载脚本。 需要注意的是,预加载脚本需要禁用 sandbox 才能支持字节码,因为字节码是基于 Node 的 vm 模块实现。从 Electron 20 开始,渲染器默认会被沙箱化,所以如果你想使用字节码来保护预加载脚本,你需要设置 sandbox: false

2. 判断源码被保护?

1. 打包一个免安装包的

powershell
npm run build:unpack

alt text

2. 进入 dist\win-unpacked\resources\app.asar 所在的目录

alt text

3. 执行 asar extract app.asar app_extracted 解压 app.asar 文件

如何解压 electron 中的 asar 文件

powershell
asar extract app.asar app_extracted

alt text

4. 对比前后的代码-被保护前后的代码

(1)被保护的源码,是二进制的,一般无法被破解,看不到源码:

alt text

(2)未开启保护源码,相当于裸奔:

alt text

electron-vite 配置别名

1. electron-vite 配置别名

js
import { resolve } from "path"; 
import {
  defineConfig,
  bytecodePlugin,
  externalizeDepsPlugin,
} from "electron-vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
import VueDevTools from "vite-plugin-vue-devtools";
import Components from "unplugin-vue-components/vite";
import { AntDesignVueResolver } from "unplugin-vue-components/resolvers";

export default defineConfig({
  main: {
    plugins: [externalizeDepsPlugin()],
  },
  preload: {
    build: {
      rollupOptions: {
        input: {
          index: resolve(__dirname, "src/preload/index.js"),
          about: resolve(__dirname, "src/preload/about.js"),
        },
      },
    },
    plugins: [externalizeDepsPlugin()],
  },
  renderer: {
    build: {
      sourcemap: true, // 启用 source map
      rollupOptions: {
        input: {
          index: resolve(__dirname, "src/renderer/index.html"),
          about: resolve(__dirname, "src/renderer/about.html"),
        },
      },
    },
    resolve: { 
      alias: { 
        "@": resolve("src"), 
        "@renderer": resolve("src/renderer"), 
        "@index": resolve("src/renderer/index"), 
      }, 
    }, 
    plugins: [
      vue(),
      vueJsx(),
      VueDevTools(),
      Components({
        resolvers: [
          AntDesignVueResolver({
            importStyle: false, // css in js
          }),
        ],
      }),
    ],
    // server: {
    //   proxy: {
    //     '/api/': {
    //       target: 'http://172.20.5.37', // 目标服务器地址
    //       changeOrigin: true,
    //     },
    //   },
    // },
  },
});

2. VSCode 按住 ctrl 键不放+点击鼠标左键 跳转到指定的文件

默认情况下,有如下配置别名的路径,如果想要快速访问 utils.js,一般会 ctrl+我们会鼠标左键点击会直接进入文件内部,但是如果没有任何配置是不会生效的

js
import { getNextId } from "@/renderer/index/utils/utils";

想要在 ctrl+鼠标左键进入文件,需要项目根目录增加 jsconfig.json 文件,内容如下:

json
{
  "compilerOptions": {
    "target": "es5",
    "module": "esnext",
    "baseUrl": "./",
    "moduleResolution": "node",
    "paths": {
      "@/*": ["src/*"],
      "@renderer/*": ["src/renderer/*"],
      "@index/*": ["src/renderer/index/*"]
    },
    "lib": ["esnext", "dom", "dom.iterable", "scripthost"],
    "allowJs": true
  }
}