Electron + Vue 项目从零创建,不使用 vue-electron-template

Electron + Vue 项目创建

1. vite项目框架搭建

按照一般的开发流程,用命令行创建一个vite项目:

# use npm or yarn or pnpm
npm(yarn | pnpm) create vite

根据自己的需要,选择相应的选项。创建完成后,进入项目,安装依赖并运行,一个基础的vite项目就创建好了。

ex:默认创建时,可能需要的额外操作
  1. TS支持

    由于vite默认使用esbuild处理TS,天然支持它,因此只需要添加TS相关依赖,并编写tsconfig.json以应用TS检查即可。

    # use npm or yarn or pnpm
    npm install -D typescript vue-tsc
    
    // tsconfig.json
    {
      "compilerOptions": {
        "target": "esnext",
        "module": "esnext",
        "strict": true,
        "jsx": "preserve",
        "moduleResolution": "node",
        "isolatedModules": true,
        "skipLibCheck": true,
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "forceConsistentCasingInFileNames": true,
        "useDefineForClassFields": true,
        "sourceMap": true,
        "baseUrl": ".",
        "types": [
          "webpack-env"
        ],
        "paths": {
          "@/*": [
            "src/*"
          ]
        },
        "lib": [
          "esnext",
          "dom",
          "dom.iterable",
          "scripthost"
        ]
      },
      "include": [
        "src/**/*.ts",
        "src/**/*.tsx",
        "src/**/*.vue",
        "tests/**/*.ts",
        "tests/**/*.tsx"
      ],
      "exclude": [
        "node_modules"
      ]
    }
    
    // package.json
    {
        ...
        "scripts": {
          "dev": "vite --host",
          "build": "vue-tsc && vite build",
        }
    }
    
  2. vue-router和vuex/pinia

    添加依赖后正常在main.ts中引入即可。

    # use npm or yarn or pnpm
    npm install vue-router vuex(or pinia)
    
    // src/main.ts
    import { createApp } from "vue";
    import router from "./router";
    import store from "./store";
    import "./style.css";
    import App from "./App.vue";
    
    createApp(App).use(router).use(store).mount("#app");
    
    // or
    
    import { createApp } from 'vue'
    import { createPinia } from 'pinia'
    import router from './router'
    import App from './App.vue'
    
    import "./style.css";
    
    const app = createApp(App)
    
    app.use(createPinia())
    app.use(router)
    
    app.mount('#app')
    
    
  3. eslint & prettier

    正常安装依赖,编写配置文件即可,这里我没有编写.prettierrc.js,用的默认配置。

    # use npm or yarn or pnpm
    npm install -D eslint prettier eslint-config-prettier eslint-plugin-prettier eslint-plugin-vue @typescript-eslint/parser vue-eslint-parser
    
    // .eslintrc.js
    module.exports = {
      root: true,
      env: {
        node: true,
        // The Follow config only works with eslint-plugin-vue v8.0.0+
        "vue/setup-compiler-macros": true,
      },
      extends: [
        "plugin:vue/vue3-essential",
        "eslint:recommended",
        "@vue/typescript/recommended",
        "plugin:prettier/recommended",
      ],
      plugins: ["vue"],
      parser: "vue-eslint-parser",
      parserOptions: {
        parser: "@typescript-eslint/parser",
        ecmaVersion: 2020, 
        sourceType: "module",
      },
      rules: {
        "no-console": import.meta.env.NODE_ENV === "production" ? "warn" : "off",
        "no-debugger": import.meta.env.NODE_ENV === "production" ? "warn" : "off",
        "prettier/prettier": ["error", { endOfLine: "auto" }],
        "no-unreachable": "warn",
        "vue/no-unused-vars": "warn",
        "@typescript-eslint/no-var-requires": "off",
      },
      globals: {},
    };
    
  4. css预处理器

    由于vite内置了关于css预处理的支持,因此只需安装相应的预处理器依赖即可,以less为例:

    # use npm or yarn or pnpm
    npm install -D less
    
2. electron集成
1. 集成

注:由于package.json中的dependencies是在node生产环境运行时需要的,而electron是完全游离在node之外的,因此不需要生产环境的依赖,安装在devDependecies下即可。

安装electron用默认源可能会比较耗时,建议用nrm换源到taobao:

# use npm or yarn or pnpm
npm install -g nrm

nrm use taobao

然后安装electron:

# use npm or yarn or pnpm
npm install -D electron

若不切换taobao源基本会在此处报错ETIMEOUT,解决方法如下:

# use npm or yarn or pnpm
npm config set ELECTRON_MIRROR https://npmmirror.com/mirrors/electron/

# 或项目根目录新建.npmrc文件,并在其中添加
electron_mirror=https://npmmirror.com/mirrors/electron/

此时已经可以编写electron程序了,让我们写一个main.js创建electron主进程:

// electron/main.js
import { app, BrowserWindow } from "electron";
const path = require('path');

const createWindow = () => {
  const win = new BrowserWindow({
    webPreferences: {
      contextIsolation: false, // 是否开启隔离上下文
      nodeIntegration: true, // 渲染进程使用Node API
      preload: path.join(__dirname, "./preload.ts"), // 需要引用js文件
    },
  });


  // 如果打包了,渲染index.html
  if (app.isPackaged) {
    win.loadFile(path.join(__dirname, "../dist/index.html"));
  } else {
    win.loadURL("http://127.0.0.1:5173");
  }
};


app.whenReady().then(() => {
  createWindow(); // 创建窗口
  app.on("activate", () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});


// 关闭窗口
app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
});

然后在package.json中指定electron程序的主进程入口,并添加electron启动脚本:

// package.json
{
  "name": "electron-vue-demo",
  "private": true,
  "version": "0.0.0",
  "type": "commonjs", // 注意,这里必须被指定为commonjs,否则打包时无法解析node的commonjs模块
  "author": "cyanAir",
  "main": "./electron/main.js", // 主进程入口
    "scripts": {
    "dev": "vite --host",
    "build": "vue-tsc && vite build",
    "dev:electron": "./electron/main.js"
  },
  ...
}

此时先运行npm run dev然后运行npm run dev:electron就可以看见已经启动了electron项目,如下:

image-20230116151658920

2. 启动流程优化

由于每次启动都需要先启动vue再启动electron有点麻烦,而且由于vue项目默认ts编译不产生产物的问题,无法通过使用ts编写electron主进程相关的代码。此时需要引入vite-plugin-electron插件:

# use npm or yarn or pnpm
npm install -D vite-plugin-electron

之后在vite.config.ts中引入:

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

import electron from "vite-plugin-electron";
import * as path from "path";

export default defineConfig({
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "src"),
      "#": path.resolve(__dirname, "electron"),
    }
  },
  plugins: [
    vue(),
    electron({
      entry: ["electron/main.ts", "electron/preload.ts"], // electron主进程调用入口文件、preload文件
      vite: {
        build: {
          rollupOptions: {
            // c/c++链接库
            external: [],
          },
        },
      },
    }),
  ],
  build: {
    emptyOutDir: true, // 默认情况下,若 outDir 在 root 目录下,则 Vite 会在构建时清空该目录
  },
});

而后将electron/mian.js改为main.ts,之后main.ts会在electron运行前编译,编译产物会出现在dist-electron/目录下,因此同时需要更改package.json中的main参数,如下:

// package.json
{
    ...
    "main": "./dist-electron/main.js", // 主进程入口
    ...
}

此后只需要运行npm run dev,就会自动运行electron。

⭐注意:上方使用的插件版本为v0.11.2,由于这个commit,若希望使用v0.15.x及之后的版本,需要修改使用方式为如下形式:

plugins: [
 vue(),
 electron([
   { entry: ["electron/preload.ts"] },
   {
     entry: ["electron/main.ts"], // electron主进程调用入口文件
     vite: {
       build: {
         rollupOptions: {
           // c/c++链接库
           external: [],
         },
       },
     },
   },
 ]),
],
3. 开发环境web devtools引入

web开发过程中,通常需要通过devtools进行调试,因此在electron应用中很有必要引入devtools,先安装electron-devtools-installer

# use npm or yarn or pnpm
# 取决于生产环境是否需要使用,安装在dep还是devDep下需要自己决定
npm install -D electron-devtools-installer

然后在electron/main.ts中引入:

// electron/main.ts
import installExtension, { VUEJS3_DEVTOOLS } from "electron-devtools-installer";
const isDevelopment = !import.meta.env.PROD; // 环境
...

app.whenReady().then(() => {
  createWindow(); // 创建窗口
  if (isDevelopment) {
    // Install Vue Devtools
    try {
      await installExtension(VUEJS3_DEVTOOLS);
    } catch (e: any) {
      console.error("Vue Devtools failed to install:", e.toString());
    }
  }
  win.webContents.openDevTools({ mode: "right", activate: true }); // 默认在右侧打开开发工具
  app.on("activate", () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

此后可以通过在electron应用中ctrl + shift + i打开/关闭开发工具。

4. 应用打包

electron应用打包官方推荐的是elelctron-forge,我当时没有用的这个包,用的electron-builder,因此下面我只会讲electron-builder的打包。

首先需要安装electron-builder

# use npm or yarn or pnpm
npm install -D electron-builder

然后在package.json中添加build参数,具体参数见electron-builder官网

// package.json
{
  ...
  "build": {
    "appId": "com.my-website.my-app", // 应用id,两个如果相同的话,安装会覆盖
    "productName": "MyApp", // 应用名,打包后的应用名称
    "copyright": "Copyright © 2022 ${author}", // 著作权
    "asar": true, // 是否使用asar归档文件,详情可以看https://www.electronjs.org/zh/docs/latest/tutorial/asar-archives
    "npmRebuild": false, // 是否每次打包时都用npm rebuild重新构建node模块生成对应的动态链接库
    "extraResources": [  // 纯静态资源(不经历build过程,单纯拷贝,类似webpack中的copy-webpack-plugin)
      {
        "from": "config.conf",
        "to": "../"
      },
    ],
    "directories": {
      "buildResources": "assets", // 静态资源路径
      "output": "release/${version}" // 输出地址
    },
    "files": [	// 需要进行打包的文件/目录
      "dist/**/*",
      "dist-electron/**/*"
    ],
    "win": { // windows打包相关配置
      "target": [ // 打包产物
        {
          "target": "nsis", // 使用的安装包程序
          "arch": [ // 架构
            "x64"
          ]
        }
      ],
      "artifactName": "${productName}_${version}.${ext}" // 安装包名称
    },
    "nsis": { // nsis安装包程序配置
      "oneClick": false, // 是否一键安装
      "language": "2052", // 默认语言,2052-简体中文
      "perMachine": false, // 是否默认为PC上的每一个用户安装
      "allowToChangeInstallationDirectory": true, // 是否允许自定义安装目录
      "deleteAppDataOnUninstall": false // 卸载时是否删除AppData目录下的文件
    }
  },
}

然后再添加electron-builder打包的脚本:

{
  ...
  "script": {
      ...
      "electron:build": "vue-tsc && vite build && electron-builder",
  },
  ...
}

此时若直接运行,会报下面的错:

image-20230116172135380

这是由于vue-tsc进行ts检查时发现有模块不能生成js产物导致的,在tsconfig.json中添加:

// tsconfig.json
{
  "compilerOptions": {
    ...
    "noEmit": true,
  },
  ...
}

在这之后就可以进行打包了,打包后的产物如图:

image-20230116172552959

其中win-unpacked/为打包后的可执行程序,MyApp_0.0.0.exe为安装包。

此时打开win-unpacked/MyApp.exe(或安装后打开应用程序),应该可以正常看到应用画面。

5. 应用日志

在打包后,我们的浏览器控制台一般情况下是无法打开的,此时如果发生了错误,我们就需要应用日志帮助我们定位问题所在。

首先安装electron-log

npm install electron-log

然后在需要的地方引入(以应用入口为例,一般应用入口会有一个全局错误处理的方法):

const log = require("electron-log");

...

// 异常捕获,避免异常闪退
process.on("uncaughtException", (e) => {
  log.error(e);
});

其中log文件默认路径为:

%USERPROFILE%\AppData\%PROJECT_NAME%\logs\main.log	// Windows
~/.config/{app name}/logs/main.log	// Linux
~/Library/Logs/{app name}/main.log	// MacOS

如果希望修改可以通过log.transports.file.resolvePath = () => PATHlog.transports.fileName来修改,其中reslovePath的定义如下:

resolvePathFn: (variables: PathVariables, message?: LogMessage) => string;

electron-log的具体用法详见其npm包

6. 路径引用

electron中文件路径与普通的vue项目略有不同,下面进行说明。

  1. vue项目中文件之间的相互引用不需要做任何修改,因为vue部分在vite打包之后会被作为一个整体放入asar归档文件中,因此可以正常引用。

  2. electron及node后端部分若需要进行文件读取等操作,有三种定义的路径(MacOS、Linux类似):

    __dirname  =>  %USERPROFILE%\AppData\Local\Programs\MyApp\resources\app.asar\%FILE_DIRNAME%
    app.getAppPath()  =>  %USERPROFILE%\AppData\Local\Programs\MyApp\resources\app.asar
    process.cwd()  =>  exe程序文件所在的目录
    
7. 非node原生模块的构建

未完待续