创建vite项目
创建命令:
yarn create vite electron-demo --template vue-ts
安装依赖并运行
cd electron-demo
yarn
yarn dev
添加Electron
安装electron
yarn add --dev electron
在package.json
中添加命令并且指定electron
入口
"main": "electron/main.js" //入口
"scripts": {
"dev": "vite",
"vite:build": "vue-tsc --noEmit && vite build",//修改
"preview": "vite preview",
"electron:dev": "electron ." //添加
},
新建electron
文件夹并添加main.js
作为electron
入口
// main.js
const { app, BrowserWindow } = require("electron");
const path = require("path");
function createWindow() {
// 创建浏览器窗口
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, "preload.js"),
},
});
// 链接url
mainWindow.loadURL("http://localhost:3000"); // 端口可能会被占用,后面会解决
// 是否开启开发者工具
// mainWindow.webContents.openDevTools()
}
// 这段程序将会在 Electron 结束初始化
// 和创建浏览器窗口的时候调用
// 部分 API 在 ready 事件触发后才能使用。
app.whenReady().then(() => {
createWindow();
app.on("activate", function () {
// 通常在 macOS 上,当点击 dock 中的应用程序图标时,如果没有其他
// 打开的窗口,那么程序会重新创建一个窗口。
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
// 除了 macOS 外,当所有窗口都被关闭的时候退出程序。 因此,通常对程序和它们在
// 任务栏上的图标来说,应当保持活跃状态,直到用户使用 Cmd + Q 退出。
app.on("window-all-closed", function () {
if (process.platform !== "darwin") app.quit();
});
在electron
文件夹中添加preload.js
作为预加载脚本
// preload.js
window.addEventListener("DOMContentLoaded", () => {});
配置vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
// https://vitejs.dev/config/
export default defineConfig({
base: "./",
plugins: [vue()],
});
运行
// 先执行
yarn dev
// 再执行
yarn electron:dev
启动脚本
运行yarn dev
之后再运行yarn electorn:dev
的方式属实不优雅,我们期望用一个命令就能解决.
最开始想用npm-run-all
这个库去进行串行执行,但是发现yarn dev
之后进程不会退出,导致yarn electorn:dev
执行不了
"scripts": {
"dev": "vite",
"vite:build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"electron": "electron .",
"electron:dev": "npm-run-all -s dev electron" // 串行执行
},
编写dev.js
在根目录创建一个scripts
文件夹,文件夹中创建dev.mjs
注意:
- 这里是使用
mjs
,以便使用import方式引用模块. - 也可以把改为
dev.js
去使用require
. - es规范下__dirname需要自己手动定义.
// script/dev.mjs
import { createServer } from "vite";
import { spawn } from "child_process";
import path from "path";
const __dirname = path.resolve();
const pkgDir = __dirname;
const binDir = path.resolve(pkgDir, "./node_modules/.bin"); // 获取bin目录
const server = await createServer({
configFile: path.resolve(__dirname, "./vite.config.ts"), // 引用根目录配置文件
});
const { config } = await server.listen();
console.log(config.server.port); // 获取到了vite开启的端口
server.printUrls(); // 打印服务器地址
let electronProcess = spawn(path.resolve(binDir, "electron.cmd"), ["."]); // 开启子进程
或者使用crossenv
库简化代码
yarn add cross-env
// script/dev.mjs
import { createServer } from "vite";
import { spawn } from "child_process";
import path from "path";
import crossEnv from "cross-env";
const __dirname = path.resolve();
const server = await createServer({
configFile: path.resolve(__dirname, "./vite.config.ts"), // 引用根目录配置文件
});
const { config } = await server.listen();
console.log(config.server.port); // 获取到了vite开启的端口
server.printUrls(); // 打印服务器地址
crossEnv(["electron", "."]); // 创建子进程
或者
// script/dev.mjs
import { createServer } from "vite";
import { spawn } from "child_process";
import electron from "electron"; // 引入
import path from "path";
import crossEnv from "cross-env";
const __dirname = path.resolve();
const server = await createServer({
configFile: path.resolve(__dirname, "./vite.config.ts"), // 引用根目录配置文件
});
const { config } = await server.listen();
console.log(config.server.port); // 获取到了vite开启的端口
server.printUrls(); // 打印服务器地址
let electronProcess = spawn(electron, ["."]); // 开启子进程
这里我们使用最后一种
修改package.json
"scripts": {
"dev": "vite",
"vite:build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"electron": "electron .",
"electron:dev": "node ./scripts/dev.mjs" // 修改
},
主进程的ts支持和HMR
如果我们想要在主进程中使用ts
,就需要先将ts
转换成js
,然后将electron
入口设在转换后的文件上.
build和watch
首先在electron
文件夹中添加vite.config.ts
import { defineConfig } from "vite";
export default defineConfig({
root: __dirname,
build: {
outDir: "../dist/main",
emptyOutDir: true,
minify: process.env.NODE_ENV === "production",
lib: {
entry: "main.ts",
formats: ["cjs"],
fileName: () => "[name].cjs",
},
},
});
scripts/dev.mjs
目前代码如下
// scripts/dev.mjs
import { createServer, build } from "vite";
import { spawn } from "child_process";
import path from "path";
import crossEnv from "cross-env";
import electron from "electron";
const __dirname = path.resolve();
const server = await createServer({
configFile: path.resolve(__dirname, "./vite.config.ts"),
});
const { config } = await server.listen();
server.printUrls();
let electronProcess = null;
await build({
configFile: path.resolve(__dirname, "./electron/vite.config.ts"),
mode: "development",
plugins: [
{
name: "electron-preload-watcher",
writeBundle() {
server.ws.send({ type: "full-reload" }); // 构建完成时强制刷新页面
},
},
],
build: {
lib: {
entry: "preload.ts", // 预加载脚本的入口
formats: ["cjs"],
fileName: () => "[name].cjs",
},
sourcemap: "inline",
watch: {}, // 监听变动
},
});
await build({
configFile: path.resolve(__dirname, "./electron/vite.config.ts"),
mode: "development",
plugins: [
{
name: "electron-main-watcher",
writeBundle() {
const env = Object.assign(process.env, {
VITE_PORT: config.server.port,
});
if (electronProcess) {
electronProcess.removeAllListeners();
electronProcess.kill(); // 关闭electron进程
}
// 开启新的electron进程
electronProcess = spawn(electron, ["."]);
// 关闭electron的时候同时关闭主进程
electronProcess.once("exit", process.exit);
electronProcess.stdout.on("data", (data) => {
const str = data.toString().trim();
str && console.log(str);
});
electronProcess.stderr.on("data", (data) => {
const str = data.toString().trim();
str && console.error(str);
});
},
},
],
build: {
lib: {
entry: "main.ts",
formats: ["cjs"],
fileName: () => "[name].cjs",
},
emptyOutDir: false, // 不清空文件夹 避免清除掉preload.cjs
watch: {}, // 监听变动
},
});
修改main.ts
// main.ts
const { app, BrowserWindow } = require("electron");
const path = require("path");
const port = process.env["VITE_PORT"];
const NODE_ENV = process.env.NODE_ENV;
function createWindow() {
// 创建浏览器窗口
const mainWindow = new BrowserWindow({
width: 600,
height: 600,
webPreferences: {
preload: path.join(__dirname, "preload.cjs"),
webSecurity: false,
},
});
// 链接url
mainWindow.loadURL(
NODE_ENV === "development"
? `http://localhost:${port}`
: `file://${path.join(__dirname, "../index.html")}`
);
// 是否开启开发者工具
mainWindow.webContents.openDevTools();
}
// 这段程序将会在 Electron 结束初始化
// 和创建浏览器窗口的时候调用
// 部分 API 在 ready 事件触发后才能使用。
app.whenReady().then(() => {
createWindow();
app.on("activate", function () {
// 通常在 macOS 上,当点击 dock 中的应用程序图标时,如果没有其他
// 打开的窗口,那么程序会重新创建一个窗口。
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
// 除了 macOS 外,当所有窗口都被关闭的时候退出程序。 因此,通常对程序和它们在
// 任务栏上的图标来说,应当保持活跃状态,直到用户使用 Cmd + Q 退出。
app.on("window-all-closed", function () {
if (process.platform !== "darwin") app.quit();
});
打包
添加scripts/build.mjs
import { build } from "vite";
import path from "path";
const __dirname = path.resolve();
await build({
configFile: path.resolve(__dirname, "./electron/vite.config.ts"),
mode: "production",
build: {
lib: {
entry: "preload.ts", // 预加载脚本的入口
formats: ["cjs"],
fileName: () => "[name].cjs",
},
sourcemap: "inline",
},
});
await build({
configFile: path.resolve(__dirname, "./electron/vite.config.ts"),
mode: "production",
build: {
lib: {
entry: "main.ts",
formats: ["cjs"],
fileName: () => "[name].cjs",
},
emptyOutDir: false, // 不清空文件夹 避免清除掉preload.cjs
},
});
process.exit();
使用electron-builder
进行打包操作
yarn add electron-builder
在package.json
中添加打包设置
{
"scripts": {
"dev": "vite",
"vite:build": "vue-tsc --noEmit && vite build",
"electron:build": "electron-builder",
"build": "node ./scripts/build.mjs && vite build && electron-builder",
"preview": "vite preview",
"electron": "electron .",
"electron:dev": "node ./scripts/dev.mjs"
},
"build": {
"appId": "com.your-website.your-app",
"productName": "ElectronApp",
"copyright": "Copyright © 2021 <your-name>",
"mac": {
"category": "public.app-category.utilities"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true
},
"files": [ // 需要打包的文件
"dist/**/*", // vite打包后的目录
"electron/**/*" // electron主进程的目录
],
"directories": {
"buildResources": "assets",
"output": "dist_electron" // 打包后的输出目录
}
}
}
打包结果
所有代码均已上传到githubelectron-vite-vue
参考
electron-vite
Vite+Electron快速构建一个VUE3桌面应用