electron-vite 快速开始
安装 VSCode
官网:https://code.visualstudio.com/download
1. 选择 system installer
版本下载
2. 安装勾选 VSCode 环境变量
3. 安装插件
Vue - Official
Prettier - Code formatter
ESLint
GitLens — Git supercharged
Error Lens
Auto Import
Auto Rename Tag
环境配置
1. 安装 Node.js
点击下载 https://nodejs.org/dist/20.18.1/node-v20.18.1-x64.msi
PS D:\project\pc.hifi> node -v
v20.18.1
经验提示
建议直接覆盖安装的方式更新 Node.js 版本而不是使用 nvm 管理工具切换 Node.js 版本
2. 检查电脑策略是否为 RemoteSigned
打开 PowerShell
,输入 Get-ExecutionPolicy
看是否输出 RemoteSigned
,如果是 Restricted
则需要继续往下看,需要设置为 RemoteSigned
Get-ExecutionPolicy
超级管理员方式打开 PowerShell
,执行策略设置为 RemoteSigned
Set-ExecutionPolicy RemoteSigned
3. 全局配置 npm 镜像加速
npm config set registry https://registry.npmmirror.com
PS C:\Users\Administrator\Desktop> npm config get registry
https://registry.npmmirror.com
PS C:\Users\Administrator\Desktop>
经验提示
electron 项目建议使用 npm 而不是 yarn 或者 pnpm。
electron-vite 初始化项目
中文官网:https://cn.electron-vite.org/
快速开始:https://cn.electron-vite.org/guide/#搭建第一个-electron-vite-项目
1. npm create 创建项目
npm create @quick-start/electron@latest
2. 安装项目依赖
npm i
npm 查看可以升级的包
1. 查看可以升级的包
要查看可以升级的包,您可以使用以下命令:
npm outdated
该命令会列出当前项目中所有已安装的包,以及它们的当前版本、所需的版本(根据 package.json
中的 dependencies
或 devDependencies
)和最新版本。如果某个包有可用的更新,它会显示出来。
输出示例:
Package Current Wanted Latest Location
express 4.17.1 4.18.0 5.0.0 my-project
lodash 4.17.19 4.17.21 4.17.21 my-project
- Current: 当前安装的版本。
- Wanted: 根据
package.json
文件中规定的版本范围,npm 认为的期望版本。 - Latest: 当前 npm 仓库中该包的最新版本。
2. 升级所有可升级的包
要将所有的包更新到最新版本,可以使用:
npm update
这条命令会根据 package.json
文件中的版本范围(如 ^
或 ~
)来更新包。如果你希望强制升级到最新的版本,不管 package.json
中的版本范围,下面有几个方法。
如果你想升级某个包,可以使用:
npm update <package-name>
2.1. 手动升级 package.json
如果你希望升级到最新的版本,确保你的 package.json
中的版本范围是开放的(例如使用 ^
或 latest
),然后运行:
npm install
这会按照 package.json
的版本要求安装所有依赖项的最新版本。
2.2. 使用 npx npm-check-updates
(ncu)
如果你希望强制升级所有包并自动更新 package.json
中的版本,可以使用 npm-check-updates
(简称 ncu
)工具:
安装
npm-check-updates
工具:bashnpm install -g npm-check-updates
运行
ncu
来查看可以升级的包:bashncu
使用
ncu -u
来自动更新package.json
中的版本号:bashncu -u
最后,重新安装所有依赖:
bashnpm install
这样,所有依赖包将被升级到最新版本,package.json
会自动更新版本号,并安装这些更新的依赖。
总结:
npm outdated
查看可以升级的包。npm update
升级符合版本范围的依赖。npx npm-check-updates
强制升级所有包并更新package.json
。
3. 启动项目
npm run dev
4. 修改 electron-builder 配置文件
electron-builder.yml 改为 electron-builder.config.js
module.exports = {
appId: "com.electron.app",
productName: "electron-vite-start",
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: "electron-vite-start",
},
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/",
},
};
package.json 指定 electron-builder --config=electron-builder.config.js 文件打包
{
"scripts": {
"format": "prettier --write .",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
"start": "electron-vite preview",
"dev": "electron-vite dev",
"build": "electron-vite build",
"postinstall": "electron-builder install-app-deps",
"build:unpack": "npm run build && electron-builder --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"
"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
,你可以在命令行中这样指定:
electron-builder --config build-config.js
这种方式允许你在构建项目时灵活选择和使用不同的配置文件。你可以将配置文件以 YAML、JSON 或 JavaScript 的格式保存,只需确保通过 --config
选项指定正确的路径和文件名即可。
打包构建
npm run build:win
asar extract app.asar app_extracted
electron-builder.config.js 修改后:
const isWin32 = process.platform === "win32";
module.exports = {
appId: "com.electron.app",
productName: "electron-vite-start",
directories: {
buildResources: "build",
},
files: [
"!**/.vscode/*",
"!src/*",
"!electron.vite.config.{js,ts,mjs,cjs}",
"!{electron.vite.config.mjs,electron-builder.config.js}",
"!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}",
"!{.env,.env.*,.npmrc,pnpm-lock.yaml}",
],
asarUnpack: ["resources/**"],
electronLanguages: isWin32
? ["en", "zh-TW", "zh-CN", "en-US", "en-GB"]
: ["en", "zh_TW", "zh_CN", "en_US", "en_GB"],
win: {
icon: "build/icon.ico",
target: [{ target: "nsis", arch: ["x64"] }],
executableName: "electron-vite-start",
},
nsis: {
artifactName: "${name}-${version}-setup.${ext}",
artifactName: "${name}-${os}-${arch}-${version}-setup.${ext}",
shortcutName: "${productName}",
uninstallDisplayName: "${productName}",
createDesktopShortcut: "always",
oneClick: false, // 设置为 false 以提供安装类型选择界面,允许用户选择是否创建桌面图标,允许用户选择安装路径
perMachine: true, // 设置为 true 将使安装程序默认为所有用户安装应用,这需要管理员权限
allowToChangeInstallationDirectory: true, // 如果设置为 true,安装程序将允许用户更改安装目录
allowElevation: true, // 一般情况下,此字段不会被直接使用,权限提升主要依赖于 perMachine 的设定。当perMachine为true,安装程序会请求管理员权限
deleteAppDataOnUninstall: true, // 如果设置为 true,卸载程序将删除AppData中的所有程序数据
createStartMenuShortcut: true, // 如果设置为 true,安装程序将在开始菜单中创建程序快捷方式
},
mac: {
type: "development",
icon: "build/icon.icns",
identity: null,
hardenedRuntime: false,
target: [
{
target: "dmg", // dmg、pkg、mas、mas-dev
arch: ["universal"], // 'x64', 'arm64', 'universal'
},
],
// // #region
// // fix: that's the same in both x64 and arm64 builds and not covered by the x64ArchFiles rule: "undefined"
// mergeASARs: false,
// singleArchFiles: "*",
// x64ArchFiles: "*",
// // #endregion
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}",
artifactName: "${name}-${os}-${arch}-${version}.${ext}",
},
linux: {
target: ["AppImage", "snap", "deb"],
maintainer: "electronjs.org",
maintainer: "",
category: "Utility",
},
appImage: {
artifactName: "${name}-${version}.${ext}",
artifactName: "${name}-${os}-${arch}-${version}.${ext}",
},
npmRebuild: false,
publish: {
provider: "generic",
url: "https://example.com/auto-updates",
url: "",
},
electronDownload: {
mirror: "https://npmmirror.com/mirrors/electron/",
},
};
打包构建
npm run build:unpack
asar extract app.asar app_extracted
5. .prettierrc.yaml 改成 .prettierrc.js
.prettierrc.js 内容如下
module.exports = {
// 使用单引号代替双引号
singleQuote: true,
// 在每行代码末尾添加分号
semi: true,
// 设置代码的最大打印宽度为140个字符
printWidth: 140,
// 在多行对象或数组的最后一项后面添加逗号
trailingComma: "all",
// 在对象大括号内不添加空格
bracketSpacing: false,
};
electron-builder.config.js
module.exports = {
appId: "com.electron.app",
productName: "electron-vite-start",
directories: {
buildResources: "build",
},
files: [
"!**/.vscode/*",
"!src/*",
"!{electron.vite.config.mjs,electron-builder.config.js}",
"!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}",
"!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.js,dev-app-update.yml,CHANGELOG.md,README.md}",
"!{.env,.env.*,.npmrc,pnpm-lock.yaml}",
],
// ...
};
6. .eslintrc.cjs 修改成如下:
要让 ESLint 支持 Prettier 的配置,你需要将 Prettier 的规则集集成到 ESLint 中。以下是更新后的 ESLint 配置,包含对 Prettier 配置的支持:
配合 VSCode 的 prettier 插件,yyds
module.exports = {
extends: [
"eslint:recommended", // 使用 ESLint 推荐的规则
"plugin:vue/vue3-recommended", // 使用 Vue 3 推荐的规则
"@electron-toolkit", // 使用 Electron 项目推荐的 ESLint 配置
"@vue/eslint-config-prettier", // 使用 Vue 项目与 Prettier 兼容的 ESLint 配置
"prettier", // 使用 Prettier 的 ESLint 配置来保证代码格式一致
],
plugins: ["prettier"], // 启用 Prettier 插件来处理代码格式问题
rules: {
"vue/require-default-prop": "off", // 关闭 Vue 对于 props 默认值的要求
"vue/multi-word-component-names": "off", // 关闭 Vue 对组件命名必须包含多个单词的规则
"vue/attribute-hyphenation": "off", // 允许在模板中使用大写字母作为属性名,而不需要强制转换成小写字母
"vue/v-on-event-hyphenation": "off", // 允许在模板中使用大写字母作为事件绑定的属性名
// 配置 Prettier 的相关选项,确保代码格式符合预期
"prettier/prettier": [
"error", // 如果格式不符合 Prettier 规则则报错
{
singleQuote: true, // 使用单引号代替双引号
semi: true, // 语句末尾加分号
printWidth: 120, // 每行代码最大长度为 120 个字符
trailingComma: "all", // 在可能的地方(对象、数组等)添加尾逗号
bracketSpacing: false, // 对象字面量中的括号不加空格(例如:{a: 1})
},
],
},
};
主要更改包括:
- 在
extends
中添加'prettier'
以确保 ESLint 配置支持并遵循 Prettier 的规则。 - 在
prettier
配置中添加自定义规则以确保代码风格一致。
这个配置文件确保 ESLint 和 Prettier 的规则不会冲突,并且可以保持代码风格的一致性。
7. 格式化代码
npm run format
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. package.json 增加 "type": "commonjs"
{
"name": "electron-vite-start",
"version": "1.0.0",
"description": "An Electron application with Vue",
"main": "./out/main/index.js",
"author": "example.com",
"homepage": "https://electron-vite.org",
"type": "commonjs",
"scripts": {
// ...
}
}
9. 打包 Windows 安装包
npm run build:win
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 多窗口
实现效果如下:
1. 修改 electron.vite.config.mjs
electron.vite.config.mjs 文件修改点如下
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
<!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
<!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
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
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
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 的最小化、缩放、最大化按钮是通过 titleBarStyle
和 titleBarOverlay
设置的
const { BrowserWindow } = require("electron");
const win = new BrowserWindow({
titleBarStyle: "hidden",
titleBarOverlay: {
color: "#2f3241",
symbolColor: "#74b1be",
height: 60,
},
});
titleBarOverlay
被设置后,只显示 最小化, 最大化/还原,关闭窗口的按钮
(1)、src/main/titlebar.js
Details
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 引入
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
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
<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
<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
模块
npm i @electron/remote --registry=https://registry.npmmirror.com
2. src/main/index.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');
代码引入
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
安装图标转换模块
npm install electron-icon-builder -D --registry=https://registry.npmmirror.com
pcakge.json 添加命令 electron-icon-builder
{
"scripts": {
"logo": "electron-icon-builder --input=./public/logo.png --output=./public/"
}
}
转换生成图标
npm run logo
electron-vite 加载本地资源
electron-vite 源码保护
官方文档:https://cn.electron-vite.org/guide/source-code-protection
1. 如何配置
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. 打包一个免安装包的
npm run build:unpack
2. 进入 dist\win-unpacked\resources\app.asar
所在的目录
3. 执行 asar extract app.asar app_extracted
解压 app.asar
文件
asar extract app.asar app_extracted
4. 对比前后的代码-被保护前后的代码
(1)被保护的源码,是二进制的,一般无法被破解,看不到源码:
(2)未开启保护源码,相当于裸奔:
electron-vite 配置别名
1. electron-vite 配置别名
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+我们会鼠标左键点击会直接进入文件内部,但是如果没有任何配置是不会生效的
import { getNextId } from "@/renderer/index/utils/utils";
想要在 ctrl+鼠标左键进入文件,需要项目根目录增加 jsconfig.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
}
}
electron-vite 减少包的体积
关键点,移除非必要的代码或者文件
- 移除不需要的语言包
- 移除多余的模块,非必要的模块不要
- 查看打包后的是否有多余的代码被打包进去了
electron-vite 如何引入 .node 插件
1. 静态引入
引入已存在的文件,文件不存在则会报错
2. 动态引入
可以条件引入对应的文件,文件可以不存在也不会报错,适用于 Windows/Mac/Linux 等多平台开发,有的有对应的安装包,有的没有对应的安装包
electron-vite js 代码压缩、js 代码混淆、html 代码压缩
1. 安装依赖
- Terser(压缩 JS 代码)
- rollup-plugin-javascript-obfuscator(对 JS 代码进行进一步混淆)
- vite-plugin-html(压缩 HTML 代码)
npm install terser rollup-plugin-javascript-obfuscator vite-plugin-html -D
2. electron.vite.config.mjs 配置示例
!!!注意这些配置都是希望在打包后生效,然而直接配置会导致开发和生产环境都生效,故需要通过一定的条件判断是否为打包命令,才进行添加对应的配置
import {
defineConfig,
externalizeDepsPlugin,
bytecodePlugin,
} from "electron-vite";
import vue from "@vitejs/plugin-vue";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
// 1) 代码混淆插件(JS Obfuscation)
import obfuscator from "rollup-plugin-javascript-obfuscator";
// 2) HTML 压缩插件
import { createHtmlPlugin } from "vite-plugin-html";
import { resolve } from "path";
export default defineConfig(({ mode }) => {
const isProd = mode === "production";
let terserObj = {};
if (isProd) {
// 为什么这么配置?
// 开发环境无需进行压缩等处理
terserObj = {
sourcemap: false, // 启用 source map
minify: "terser", // 使用 terser 进行压缩
terserOptions: {
compress: {
drop_console: true, // 去掉 console
drop_debugger: true, // 去掉 debugger
},
},
};
}
return {
// 主进程配置
main: {
build: {
...terserObj,
rollupOptions: {
// 在 rollupOptions 下添加代码混淆插件
plugins: [
// 这里对主进程生成的 js 进一步混淆
isProd &&
obfuscator({
compact: true,
controlFlowFlattening: true,
controlFlowFlatteningThreshold: 1,
deadCodeInjection: true,
deadCodeInjectionThreshold: 1,
}),
].filter(Boolean),
},
},
plugins: [
isProd && bytecodePlugin(), // 将主进程代码编译成 V8 字节码(可选)
externalizeDepsPlugin(), // electron-vite 内置插件
nodeResolve({ preferBuiltins: true }),
commonjs(),
].filter(Boolean),
},
// 预加载脚本配置 (preload)
preload: {
build: {
...terserObj,
rollupOptions: {
input: {
index: resolve(__dirname, "src/preload/index.js"),
about: resolve(__dirname, "src/preload/about.js"),
},
plugins: [
isProd &&
obfuscator({
compact: true,
controlFlowFlattening: true,
controlFlowFlatteningThreshold: 1,
deadCodeInjection: true,
deadCodeInjectionThreshold: 1,
}),
].filter(Boolean),
},
},
plugins: [externalizeDepsPlugin(), isProd && bytecodePlugin()],
},
// 渲染进程配置 (renderer)
renderer: {
build: {
...terserObj,
rollupOptions: {
input: {
index: resolve(__dirname, "src/renderer/index.html"),
about: resolve(__dirname, "src/renderer/about.html"),
},
// 如果需要对渲染进程的 JS 进一步混淆,可同样添加
plugins: [
isProd &&
obfuscator({
compact: true,
controlFlowFlattening: true,
controlFlowFlatteningThreshold: 1,
deadCodeInjection: true,
deadCodeInjectionThreshold: 1,
}),
].filter(Boolean),
},
},
resolve: {
alias: {
"@renderer": resolve("src/renderer/src"),
},
},
plugins: [
vue(),
/**
* 使用 vite-plugin-html 对 HTML 进行压缩
* 注意:只在生产环境下启用,不要在 dev 启用
*/
isProd &&
createHtmlPlugin({
removeComments: true,
collapseWhitespace: true,
collapseBooleanAttributes: true,
removeEmptyAttributes: true,
minifyCSS: true, // 内联 CSS 也进行压缩
minifyJS: true, // 内联 JS 也进行压缩
}),
].filter(Boolean),
},
};
});
3. 功能说明 & 注意事项
Terser(JS 压缩)
- 使用
build.minify = 'terser'
可以让最终生成的 JS 被 Terser 压缩,配合terserOptions
去除console.log
、debugger
等。 - 如果你的项目更倾向于快速打包和较低强度的压缩,也可用内置的
esbuild
,但想要更多可控的 JS 压缩/混淆选项,还是terser
更灵活。
- 使用
rollup-plugin-javascript-obfuscator(JS 混淆)
- 在
rollupOptions.plugins
中使用,可以对打包出的 JS 代码进行进一步混淆,比如变量名、逻辑流扁平化等。 - 优点是混淆程度高,反编译难度更大;缺点是打包后的体积会显著增大,也会拖慢代码运行速度。
- 混淆常见于对源码安全性要求高,但也要权衡调试和运行效率的影响。
- 在
vite-plugin-html(HTML 压缩)
- 可以把打包输出的 HTML 进行压缩,比如移除注释、空格、内联 CSS/JS 压缩等。
- 如果你有多入口 HTML,都能通过
rollupOptions.input
进行多入口打包,并统一由vite-plugin-html
压缩。
bytecodePlugin()(V8 字节码编译)
- electron-vite 自带的
bytecodePlugin()
可以编译源代码为 V8 字节码,进一步增加反编译难度;注意它会让体积增大,并可能影响启动速度。 - 你可以只对主进程、预加载脚本做字节码,也可以都做;使用时只需在对应
plugins
中加入即可。
- electron-vite 自带的
在开发环境中调试
- 通常只在生产(
build
)环境下启用这些压缩/混淆插件。 - 在开发模式下(
npm run dev
),保留源码的可读性和热重载效率通常更重要,不必开启所有的混淆和压缩。
- 通常只在生产(
安全性
- 混淆并不是绝对安全。如果攻击者可以直接访问你的打包文件,可以通过逆向工程获取逻辑。
- Electron 应用如果对安全有强需求,最好结合系统的代码签名(Code Signing)、完整性校验(Integrity Checks) 等。
4. 小结
- Terser:负责基础的 JS 压缩、去除调试语句、变量名简化等。
- rollup-plugin-javascript-obfuscator:在 Terser 之上进一步做逻辑流扁平化、字符串加密等深度混淆。
- vite-plugin-html:对 HTML 进行压缩(空格、注释、内联 CSS/JS),保证打包产物的体积更小、更不易阅读。
只要在 electron.vite.config.mjs
中合理组合上述插件,就可以在 electron-vite 中同时实现JS 压缩 + JS 混淆 + HTML 压缩,从而在一定程度上保护源码并提升产物性能。
electron-vite 环境变量配置
希望根据打包命令的不同,打包出不同的包
defineConfig 可以接受一个函数作为回调,第一个参数中,会包含 mode、command,一般会根据这个 mode 和 command 命令来区分执行的命令,从而做出返回不同的配置的目录
electron.vite.config.mjs
- 默认的配置
export default defineConfig({
main: {
plugins: [externalizeDepsPlugin(), bytecodePlugin()],
resolve: {
alias: {
"@": resolve("src"),
},
},
},
// ...
});
- 根据环境变量以及 mode 进行的配置
export default defineConfig(({ mode, command }) => {
return {
main: {
plugins: [externalizeDepsPlugin(), bytecodePlugin()],
resolve: {
alias: {
"@": resolve("src"),
},
},
},
// ...
};
});
electron-builder.config.js
这里可以在执行命令的时候添加环境变量,来区分执行了哪一个命令,比如 cross-env BUILD_MODE=production,然后在代码中通过 process.env.BUILD_MODE 读取,从而实现根据不同的变量来打包不同的结果,如果要读取.env.x 相关的配置文件,则还可以直接通过 dotenv 来解析对应的文件实现
package.json
{
"scripts": {
"build:prod": "electron-vite build --mode=production && cross-env BUILD_MODE=production electron-builder --config=electron-builder.config.js"
}
}
electron-builder.config.js
module.exports = {
appId: "com.your_app.name",
productName: "your_app_name",
directories: {
buildResources: "build",
},
files: [
"!**/.vscode/*",
"!src/*",
"!electron.vite.config.{js,ts,mjs,cjs}",
"!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.js,dev-app-update.yml,CHANGELOG.md,README.md}",
"!{jsconfig.json,*.md,email_upload_file.png,electron-builder.config.js}",
"!{.env,.env.*,.npmrc,pnpm-lock.yaml}",
],
artifactName: `your_app_name-\${os}-\${arch}-${process.env.BUILD_MODE}-\${version}.\${ext}`,
};