Electron9+vue-cli3开发跨平台桌面应用

5,448 阅读17分钟

前言

本文参考了:zhuanlan.zhihu.com/p/75764907 这篇文章的大纲脉络

本文一步一步教你如何使用 Electron9 和 vue-cli3,在完全保留vue开发web应用的习惯下,搭建桌面应用。

本文不涉及Electron和vue的开发教程,仅以实现两者结合为目的,如深入学习Electron和vue,请访问官方:

Electron: electronjs.org/

vue: cn.vuejs.org/

vue-cli: cli.vuejs.org/zh/

sass: www.sass.hk

学习本文前,你最好熟悉以下技能:

HTML、CSS、JavaScript vue2.x sass

electron 基础

※注:本文代码区域每行开头的“+”表示新增,“-”表示删除,“M”表示修改;代码中的“...”表示省略。

先看看通过本教程能学到什么:

目录

1 创建项目

1.1 使用cnpm加速下载

1.2 为什么不使用SimulatedGREG/electron-vue

1.3 安装/升级vue-cli3

1.4 创建vue项目

1.5 自动安装electron

1.6 手动安装electron

1.7 编译并启动APP

2 配置项目

2.1 配置ESLint代码格式检查工具

2.2 配置 prettier 插件格式化配置文件

2.3 配置vue

3 项目基本设定

3.1 主进程和渲染进程简介

3.2 APP窗口大小

3.3 取消跨域限制

3.4 取消顶部菜单栏

3.5 设置APP窗口图标

3.6 设置APP窗口标题栏名称

4 build最终产品

4.1 设置APP及安装包图标

4.2 打包APP

4.3 打包时可能碰到的问题

4.4 打包的更多配置

5 一些配置及问题

5.1 在渲染进程使用 node 模块

5.2 取消外边框

5.3 手动安装 vue-devTools

5.4 注册快捷键打开devTools

5.5 设置窗口拖动区域

5.6 background.js 代码 分析

5.7 渲染进程调用主进程模块

5.8 限制只能创建一个程序实例进程

5.9 主进程及渲染进程崩溃时的处理

6 移植vue项目到electron-vue-demo中

1 创建项目

1.1 使用cnpm加速下载

国内直接使用 npm 的官方镜像是非常慢的,这里推荐使用淘宝 NPM 镜像。

使用淘宝定制的 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm

npm install -g cnpm --registry=https://registry.npm.taobao.org

这样就可以使用 cnpm 命令来安装模块了

cnpm install [name]

设置它后所有的npm install都会默认从淘宝服务器下载,通过npm config list查看是否设置成功,成功后metrics-registry的值会修改成淘宝服务器地址

npm config set registry  https://registry.npm.taobao.org

切换回默认地址下载,这样运行 npm install [name] 时就不会从淘宝镜像去下载了

npm config set registry https://registry.npmjs.org/

1.2 为什么不使用SimulatedGREG/electron-vue

SimulatedGREG/electron-vue已经很久没有更新了,而且其生成的工程结构并不是vue-cli3。所以放弃使用

1.3 安装/升级vue-cli3

写这篇文章时 我的 vue-cli3 版本是 3.12.1

执行这个命令检查 vue-cli 版本

vue --version

如果版本低于3,先卸载

cnpm uninstall vue-cli -g

再安装 vue-cli 3,这里指定版本和我目前的一致,如果不指定直接 npm install -g @vue/cli 会下载最新的 vue-cli4,里面的配置和 3 有差异,目前暂未研究过。如果读者本来就用的 vue-cli4 ,略过即可。

npm install -g @vue/cli@3.12.1

vue-cli 官方链接:cli.vuejs.org/zh/

1.4 创建vue项目

找个喜欢的目录,执行以下命令,创建vue项目:

(这里把项目名称定为electron-vue-demo)

vue create electron-vue-demo

会出现以下选项(如果熟悉此步骤可跳过本节内容):

Vue CLI v3.12.1
? Please pick a preset: (Use arrow keys)
> default (babel, eslint)
  Manually select features  

选择“Manually select features” (自定义安装)。

? Please pick a preset: Manually select features
? Check the features needed for your project:
 (*) Babel
 ( ) TypeScript
 ( ) Progressive Web App (PWA) Support
 (*) Router
 (*) Vuex
>(*) CSS Pre-processors
 (*) Linter / Formatter
 ( ) Unit Testing
 ( ) E2E Testing      
? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) n 

选择CSS预处理模块,这里我们使用“Sass/SCSS (with node-sass)”

> Sass/SCSS (with node-sass)

选择ESLint代码格式检查工具的配置,选择“ESLint + Standard config”,标准配置。

❯ ESLint + Standard config 

Line on save表示在保存代码的时候,进行格式检查。

Lint and fix on commit表示在git commit的时候自动纠正格式。

这里只选择“Lint on save”。

>(*) Lint on save

这里问把 babel, postcss, eslint 这些配置文件放哪?

In dedicated config files 表示独立文件

In package.json 表示放在package.json里

这里选择“In package.json”。

 In package.json 

是否为以后的项目保留这些设置?选择“N”。

然后耐心等待项目安装完成。

? Save this as a preset for future projects? (y/N) n  

1.5 自动安装electron

※注:此过程可能需要科学上网,由于直接从国外镜像下载较慢,可能需要等待很漫长的时间。如果你对自己的网速没有超强自信,请跳过本节,前往1.6小节手动安装。

进入到项目根目录,执行:

vue add electron-builder

自动安装依赖这个命令行插件:Vue CLI Plugin Electron Builder,不用自己安装。你只需要在使用Vue-CLI 3或4创建的应用程序的目录中打开一个终端,然后执行命令 vue add electron-builder, 就可以了。

关于 Vue CLI Plugin Electron Builder 可以参考这个文档:nklayman.github.io/vue-cli-plu…

在安装过程中,可能会卡在这一步不动了(我未遇到):

node ./download-chromedriver.js

没关系,我们先强制结束掉。再执行一次vue add electron-builder,然后就可以顺利通过了。

接下来出现配置选项:

? Choose Electron Version (Use arrow keys)
  ^7.0.0
  ^8.0.0
> ^9.0.0

选择Electron版本。选择 “^9.0.0”。

然后耐心等待安装完成。如果中间出现错误中断了,请重复此步骤。

安装完成后会自动在src目录下生成background.js并修改了package.json。

※注:由于网络原因,如果中间出现过中断失败,再次重新安装可能会很快完成,但实际上electron可能并未安装完全。建议完成以上步骤后,直接删除项目根目录的node_modules/,并且执行cnpm install,从国内镜像重新安装所有依赖包。

1.6 手动安装electron

※注:如果已经通过1.5章节的操作,请直接跳过本小节。

修改package.json,添加以下8行:

...
"scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
+    "electron:build": "vue-cli-service electron:build",
+   "electron:serve": "vue-cli-service electron:serve",
+    "postinstall": "electron-builder install-app-deps",
+    "postuninstall": "electron-builder install-app-deps"
  },
+  "main": "background.js",
  "dependencies": {
    "core-js": "^2.6.5",
    "vue": "^2.6.10",
    "vue-router": "^3.0.3",
    "vuex": "^3.0.1"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "^3.12.0",
    "@vue/cli-plugin-eslint": "^3.12.0",
    "@vue/cli-service": "^3.12.0",
    "@vue/eslint-config-standard": "^4.0.0",
    "babel-eslint": "^10.0.1",
 +  "electron": "^9.0.0",
 +  "electron-devtools-installer": "^3.1.0",
    "eslint": "^5.16.0",
    "eslint-plugin-vue": "^5.0.0",
    "node-sass": "^4.12.0",
    "sass-loader": "^8.0.0",
 + "vue-cli-plugin-electron-builder": "^2.0.0-rc.5",
    "vue-template-compiler": "^2.6.10"
  },
 ...

新建src/background.js

在src目录下新建background.js,复制以下代码:

'use strict'

import { app, protocol, BrowserWindow } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
const isDevelopment = process.env.NODE_ENV !== 'production'

// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
  { scheme: 'app', privileges: { secure: true, standard: true } }
])

let win = null

async function createWindow() {
  // Create the browser window.
  win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      // Use pluginOptions.nodeIntegration, leave this alone
      // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
      nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION
    }
  })

  if (process.env.WEBPACK_DEV_SERVER_URL) {
    // Load the url of the dev server if in development mode
    await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
    if (!process.env.IS_TEST) win.webContents.openDevTools()
  } else {
    createProtocol('app')
    // Load the index.html when not in development
    win.loadURL('app://./index.html')
  }
  
}

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (BrowserWindow.getAllWindows().length === 0) createWindow()
})

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
  if (isDevelopment && !process.env.IS_TEST) {
    // Install Vue Devtools
    try {
      await installExtension(VUEJS_DEVTOOLS)
    } catch (e) {
      console.error('Vue Devtools failed to install:', e.toString())
    }
  }
  createWindow()
})

// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
  if (process.platform === 'win32') {
    process.on('message', (data) => {
      if (data === 'graceful-exit') {
        app.quit()
      }
    })
  } else {
    process.on('SIGTERM', () => {
      app.quit()
    })
  }
}

以上代码是1.5小节使用自动化方式安装后生成的。

安装依赖包

在项目根目录执行,安装全部依赖包:

cnpm install

如果安装过程中报错:Error: post install error, please remove node_modules before retry!可以忽略,不影响后续使用。(这个我没有遇到)

1.7 编译并启动APP

执行以下命令,开始编译APP,并启动开发环境APP:

npm run electron:serve

首次启动可能会等待很久,出现以下信息:

Failed to fetch extension, trying 4 more times
Failed to fetch extension, trying 3 more times
Failed to fetch extension, trying 2 more times
Failed to fetch extension, trying 1 more times
Failed to fetch extension, trying 0 more times
Vue Devtools failed to install: Error: net::ERR_CONNECTION_TIMED_OUT

这是因为在请求安装vuejs devtools插件。需要科学上网才能安装成功。如果不能科学上网也没关系,耐心等待5次请求失败后会自动跳过。

编译成功后,就会出现开发环境的APP了。

因为下载不下来,并且每次保存文件热更新时都会尝试重新下载,所以这里将对应的代码注释,然后去vue官网手动下载 vue-devtools再安装,具体参照 5.3

// 网络原因插件下载会失败
// import installExtension, { VUEJS_DEVTOOLS } from "electron-devtools-installer";
...
// 下方相应的方法也删除

2 配置项目

2.1 配置ESLint代码格式检查工具

ESlint可以高效的检查代码格式,让参与项目的所有工程师都能保持统一的代码风格。其检测精度甚至可以精确到是否多一个空格或者少一个空格。代码格式的统一对提高团队的协同开发效率有很大的帮助,特别是对有代码洁癖的工程师。

在项目根目录下创建 .eslintrc.js

注意文件名前面有个“.”,当 package.json 中也配置了 eslintConfig 时,.eslintrc.js 会覆盖 package.json 中的配置,这里可以将 package.json 中的 eslintConfig 配置删除

请粘贴以下代码:

module.exports = {
  root: true,
  env: {
    node: true,
  },
  extends: ['plugin:vue/essential', '@vue/standard'],
  rules: {
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    // 不检测语句末尾的分号
    semi: ['off', 'always'],
    // 强制缩进为2个空格
    indent: ['error', 2],
    // 关闭函数名称跟括号之间的空格检测
    'space-before-function-paren': 0,
    // 忽略大括号内的空格
    'object-curly-spacing': 0,
    // 当最后一个元素或属性与闭括号 ] 或 } 在 不同的行时,允许(但不要求)使用拖尾逗号;当在 同一行时,禁止使用拖尾逗号。
    'comma-dangle': ['error', 'only-multiline'],
    // 要求使用骆驼拼写法
    camelcase: [
      'error',
      {
        properties: 'never', // 不检查属性名称
        ignoreDestructuring: true, // 不检查解构标识符
      },
    ],
  },
  parserOptions: {
    parser: 'babel-eslint',
  },
}

以上是我常用的 .eslintrc.js 配置。如果你有更多的配置需求,可参阅:cn.eslint.org/docs/rules

格式化的配置文件默认为 .editorconfig ,配置如下:

[*.{js,jsx,ts,tsx,vue}]
indent_style = space   <--这里定义缩进类型是空格还是tab
indent_size = 2        <--这里需要与.eslintrc.js的indent对应
trim_trailing_whitespace = true
insert_final_newline = true

.editorconfig 作用:

格式化插件 prettier 会优先读取项目中的配置文件,但是在 .editorconfig 中无法配置行末不加分号,所以我这里选用 .prettierrc 配置文件来配置,作用和.editorconfig 文件一致

2.2 配置 prettier 插件格式化配置文件

我用的格式化插件是prettier,如果读者不是用的这款,请根据官方文档自行配置。删除.editorconfig,新建 .prettierrc

.prettierrc:

{
  trailingComma: "es5", // ' es5': 在ES5中有效的尾随逗号(对象,数组等) 'all': 尾随逗号 尽可能(函数参数)
  tabWidth: 2,   // 每个制表符占用的空格数
  semi: false,  // 是否在每行末尾添加分号
  singleQuote: true, // 是否使用单引号
  printWidth:150 // 默认 80,达到一定打印宽度使其标签属性换行显示,属性会看得更清晰,不想要换行效果,设置大一点就行了
};

printWidth 设置后的效果:

image.png

以上是我常用的 .prettierrc 配置。如果你有更多的配置需求,可参阅:prettier.io/docs/en/con…

.editorconfig、.prettierrc 用于IDE(我用的 vscode)自动格式化代码 .eslintrc.js 用于ESlint检测

如果实在用不惯eslint,可以关闭它:

// vue.config.js
lintOnSave: false

2.3 配置vue

在项目根目录下创建vue.config.js,粘贴以下代码:

const path = require('path');

function resolve (dir) {
  return path.join(__dirname, dir);
}

module.exports = {
  publicPath: './',
  devServer: {
    // can be overwritten by process.env.HOST
    host: '0.0.0.0',  
    port: 8080
  },
  chainWebpack: config => {
    config.resolve.alias
      .set('@', resolve('src'))
      .set('src', resolve('src'))
      .set('common', resolve('src/common'))
      .set('components', resolve('src/components'));
  }
};

devServer 用于设置开发环境的服务,这里表示在本地8080端口启动web服务。

chainWebpack 我们给项目目录起了“别名(alias)”,在代码中,我们可以直接用“别名”访问资源,省去了每次输入完整相对路径的麻烦。

※注: ◉ 在js代码中可直接使用别名,例如: @/common/js/xxx.js 等价于 src/common/js/xxx.js common/js/xxx.js 等价于 src/common/js/xxx.js ◉ 在css或者html中使用别名,需要在别名前加“~”,例如: @import "~common/stylus/font.styl";

3 项目基本设定

3.1 主进程和渲染进程简介

在开始下面的步骤之前,很有必要简单了解下Electron的应用架构。

主进程

Electron 运行 package.json 的 main 脚本(background.js)的进程被称为主进程。 在主进程中运行的脚本通过创建web页面来展示用户界面。 一个 Electron 应用总是有且只有一个主进程。

渲染进程

由于 Electron 使用了 Chromium 来展示 web 页面,所以 Chromium 的多进程架构也被使用到。 每个 Electron 中的 web 页面运行在它自己的渲染进程中。

在普通的浏览器中,web页面通常在一个沙盒环境中运行,不被允许去接触原生的资源。 然而 Electron 的用户在 Node.js 的 API 支持下可以在页面中和操作系统进行一些底层交互。

主进程与渲染进程的关系

主进程使用 BrowserWindow 实例创建页面。 每个 BrowserWindow 实例都在自己的渲染进程里运行页面。 当一个 BrowserWindow 实例被销毁后,相应的渲染进程也会被终止。

主进程管理所有的web页面和它们对应的渲染进程。 每个渲染进程都是独立的,它只关心它所运行的 web 页面。

具体可参阅官方文档: electronjs.org/docs/tutori…

3.2 APP窗口大小

修改background.js:

const win = new BrowserWindow({
M    width: 1200,
M    height: 620,
    webPreferences: {
      // Use pluginOptions.nodeIntegration, leave this alone
      // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
      nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
    }
  })

设置全屏

代码如下,我用了 setFullScreen 方法,因为我的项目需要完全全屏。

  const win = new BrowserWindow({
    width: 1200,
    height: 620,
    webPreferences: {
      // Use pluginOptions.nodeIntegration, leave this alone
      // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
      nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
 +    fullscreen: true,
 +    skipTaskbar: false,
 +    fullscreenable: true,
 +    simpleFullscreentrue,
    },
      
 +    // 经在 win10 系统测试,此方法全屏时不能挡住下方任务栏
 +    // win.maximize()	
 +    // 此方法可以挡住下方任务栏
 +    win.setFullScreen(true)
  });

全屏后设置的宽高就失效了

3.3 取消跨域限制

修改background.js:

const win = new BrowserWindow({
    width: 1200,
    height: 620,
    webPreferences: {
      // Use pluginOptions.nodeIntegration, leave this alone
      // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
      nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
+     webSecurity: false,
    }
  })

官方建议:在渲染进程(BrowserWindowBrowserView 和 <webview>)中禁用 webSecurity 将导致至关重要的安全性功能被关闭。不要在生产环境中禁用webSecurity

原因:禁用 webSecurity 将会禁止同源策略并且将 allowRunningInsecureContent 属性置 true。 换句话说,这将使得来自其他站点的非安全代码被执行。

3.4 取消顶部菜单栏

  // 去掉窗口顶部菜单
  win.setMenu(null);

3.5 设置APP窗口图标(我用的win10系统,未生效,打包后才生效)

准备windows和macOS两版图标。

windows: app.ico 最小尺寸:256x256 macOS: app.png或app.icns 最小尺寸:512x512 (后续4.1章节用到)

把图标文件放到public/目录下,项目结构如下:

|- /dist_electron
  (略)
|- /public
   |- app.icns  <-- 本教程暂时未使用icns
   |- app.ico
   |- app.png
   |- favicon.ico
   |- index.html
|- /src
  (略)

可以顺便把favicon.ico也修改一下,但是在桌面版APP上是用不到的。如果以后生成纯web项目才会用到。

修改background.js,让APP窗口应用图标:

  ...
+  const path = require('path')
  ...
  const win = new BrowserWindow({
    width: 1200,
    height: 620,
+   icon: path.join(__static, 'app.ico'), // 任务栏图标 及 任务栏预览图标,注意图标放在public目录中
    webPreferences: {
      // Use pluginOptions.nodeIntegration, leave this alone
      // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
      nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
      webSecurity: false,
    }
  });
  ...

这里的${__static}对应的是public目录

3.6 设置APP窗口标题栏名称

修改public/index.html 中的 electron-vue-demo 即可(我的项目中已经取消了外边框,是看不到这个标题的)

4 build最终产品

这里我们已经集成了electron-builder工具,官方文档可以参阅:github.com/electron-us…

4.1 设置APP名称及图标

修改vue.config.js, 添加下列代码:

  pluginOptions: {
    electronBuilder: {
      nodeIntegration: true,  // 渲染进程可以使用node模块
      builderOptions: {
        win: {
          icon: "./public/app.ico"
        },
        mac: {
          icon: "./public/app.png"
        },
        productName: "AppDemo"
      }
    }
  }

这里配置了打包出来的应用图标,安装到桌面的应用名称

4.2 打包APP

执行以下命令,可以build工程:

npm run electron:build

最终在dist_electron目录下生成build后的产品。

4.3 打包时可能碰到的问题

因为网络原因,图中的这两个文件中的内容可能下载不下来,根据命令行中对应的文件路径去github下载,然后放到文件夹内即可

这里提供一个打包文件的下载链接,可能和你的版本不对,以命令行工具中的下载链接为准

链接:pan.baidu.com/s/15V7eUTSl…

提取码:gl03

放置后是文件夹内部是这样的:

有两个版本是因为我之前用过electron-vue 创建过项目,那个依赖的版本比较低

4.4 打包的更多配置

这里进一步做了一些配置,让打出来的包可以自定义安装路径等,更多配置参考文档:

参考文档:www.electron.build/configurati…

vue.config.js 中:

  ...
  // 引入package.json 以获取文件中的项目信息
  const PACKAGE = require('./package.json')
  let config = {
      productName: PACKAGE.name,
      version: PACKAGE.version,
  }
  ...
  pluginOptions: {
    electronBuilder: {
      nodeIntegration: true, // 渲染进程可以使用node模块
      builderOptions: {
        asar: false,  
        productName: `${config.productName}`, // 项目名,这也是生成的exe文件的前缀名
        appId: 'com.electron.template', // 应用程序id
        copyright: 'Copyright © template',  // 应用程序版权行
        directories: {
          output: 'dist_electron',  // 打包输出的目录,相对于项目根路径
        },
        win: {
          legalTrademarks: 'Copyright © template',  // 商标注册
          publisherName: 'electron',  // 发行者名称,与代码签名证书中的名称完全相同
          requestedExecutionLevel: 'highestAvailable',  // 应用程序请求执行的安全级别。
          target: [
            {
              target: 'nsis', // 目标包类型
              arch: ['ia32'], // 属于X86体系结bai构的du32位版本
            },
          ],
          // 没有配置 nsis 的时候的安装包名,此配置项会被 nsis 覆盖
          // artifactName: `${config.productName}.exe`,  
          icon: './public/app.ico', // 图标路径,安装包和免安装程序都会应用
        },
        mac: {
          identity: 'com.electron.templat',
          target: ['dmg'],
          // artifactName: `${config.productName}.dmg`,
          icon: './public/app.png',
        },
        dmg: {
          title: `${config.productName}`,
          // artifactName: `${config.productName}.dmg`,
          icon: './public/app.png',
        },
        nsis: {
          // 创建一键安装程序还是辅助安装程序
          oneClick: false,
          // 是否允许用户更改安装目录。
          allowToChangeInstallationDirectory: true,
          // 是否为辅助安装程序显示安装模式安装程序页(选择每台计算机或每用户)。或者是否总是按所有用户(每台机器)安装
          perMachine: true,
          // 允许申请提升。如果为false,用户将不得不以提升的权限重启安装程序
          allowElevation: true,
          // 打出的安装包名称,默认 ${productName} Setup ${version}.${ext},${productName} 对应productName 或 package.json 中的name, ${version}对应 package.json 中的 version 
          artifactName: `${config.productName}-安装包-V${config.version}.exe`,
          // 完成后是否运行已安装的应用程序。对于辅助安装程序,相应的复选框将被删除
          runAfterFinish: true,
          // 用于所有快捷方式的名称。默认为应用程序名称,即 productName 的值,如果 productName 没有设置,则默认是 package.json 中的 name,如果name 也没有设置,将报错
          shortcutName: `${config.productName}`,
        },
      },
    },
  },

5 相关配置及坑(持续更新中)

5.1 在渲染进程使用 node 模块

// vue.config.js
module.exports = {
  pluginOptions: {
    electronBuilder: {
      nodeIntegration: true
    }
  }
}

在vue.config.js 中设置后 process.env.ELECTRON_NODE_INTEGRATION 的值就为true了, 对应 background.js 中的

nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,

5.2 取消外边框

  const win = new BrowserWindow({
    width: 1200,
    height: 620,
    webPreferences: {
      nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
      webSecurity: false,
      // eslint-disable-next-line no-undef
      icon: path.join(__static, 'icon.png'),
      
    },
 +   frame: false,
  });

更多参考:www.bookstack.cn/read/electr…

5.3 手动安装 vue-devTools

因为网络原因自动下载失败,这里先将 vue-devTools 手动下载后放到 dist_electron 文件夹中,因为 __dirname 以它为基准路径。

vue-devTools 下载地址:github.com/vuejs/vue-d…

用 session.defaultSession.loadExtension 方法加载插件,注意判断在开发环境中才在加载

app.on("ready", async () => {
  // 开发环境下才加载vue-devtools
  if (isDevelopment && !process.env.IS_TEST) {
    // Install Vue Devtools
    loadVueDevTools()
  }
  createWindow();
});

// 加载 vue 开发者工具
function loadVueDevTools() {
  session.defaultSession
    .loadExtension(path.join(__dirname, "./devTools/vue-devtools"))
    .then(res => {
      console.log("Vue Devtools loaded successfully");
    })
    .catch(err => {
      console.error("Vue Devtools failed to install:", err.toString());
    });
}

5.4 注册快捷键打开devTools

设置成功后就可以通过 CommandOrControl+Shift+i 来切换打开开发者模式了

在windows下,按Ctrl+Shift+i 即可切换 devTools

在macOS下,按Commond+Shift+i 即可切换 devTools

因为 F12 是系统保留快捷键,所以这里不用 F12 注册

app.on("ready", async () => {
  // 开发环境下才加载vue-devtools
  if (isDevelopment && !process.env.IS_TEST) {
    // Install Vue Devtools
    loadVueDevTools()
    registerToggleDevTools()
  }
  createWindow();
});

// 注册快捷键切换打开开发者工具
function registerToggleDevTools() {
   globalShortcut.register("CommandOrControl+Shift+i", function() {
    BrowserWindow.getFocusedWindow().webContents.toggleDevTools();
  });
}

5.5 设置窗口拖动区域

在指定元素上设置这个属性后,点击就能拖动窗口了

-webkit-app-region: drag;

在可拖拽区域内部使用 -webkit-app-region: no-drag 则可以将其中部分区域排除。 请注意, 当前只支持矩形形状

文本选择

在无框窗口中, 拖动行为可能与选择文本冲突。 例如, 当您拖动标题栏时, 您可能会意外地选择标题栏上的文本。 为防止此操作, 您需要在可区域中禁用文本选择, 如下所选:

.titlebar {
  -webkit-user-select: none;
  -webkit-app-region: drag;
}

5.6 background.js 代码 分析

5.6.1 变量

  • process.env.NODE_ENV

    开发环境为 development

    生产环境为 production

  • process.env.ELECTRON_NODE_INTEGRATION

    对应 vue.config.js 中的 nodeIntegration 属性

  pluginOptions: {
    electronBuilder: {
      nodeIntegration: true,  // 渲染进程可以使用node模块
      builderOptions: {
        win: {
          icon: "./public/app.ico"
        },
        mac: {
          icon: "./public/app.png"
        },
        productName: "AppDemo"
      }
    }
  }
  • process.env.WEBPACK_DEV_SERVER_URL

    对应本地启动服务的url地址,我的是:http://localhost:8080

  • __static

    对应 项目的 public 目录的绝对路径

5.6.2 方法

  • createProtocol("app")

    来自 import { createProtocol } from "vue-cli-plugin-electron-builder/lib"; 如下:

export default scheme => {
  protocol.registerBufferProtocol(
    scheme,
    (request, respond) => {
      let pathName = new URL(request.url).pathname
      pathName = decodeURI(pathName) // Needed in case URL contains spaces

      readFile(path.join(__dirname, pathName), (error, data) => {
        if (error) {
          console.error(`Failed to read ${pathName} on ${scheme} protocol`, error)
        }
        const extension = path.extname(pathName).toLowerCase()
        let mimeType = ''

        if (extension === '.js') {
          mimeType = 'text/javascript'
        } else if (extension === '.html') {
          mimeType = 'text/html'
        } else if (extension === '.css') {
          mimeType = 'text/css'
        } else if (extension === '.svg' || extension === '.svgz') {
          mimeType = 'image/svg+xml'
        } else if (extension === '.json') {
          mimeType = 'application/json'
        }

        respond({ mimeType, data })
      })
    },
    error => {
      if (error) {
        console.error(`Failed to register ${scheme} protocol`, error)
      }
    }
  )
}

关于 protocal 模块,暂未理解~

5.7 渲染进程调用主进程模块

5.7.1 remote 模块介绍

remote 模块提供了一种在渲染进程(网页)和主进程之间进行进程间通讯(IPC)的简便途 径。Electron中,与GUI相关的模块(如dialog,menu等)只存在于主进程,而不在渲染进程中。为了能从渲染进程中使用它们,需要用ipc模块来给主进程发送进程间消息。使用remote模块,可以调用主进程对象的方法,而无需显式地发送进程间消息。

remote 模块默认是被禁用的,需要在主进程的 BrowserWindow - 通过设置 enableRemoteModule 选项为 true 来开启

const win = new BrowserWindow({
    width: 1200,
    height: 620,
    webPreferences: {
      nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
      webSecurity: false,
      // eslint-disable-next-line no-undef
      icon: path.join(__static, 'icon.png'),
 +    enableRemoteModule: true,
    },
        frame: false,
  });

5.7.2 使用方法示例

示例:从渲染进程创建浏览器窗口

const { BrowserWindow } = require('electron').remote
let win = new BrowserWindow({ width: 800, height: 600 })
win.loadURL('https://github.com')

5.8 限制只能创建一个程序实例进程

更新于:2022-05-23

需求:最近项目中提出一个需求,要求只能创建一个应用实例进程,再次双击快捷图标打开应用时,聚焦到第一个应用实例窗口,而不是重新创建一个应用实例。 将 1.6节 中的代码改写一下即可:

原代码:

app.on('ready', async () => { 
    if (isDevelopment && !process.env.IS_TEST) { 
    try { 
        await installExtension(VUEJS_DEVTOOLS)
    }
    catch (e) { 
        console.error('Vue Devtools failed to install:', e.toString()) 
    } 
    }
    createWindow() 
})

原理:启动第二个应用实例时如果已经存在了程序主窗口,恢复显示第一个主窗口即可。参数详情请参阅官方文档。

改写后的代码:

// 程序主窗口
let mainWindow = null
...
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
    app.quit()
} else {

    app.on('second-instance', (event, commandLine, workingDirectory) => {

        // 当运行第二个实例时,将会聚焦到 mainWindow 这个窗口
        if (mainWindow) {
            if (mainWindow.isMinimized()) mainWindow.restore()
            mainWindow.focus()
        }
    })
    
    // 创建 mainWindow, 加载应用的其余部分, etc...
    app.on('ready', async () => {
        if (isDevelopment && !process.env.IS_TEST) { 
    try { 
        await installExtension(VUEJS_DEVTOOLS)
    }
    catch (e) { 
        console.error('Vue Devtools failed to install:', e.toString()) 
    } 
    }
       createWindow() 
    })
    
    // electron 9 以上版本官方文档中用的 app.whenReady().then(() => {...} ) 方法,
    // 替换掉 app.on('ready', async () => {...}) 即可
}

5.9 主进程及渲染进程崩溃时的处理

更新于:2022-05-23

5.9.1 crashReporter 模块

作用:将崩溃日志提交给远程服务器,主进程及渲染进程均可使用

以下是一个设置Electron自动提交崩溃日志到远程服务器的示例:

const { crashReporter } = require('electron')
crashReporter.start({ submitURL: 'https://your-domain.com/url-to-submit' })

构建一个用于接受和处理崩溃日志的服务,你需要以下工程

const { crashReporter } = require('electron')
const { saveLog } = require('./main_tools.js')

crashReporter.start({
    submitURL: 'https://example.com',
    uploadToServer: false, // 崩溃报告不上传,保存到本地
})

// 记录 crashReporter 的日志路径:app.getPath('crashDumps'),方便查找
saveLog(
    `Crash dumps directory: ${app.getPath('crashDumps')}\n\n`,
    'C:/Users/DELL/Desktop'
)

5.9.2 监听到渲染进程崩溃时的重新加载机制

这样当监听到渲染进程崩溃后会执行 reload 方法,从而刷新页面:


const { app,BrowserWindow } = require('electron')
win = new BrowserWindow({...})
const { saveLog } = require('./main_tools.js')

...
    // 渲染器进程意外消失时触发。 这种情况通常因为进程崩溃或被杀死。
    win.webContents.on('render-process-gone', (event, details) => {
        // if (typeof event === 'object') {
        //     event = JSON.stringify(event)
        // }
        if (typeof details === 'object') {
            details = JSON.stringify(details)
        }

        saveLog(
            `接收时间: ${new Date().toLocaleString()}` +
                '\n' +
                'render-process-gone 触发--> details:' +
                details +
                '\n\n',
            'C:/Users/DELL/Desktop'
        )

        if (win.isDestroyed()) {
            app.relaunch()
            app.exit(0)
        } else {
            BrowserWindow.getAllWindows().forEach((w) => {
                if (w.id !== win.id) w.destroy()
            })
            win.webContents.reload()
        }
    })

6 移植vue项目到electron-vue-demo中

我的vue项目是vue-cli3创建的,将整个src 文件夹中的内容移植到 electron-vue-demo 的src 文件夹中,因为我的项目依赖了element-ui 及 axios,下载插件:

 cnpm i -S element-ui axios

然后运行:npm run electron:serve 就已经跑起来了,美滋滋~!