头图

当你需要将一个vue项目打包成electron时,只需要正常的安装electron依赖、electron-builder依赖,正常的配置好主进程、预加载脚本、渲染进程即可。

依赖安装

这里默认你已经写好了一个vue项目,脚手架为vite,然后需要打包成electron
安装electron

pnpm add -D electron

安装electron打包依赖

pnpm install electron-builder -D

为了使项目和electron正常运行,需要先运行项目,使得其开发服务器的url可以正常访问,然后再开启electron去加载url。 此处需要安装两个库:

pnpm install -D cross-env wait-on

cross-env:该库让开发者只需要注重环境变量的设置,而无需担心平台设置
wait-on:等待资源,此处用来等待开发服务器的url,然后开启electron
此处依赖准备完毕。

配置修改及运行

修改package.json

  "scripts": {
    "name": "snow-admin",
    "private": true,
    "version": "0.0.0",
    "type": "commonjs",
    "description": "snow-admin",
    "main": "./electron/main.js",
    "author": "wangfan",
    "license": "MIT",
    "dev": "vite",
    "electron:dev": "wait-on tcp:5173 && cross-env VITE_USER_NODE_ENV=development electron .",
  },
解释:
author、description为必填项
"main": "./electron/main.js"为主进程加载路径
"type": "commonjs"表示以commonjs运行
启动npm run electron:dev的时候,打开端口为5173,VITE_USER_NODE_ENV环境为development,启动electron .

在根目录新建electron文件夹
image.png
main.js为主进程
preload.js为预加载脚本
修改main.js中的内容

const { app, BrowserWindow } = require("electron");
const path = require("path");
// const NODE_ENV = process.env.VITE_USER_NODE_ENV;

// 创建一个窗口-封装
function createWindow() {
  // 创建一个窗口
  const win = new BrowserWindow({
    width: 800, // 窗口宽度
    height: 500, // 窗口高度
    autoHideMenuBar: true, // 隐藏默认菜单
    webPreferences: {
      preload: path.resolve(__dirname, "./preload.js") // 加载预加载脚本,绝对路径
    }
  });

  // 以url方式打开
  win.loadURL("http://localhost:5173");
}

// 监听app的ready事件
app.on("ready", () => {
  createWindow();
  // window所有窗口关闭时,并且不是苹果系统,退出应用-管理窗口的生命周期
  app.on("window-all-closed", () => {
    if (process.platform !== "darwin") app.quit();
  });
});

// 应用被激活时,窗口数量为0,自动创建一个窗口-管理窗口的生命周期
app.on("activate", () => {
  if (BrowserWindow.getAllWindows().length === 0) createWindow();
});

这里就是一个最基本的主进程逻辑,唯一不同的就是打开方式是以url方式打开,路径为本地项目5173端口
修改预加载脚本preload.js中的内容

const { contextBridge } = require("electron");

const NODE_ENV = process.env.VITE_USER_NODE_ENV;
console.log("预加载脚本,运行环境", NODE_ENV, process);

contextBridge.exposeInMainWorld("myAPI", {
  mytext: "这是暴露的变量"
});

这一步是为了让你知道electron成功运行了,当你项目启动的时候,控制台会输出console.log打印语句process是只有node端才能使用的字段,若浏览器成功输出,说明你成功调用nodeAPI
然后先启动你的vue项目

pnpm run dev

image.png
这里我的端口是5173
然后启动electron项目

pnpm run electron:dev

image.png
electron中成功打开vue项目,打开控制台可以看到预加载脚本console.log成功输出了,说明vue项目打包electron成功了。
至于vue项目如何与electron主进程通信,这个就是electron的基本知识了,这里不细讲。
通信的过程应该是这样的:
vue项目(渲染进程) -> 预加载脚本(preload.js) -> 主进程(main.js)
vue项目中正常过的调用预加载脚本函数,然后预加载脚本发送事件到主进程,主进程监听事件做逻辑处理
例如:vue项目中调用预加载脚本定义的myAPI.saveFile函数,然后主进程监听事件

// vue项目
window.myAPI.saveFile(form.value.username);

// 预加载脚本preload.js
contextBridge.exposeInMainWorld("myAPI", {
  mytext: "这是暴露的变量",
  saveFile: data => {
    console.log("调用预加载脚本");
    ipcRenderer.send("file-save", data);
  },
});

// 主进程main.js
ipcMain.on("file-save", ()=> {});

项目打包

package.json中修改

 "scripts": {
    "dev": "vite",
    "electron:build": "cross-env VITE_USER_NODE_ENV=production electron-builder",
  },

这里实际上是指定你的运行环境为production,然后执行electron-builder命令来打包electron
package.json中与scripts同级,electron-builde打包配置:

  "build": {
    "appId": "snow-admin",
    "productName": "electron-snow",
    "copyright": "Copyright © 2024",
    "mac": {
      "category": "public.app-category.utilities",
      "icon": "./logo.ico"
    },
    "win": {
      "icon": "./logo.ico",
      "target": [
        {
          "target": "nsis",
          "arch": [
            "x64"
          ]
        }
      ]
    },
    "nsis": {
      "oneClick": false,
      "perMachine": false,
      "allowToChangeInstallationDirectory": true,
      "createDesktopShortcut": true,
      "createStartMenuShortcut": true,
      "shortcutName": "sd-cashier"
    },
    "files": [
      "dist/**/*",
      "electron/**/*"
    ],
    "directories": {
      "buildResources": "assets",
      "output": "dist_electron"
    }
  },

配置注释


"build": {  
  "appId": "your.id", // 应用的唯一ID  
  "productName": "YourProductName", // 安装后生成的文件夹和快捷方式的名称  
  "win": { 
      "icon": "./logo.ico", // 应用图标
      "target": [
        {
          "target": "nsis", // 指定使用 NSIS 作为安装程序格式
          "arch":  ["x64"] // 生成64位安装包
        }
      ]
  },
  "nsis": {  
    "oneClick": false, // 是否一键安装,如果为 false,则显示安装向导  
    "allowElevation": true, // 是否允许请求提升(以管理员身份运行)  
    "allowToChangeInstallationDirectory": true, // 是否允许用户更改安装目录  
    "createDesktopShortcut": true, // 是否在桌面上创建快捷方式  
    "createStartMenuShortcut": true, // 是否在开始菜单中创建快捷方式  
    "shortcutName": "YourAppName", // 快捷方式的名称  
    "uninstallDisplayName": "Your App", // 卸载时显示的名称  
    "license": "path/to/license.txt", // 许可证文件的路径  
    "installerIcon": "path/to/installer-icon.ico", // 安装程序图标的路径  
    "uninstallerIcon": "path/to/uninstaller-icon.ico", // 卸载程序图标的路径  
    "installerHeaderIcon": "path/to/header-icon.ico", // 安装向导头部的图标路径  
    "installerSidebarIcon": "path/to/sidebar-icon.bmp", // 安装向导侧边栏的图标路径(必须是 BMP 格式)  
    "runAfterFinish": true, // 安装完成后是否运行应用  
    "perMachine": true, // 是否为所有用户安装(而非仅当前用户)  
    "script": "path/to/custom-nsis-script.nsh", // 自定义 NSIS 脚本的路径  
    "compression": "lzma", // 压缩方式,可选值包括 'none', 'zip', 'lzma' 等  
    "artifactName": "${productName}-${version}-Setup.${ext}", // 自定义输出文件的名称  
  },  
}

打包线上web项目

跟启动本地项目没有什么区别,由于我们将已有的web端项目打包,所以很难做到将vue项目完全迁移到electron中,最简单的方法就是修改主进程中的win.loadURL("你的线上url");
具体是通过process.env.VITE_USER_NODE_ENV来区分你的运行环境,这里做一个主进程的示例:

const NODE_ENV = process.env.VITE_USER_NODE_ENV;
// 创建一个窗口-封装
function createWindow() {
  // 创建一个窗口
  const win = new BrowserWindow({
    width: 800, // 窗口宽度
    height: 500, // 窗口高度
    autoHideMenuBar: true, // 隐藏默认菜单
    webPreferences: {
      preload: path.resolve(__dirname, "./preload.js") // 加载预加载脚本,绝对路径
    }
  });

  ipcMain.on("file-save", writeFile);

  if (NODE_ENV == "development") {
    win.loadURL("http://localhost:5173"); // 本地
  } else {
    win.loadURL("http://101.126.93.137:82"); // 线上
  }
}

// 监听app的ready事件
app.on("ready", () => {
  createWindow();
  // window所有窗口关闭时,并且不是苹果系统,退出应用-管理窗口的生命周期
  app.on("window-all-closed", () => {
    if (process.platform !== "darwin") app.quit();
  });
});

通过环境区分来判断你打开的项目
线上和本地其实是一样的,只要项目在electron中正常打开就都具备与主进程通信的能力。
其实你的vue项目是一个渲染进程,也就是web端运行的,而electron正好可以通过预加载脚本来让渲染进程和主进程通信,也就是说只要你的项目正常打开,它就运行在渲染进程,它就具备与主进程通信的能力。

打包生产环境dist到electron本地启动

需要先掌握上一节,《打包线上web项目》的基础知识,篇章为上一篇的进阶

由于electron使用win.loadURL来加载页面会有CSP(内容安全策略风险),并且它引用的是线上链接,所以非常不安全。
我们可以将vite+vue生产环境的包打到electron来生成应用,这样更安全,且不需要发布线上版本,安装即用。
首先你要先配置好你的启动环境,在我们本地开发的时候,electron监听的是本地url链接,若打包生产,则使用的是生产包资源(dist)

配置启动环境

配置启动环境,本地项目运行,一键启动web端和electron端

先配置本地启动,通过concurrently来同时启动多个命令,通过cross-env来设置运行环境

pnpm add -D concurrently
pnpm install cross-env --save-dev
  "scripts": {
    "dev": "vite",
    "start": "concurrently \"pnpm run dev\" \"pnpm run electron:dev\"",
    "electron:dev": "cross-env VITE_USER_NODE_ENV=development electron .",
    "electron:build": "vite build --mode production && cross-env VITE_USER_NODE_ENV=production electron-builder",
    "build:dev": "vue-tsc && vite build --mode development",
    "build:prod": "vue-tsc && vite build --mode production"
  },

本地开发的时候使用pnpm run start来同时启动pnpm run devpnpm run electron:dev两条命令
pnpm run dev就是正常启动本地vite项目
pnpm run electron:dev就是启动electron项目,在启动这条命令的时候,会通过cross-env来设置环境变量VITE_USER_NODE_ENV=development,主进程就可以通过VITE_USER_NODE_ENV来判断当前运行的是哪个环境
在本地项目中,我们需要在vite.config.ts中设置启动端口

    server: {
      port: 8080, // 设置端口号
      cors: true, // 允许跨域
      hmr: true, // 开启热更新
    },

主进程中就可以根据当前环境来判断加载方式,若是开发环境就监听本地的8080端口,若是生产环境则使用vite打包后的dise/index.html文件,这个是启动入口
image.png
这样做,你在本地开发的时候只需要运行pnpm run start命令,就会启动本地项目以及electron,由于配置了hmr热更新,修改页面electron项目也会同步更新。

打包生产环境,electron嵌入生产环境生成应用

我们在主进程中做了判断

if (process.env.VITE_USER_NODE_ENV == "development") {
    win.loadURL("http://localhost:8080");
  } else {
    win.loadFile(path.resolve(__dirname, "../dist/index.html"));
  }

若不是开发环境,则走生产环境,这里electron加载的文件为vite打包后的dist/index.html文件,使用的本地资源进行打包。
image.png
package.json中我们配置了这么一条命令:

"scripts": {
    "electron:build": "vite build --mode production && cross-env VITE_USER_NODE_ENV=production electron-builder",
  },

首先需要搞清楚一件事,有些script启动命令使用单个&,有些使用两个&
&:同时启动多个命令
&&:依次启动多个命令
这里先启动vite build --mode production打包生产环境,然后启动cross-env VITE_USER_NODE_ENV=production electron-builder设置生产环境,并且使用electron-builder打包应用。
我们稍后再运行打包命令,先配置打包路径
image.png
在主进程文件中配置预加载脚本的路径
image.png
打包配置,这里使用electron-builder进行打包,在package.json中配置,与scripts同级

  "build": {
    "appId": "snow-admin",
    "productName": "snow-admin",
    "copyright": "Copyright © 2024",
    "mac": {
      "category": "public.app-category.utilities",
      "icon": "./logo.ico"
    },
    "win": {
      "icon": "./logo.ico",
      "target": [
        {
          "target": "nsis",
          "arch": [
            "x64"
          ]
        }
      ]
    },
    "nsis": {
      "oneClick": false,
      "perMachine": false,
      "allowToChangeInstallationDirectory": true,
      "createDesktopShortcut": true,
      "createStartMenuShortcut": true,
      "shortcutName": "sd-cashier"
    },
    "files": [
      "dist/**/*",
      "electron/**/*"
    ],
    "directories": {
      "buildResources": "assets",
      "output": "dist_electron"
    }
  },

运行打包指令pnpm run develectron:build生成应用程序
image.png

打包后的几种常见报错

1、安装应用后项目白屏
image.png
这种可能是vite打包资源没有指定到正确路径,检查vite.config.ts文件中的base字段,将其改为'./'
vite官方文档上有说明:vite-base
image.png

注意:这个适用于嵌入形式开发,如果你修改了路径打包成electron后,再打包web端需要改回来

2、项目正常启动,但是预加载脚本运行报错
image.png
可能是预加载脚本资源路径报错导致,检查你的文件位置,若主进程和预加载脚本处于同级目录,这里就不能用../preload.js
image.png
将其改为preload.js./preload.js即可

const win = new BrowserWindow({
    // 省略其它...
  
    webPreferences: {
      preload: path.resolve(__dirname, "preload.js") // 加载预加载脚本
    }
  });

在这种资源嵌入打包的场景下,很多时候资源加载报错都是因为路径引入的问题导致,出现这种问题一定要好好检查一下各文件的所处位置和引入路径。


兔子先森
388 声望16 粉丝

致力于新技术的推广与优秀技术的普及。