前言
本文参考了: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 设置后的效果:
以上是我常用的 .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,
+ simpleFullscreen: true,
},
+ // 经在 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,
}
})
官方建议:在渲染进程(
BrowserWindow
、BrowserView
和<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下载,然后放到文件夹内即可
这里提供一个打包文件的下载链接,可能和你的版本不对,以命令行工具中的下载链接为准
提取码: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' })
构建一个用于接受和处理崩溃日志的服务,你需要以下工程
- socorro
- mini-breakpad-server 因为我的项目使用环境不能连接外网,所以只把崩溃日志保持到了本地,如果需要上传崩溃日志请参考文档:www.electronjs.org/zh/docs/lat…。只把崩溃日志保存到本地可以这样写:
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 就已经跑起来了,美滋滋~!