创建vite+vue+electron项目
写在前面的废话
首先,这是一篇缝合文,我的目的就是想用vite、vue结合electron打包一个windows应用;其次,项目只是这三个工具的简单应用,目前还没有往里面添加其他内容。再次,项目过程中参考了google的多篇文字内容,所以如果看到有和别人一样的代码内容,不需要奇怪我就是抄的。最后,旨在记录自己项目过程中遇到的一些bug以及如果需要创建类似项目的基本流程,所以内容并不晦涩难懂,也可能会有疏漏错误,如有错误,还望指正。
PS:请勿转载也没有多大的转载价值了,您看看就好。
工具版本
- node v18.16.0
- npm v9.6.4
- vite v4.2.0
- vue v3.2.47
- electron v24.1.2
正文
创建vite+vue项目
npm create vite 【项目名】 -- --template vue
安装 electron相关依赖
npm i -D electron electron-builder vite-plugin-electron vite-plugin-electron-renderer
修改vite.config.js
import { rmSync } from 'node:fs'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path';
import { builtinModules } from 'module';
import electron from 'vite-plugin-electron'
import renderer from 'vite-plugin-electron-renderer'
import pkg from './package.json'const port = process.env.port || process.env.npm_config_port || 8081 // 端口
// https://vitejs.dev/config/
export default defineConfig(({ command }) => {//同步删除给定路径上的文件rmSync('dist-electron', { recursive: true, force: true })const isServe = command === 'serve'const isBuild = command === 'build'const sourcemap = isServe || !!process.env.VSCODE_DEBUGreturn {resolve: {alias: {'@': resolve('src'),},},server: {host: 'localhost',port: port,open: true, //先注释,不然启动不起来怪尴尬的strictPort: false,https: false,// proxy: {//跨域设置// },},plugins: [vue(),electron([{// Main-Process entry file of the Electron App.entry: 'electron/main.js',onstart(options) {if (process.env.VSCODE_DEBUG) {console.log(/* For `.vscode/.debug.script.mjs` */'[startup] Electron App')} else {options.startup()}},vite: {build: {sourcemap,minify: isBuild,outDir: 'dist-electron/main',rollupOptions: {external: Object.keys('dependencies' in pkg ? pkg.dependencies : {}),},},},},{entry: 'electron/preload.js',onstart(options) {// Notify the Renderer-Process to reload the page when the Preload-Scripts build is complete, // instead of restarting the entire Electron App.options.reload()},vite: {build: {sourcemap: sourcemap ? 'inline' : undefined, // #332minify: isBuild,outDir: 'dist-electron/preload',rollupOptions: {external: Object.keys('dependencies' in pkg ? pkg.dependencies : {}),},},},}]),// Use Node.js API in the Renderer-processrenderer(),],build: {assetsDir: 'static', // 静态资源的存放目录assetsPublicPath: './',assetsInlineLimit: 4096, // 图片转 base64 编码的阈值chunkSizeWarningLimit: 1000,rollupOptions: {external: [ // 告诉 Rollup 不要打包内建 API'electron',...builtinModules,],},optimizeDeps: {exclude: ['electron'], // 告诉 Vite 排除预构建 electron,不然会出现 __diranme is not defined},},clearScreen: false,}
})
创建electron文件夹,并且新建main.js和preload.js
main.js内容如下:
// 控制应用生命周期和创建原生浏览器窗口的模组
const { app, BrowserWindow, shell, ipcMain, protocol } = require('electron')
const { release } = require('node:os')
const path = require('path')
const isDev = process.env.NODE_ENV === 'development' ? true : false
const port = process.env.port || process.env.npm_config_port || 8081 // 端口protocol.registerSchemesAsPrivileged([{ scheme: 'app', privileges: { secure: true, standard: true, stream: true } }]);
// 禁用 Windows 7 的 GPU 加速
if (release().startsWith('6.1')) app.disableHardwareAcceleration()
// 为 Windows 10+ 通知设置应用程序名称
if (process.platform === 'win32') app.setAppUserModelId(app.getName())
//
if (!app.requestSingleInstanceLock()) {app.quit()process.exit(0)
}
let mainWindow = null;
function createWindow() {// 创建浏览器窗口mainWindow = new BrowserWindow({width: 1800,height: 1600,minWidth: 1000,minHeight: 800,webPreferences: {nodeIntegration: true, //在渲染进程启用Node.jscontextIsolation: false,preload: path.join(__dirname, 'preload.js')}})// 加载 index.htmlmainWindow.loadURL(isDev? `http://localhost:${port}`: `file://${path.join(__dirname, '../dist/index.html')}`);if (isDev) {// 打开开发工具mainWindow.webContents.openDevTools()}// Test actively push message to the Electron-RenderermainWindow.webContents.on('did-finish-load', () => {mainWindow?.webContents.send('main-process-message', new Date().toLocaleString())})// Make all links open with the browser, not with the applicationmainWindow.webContents.setWindowOpenHandler(({ url }) => {if (url.startsWith('https:')) shell.openExternal(url)return { action: 'deny' }})
}// 这段程序将会在 Electron 结束初始化
// 和创建浏览器窗口的时候调用
// 部分 API 在 ready 事件触发后才能使用。
app.whenReady().then(createWindow)
app.on('activate', function () {// 通常在 macOS 上,当点击 dock 中的应用程序图标时,如果没有其他// 打开的窗口,那么程序会重新创建一个窗口。const allWindows = BrowserWindow.getAllWindows()if (allWindows.length) {allWindows[0].focus()} else {createWindow()}
})
app.on('second-instance', () => {if (mainWindow) {// Focus on the main window if the user tried to open anotherif (mainWindow.isMinimized()) mainWindow.restore()mainWindow.focus()}
})
// 除了 macOS 外,当所有窗口都被关闭的时候退出程序。 因此,通常对程序和它们在
// 任务栏上的图标来说,应当保持活跃状态,直到用户使用 Cmd + Q 退出。
app.on('window-all-closed', function () {mainWindow = nullif (process.platform !== 'darwin') app.quit()
})
// New window example arg: new windows url
ipcMain.handle('open-win', (_, arg) => {const childWindow = new BrowserWindow({webPreferences: {preload,nodeIntegration: true,contextIsolation: false,},})if (process.env.VITE_DEV_SERVER_URL) {childWindow.loadURL(`http://localhost:${port}#${arg}`)} else {childWindow.loadFile(path.join(__dirname, '../dist/index.html'), { hash: arg })}
})
preload.js内容如下:
window.addEventListener('DOMContentLoaded', () => {const replaceText = (selector, text) => {const element = document.getElementById(selector);if (element) element.innerText = text;};for (const type of ['chrome', 'node', 'electron']) {replaceText(`${type}-version`, process.versions[type]);}
});
function domReady(condition = ['complete', 'interactive']) {return new Promise((resolve) => {if (condition.includes(document.readyState)) {resolve(true)} else {document.addEventListener('readystatechange', () => {if (condition.includes(document.readyState)) {resolve(true)}})}})
}const safeDOM = {append(parent, child) {if (!Array.from(parent.children).find(e => e === child)) {return parent.appendChild(child)}},remove(parent, child) {if (Array.from(parent.children).find(e => e === child)) {return parent.removeChild(child)}},
}/* https://tobiasahlin.com/spinkit* https://connoratherton.com/loaders* https://projects.lukehaas.me/css-loaders* https://matejkustec.github.io/SpinThatShit*/
function useLoading() {const className = `loaders-css__square-spin`const styleContent = `
@keyframes square-spin {25% { transform: perspective(100px) rotateX(180deg) rotateY(0); }50% { transform: perspective(100px) rotateX(180deg) rotateY(180deg); }75% { transform: perspective(100px) rotateX(0) rotateY(180deg); }100% { transform: perspective(100px) rotateX(0) rotateY(0); }
}
.${className} > div {animation-fill-mode: both;width: 50px;height: 50px;background: #fff;animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite;
}
.app-loading-wrap {position: fixed;top: 0;left: 0;width: 100vw;height: 100vh;display: flex;align-items: center;justify-content: center;background: #282c34;z-index: 9;
}`const oStyle = document.createElement('style')const oDiv = document.createElement('div')oStyle.id = 'app-loading-style'oStyle.innerHTML = styleContentoDiv.className = 'app-loading-wrap'oDiv.innerHTML = `<div class="${className}"><div></div></div>`return {appendLoading() {safeDOM.append(document.head, oStyle)safeDOM.append(document.body, oDiv)},removeLoading() {safeDOM.remove(document.head, oStyle)safeDOM.remove(document.body, oDiv)},}
}
// ----------------------------------------------------------------------
const { appendLoading, removeLoading } = useLoading()
domReady().then(appendLoading)
window.onmessage = (ev) => {ev.data.payload === 'removeLoading' && removeLoading()
}
setTimeout(removeLoading, 4999)
修改package.json文件
"main": "electron/main.js",//增加,重要。
//去掉"type": "module",
"scripts": { //修改"build": "vite build && electron-builder",//增加"electron:serve": "electron ."
}
//增加build项"build": {"appId": "electronApp","productName": "某应用","copyright": "Copyright © 2023","nsis": {"oneClick": false,"allowToChangeInstallationDirectory": true},"asar": true,"asarUnpack":["./dist/electron","./package.json"],"win":{"target": [{"target": "nsis","arch": [ "x64", "ia32"]}]},"extraResources": [{"from": "public/", "to": "static/"} ],"files": ["dist//*","electron//*"],"directories": {"output": "release"}},
运行npm run dev命令,项目正常启动。
项目启动完成后发现控制台有个警告,警告如图
解决方法:在index.html文件head标签内增加如下元数据标签
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
打包后效果:
npm run build