创建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_DEBUG
  return {
    //base:'',//有点重要,新项目打包时没留意项目设置的是绝对路径,导致项目打包后白屏在这里浪费了很长时间。
    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, // #332
              minify: isBuild,
              outDir: 'dist-electron/preload',
              rollupOptions: {
                external: Object.keys('dependencies' in pkg ? pkg.dependencies : {}),
              },
            },
          },
        }
      ]),
      // Use Node.js API in the Renderer-process
      renderer(),
    ],
    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')
/*
* process.env.NODE_ENV用在正式项目中,此值有可能是undefined
* 可用electron的app.isPackaged代替判断是否开发环境
* app.isPackaged返回一个boolean值,如果应用已经打包,返回true ,否则返回false 。 对于大多数应用程序,此属性可用于区分开发和生产环境。
*/
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.js
      contextIsolation: false,
      preload: path.join(__dirname, 'preload.js')
    }
  })
  // 加载 index.html
  mainWindow.loadURL(
    isDev
      ? `http://localhost:${port}`
      : `file://${path.join(__dirname, '../dist/index.html')}`
  );
  if (isDev) {
    // 打开开发工具
    mainWindow.webContents.openDevTools()
  }
  // Test actively push message to the Electron-Renderer
  mainWindow.webContents.on('did-finish-load', () => {
    mainWindow?.webContents.send('main-process-message', new Date().toLocaleString())
  })

  // Make all links open with the browser, not with the application
  mainWindow.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 another
    if (mainWindow.isMinimized()) mainWindow.restore()
    mainWindow.focus()
  }
})
// 除了 macOS 外,当所有窗口都被关闭的时候退出程序。 因此,通常对程序和它们在
// 任务栏上的图标来说,应当保持活跃状态,直到用户使用 Cmd + Q 退出。
app.on('window-all-closed', function () {
  mainWindow = null
  if (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 = styleContent
  oDiv.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

打包后bug总结: 

直接整个开发的项目,问题还是很多啊。

1、关注路径问题,绝对路径和相对路径真的得注意

2、加入vue-router,将router的mode: 'history'先注释掉或改成'hash'

3、axios http请求不到,暂时方案是baseURL进行判断,如果是development走代理,否则,就是直接请求服务器地址

//import.meta.env.VITE_BASE_PATH=http://xxx.xxx.xx.xx:xxxx
const services = axios.create({
  baseURL:
    process.env.NODE_ENV === 'development'
      ? '/api'
      : import.meta.env.VITE_BASE_PATH,
})

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值