Electron集成Vue Cli创建项目

设置镜像

NPM镜像

1
2
npm config set registry https://registry.npm.taobao.org
npm config list

还原默认

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

Electron镜像

查看配置文件的位置

1
npm config list

可以查看到本机的userconfig在哪,即.npmrc文件在哪

比如我的

userconfig C:\Users\Jian.npmrc

打开该文件 添加

1
2
registry=https://registry.npm.taobao.org
electron_mirror="https://npm.taobao.org/mirrors/electron/"

Vue CLi3环境配置

卸载旧版本

1
2
3
npm uninstall vue-cli -g
# OR
yarn global remove vue-cli

安装新版本

1
2
3
npm install -g @vue/cli
# OR
yarn global add @vue/cli

检查其版本是否正确 (3.x)

1
vue --version

electron-builder

新项目添加依赖

1
vue ui

安装插件

vue-cli-plugin-electron-builder

插件官网地址: https://nklayman.github.io/vue-cli-plugin-electron-builder/

选择Electron版本为5.0.0

Electron5.06.0的语法变化不大 选用5.0是因为node-ffi第三方修改版也只能支持到5.0

旧项目添加依赖

开发依赖添加

1
2
3
4
"devDependencies": {
"electron": "5.0.0",
"vue-cli-plugin-electron-builder": "^1.4.0"
}

运行脚本添加

1
2
3
4
5
6
7
"scripts": {
"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",

入口变成了background.js

在src目录下创建background.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
"use strict";

import { app, protocol, BrowserWindow } from "electron";
import {
createProtocol,
installVueDevtools
} from "vue-cli-plugin-electron-builder/lib";
const isDevelopment = process.env.NODE_ENV !== "production";

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;

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

function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
});

if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
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");
}

win.on("closed", () => {
win = null;
});
}

// 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 (win === null) {
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 installVueDevtools();
} 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();
});
}
}

就项目中的Electron中的静态页面建议放在public文件夹中

安装依赖报错

Error: Cannot find module ‘fs/promises’

NodeJS版本太低导致的

从NodeJS 14.0.0开始 暴露为 require('fs/promises')

之前为require("fs").promises

解决方法

升级NodeJS版本

步骤

1
nvm list

使用14以上版本

1
2
3
nvm use 14.21.3

nvm list

常用配置

调试框分离

1
win.webContents.openDevTools({mode:'detach'});

隐藏菜单栏

1
2
import { Menu } from "electron";
Menu.setApplicationMenu(null);

设置标题

窗口加载页面之前用的是窗口的title,加载之后用的是页面的title,所以最好两处都设置。

窗口的标题

1
2
3
4
5
6
7
8
9
10
11
12
const win = new BrowserWindow({
width: 1200,
height: 600,
title: "码客脑图",
webPreferences: {
webviewTag: true,
webSecurity: false,
enableRemoteModule: true,
nodeIntegration: true,
contextIsolation: false,
},
});

页面的标题

1
2
3
4
5
6
7
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>码客脑图</title>
</head>

运行报错

运行

1
npm run electron:serve

报错

INFO Launching Electron…

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

这是因为Devtools的安装需要翻墙

注释掉src/background.js中的以下代码就行了

1
2
3
4
5
6
7
8
if (isDevelopment && !process.env.IS_TEST) {
// Install Vue Devtools
try {
await installVueDevtools();
} catch (e) {
console.error("Vue Devtools failed to install:", e.toString());
}
}

虽然还是会报错 但是不用等它尝试下载那么多次了 不用管这个错误即可

新版本需要注释的是

1
2
3
4
5
6
7
8
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());
}
}

打包配置

我们使用的vue-cli-plugin-electron-builder内部也是用electron-builder打包的,但是配置的位置不能像之前那样配置了

官方:https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/configuration.html

使用vue-cli-plugin-electron-builder的配置

在项目的根目录中的vue.config.js中添加以下配置

项目中没有发现vue.config.js这个文件,这是由于vue-cli3.0是号称0配置的,所以我们自己在根目录下创建一个vue.config.js文件即可。注意!根目录!!!!

配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
module.exports = {
pluginOptions: {
electronBuilder: {
builderOptions: {
appId: "cn.psvmc",
productName: "星火智慧课堂",
icon: "./app.ico",
files: ["**/*", "static/*"],
asar: true,
mac: {
icon: "./app.ico",
target: ["zip", "dmg"]
},
win: {
icon: "./app.ico",
target: ["zip", "nsis"]
},
nsis: {
oneClick: false,
allowElevation: true,
allowToChangeInstallationDirectory: true,
installerIcon: "./app.ico",
uninstallerIcon: "./app.ico",
installerHeaderIcon: "./app.ico",
createDesktopShortcut: true,
createStartMenuShortcut: true,
license: "./LICENSE.txt"
}
}
}
}
};

注意事项

  • 图标的路径是相对于vue.config.js所在目录的相对位置,也就是说图标要放在项目的根目录,不是构建生成后目录的路径。app.ico 至少为 256x256
  • LICENSE.txt文件的编码必须为GBK编码

页面加载方式

之前直接用Electron写的代码,后来要结合Vue Cli3创建的项目,本来想的是直接把Electron的代码放在Vue的public目录中,加载的时候用下面的方式

1
2
3
4
5
6
7
8
9
10
if (process.env.WEBPACK_DEV_SERVER_URL) {
// 开发环境
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL);
pptWindow.loadFile("../public/classtools/ppt/ppt.html");
} else {
// 正式发布
createProtocol("app");
win.loadURL("app://./index.html");
pptWindow.loadURL("app://./classtools/ppt/ppt.html");
}

但是发现开发环境中完全没问题,打包后就各种找不到依赖

所以这种方式是行不通的,只能把Electron中的静态页面用Vue的方式在写一遍

但是问题是Electron中用的Node,包导入导出用的CommonJS规范,而Vue用的是ES6的规范,所以代码也要微改。

原写法

1
exports.mydb = mydb;

改为

1
export default mydb;

加载页面方式改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (process.env.WEBPACK_DEV_SERVER_URL) {
// 根页面
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL);
// 内页
toolbarWindows.loadURL(
process.env.WEBPACK_DEV_SERVER_URL + "#toolbar"
);
} else {
createProtocol("app");
// 根页面
win.loadURL("app://./index.html");
// 内页
toolbarWindows.loadURL("app://./index.html#toolbar");
}

loadFile全部改为了loadURL

加载项目内文件

假如我们的项目下文件的位置为:/public/wordlist.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
readFileToArr(fReadName, callback) {
let fRead = fs.createReadStream(fReadName);
let objReadline = readline.createInterface({
input: fRead
});
let arr = [];
objReadline.on("line", function(line) {
arr.push(line);
});
objReadline.on("close", function() {
callback(arr);
});
}

readFileToArr("public/wordlist.txt",function(arr) {})

这样在开发时我们能访问到,但是打包后它会从程序的根目录中查找/public/wordlist.txt,但是文件的位置并不在此处,我们就要设置文件的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
module.exports = {
pluginOptions: {
electronBuilder: {
builderOptions: {
appId: "cn.psvmc.cidian",
productName: "词典",
icon: "./app.ico",
extraResources: {
from: "./public/wordlist.txt",
to: "../public/wordlist.txt"
},
asar: true,
mac: {
icon: "./app.ico",
target: ["zip", "dmg"]
},
win: {
icon: "./app.ico",
target: ["zip", "nsis"]
},
nsis: {
oneClick: false,
allowElevation: true,
allowToChangeInstallationDirectory: true,
installerIcon: "./app.ico",
uninstallerIcon: "./app.ico",
installerHeaderIcon: "./app.ico",
createDesktopShortcut: true,
createStartMenuShortcut: true,
license: "./LICENSE.txt"
}
}
}
}
};

其中主要配置

1
2
3
4
extraResources: {
from: "./public/wordlist.txt",
to: "../public/wordlist.txt"
}

小贴士

如果是electron-vue ,在vue中使用process.cwd()方法可以读取到打包后的根目录path,不过默认也是从项目根目录算起。

1
2
3
4
5
6
7
8
let file = process.cwd() + 'config.json';
fs.readFile(file, 'utf-8', function (err, data) {
if (err) {
console.log(err)
} else {
console.log(data)
}
})

ffmpeg

调用二进制文件(FFMPEG)

Electron默认是开启asar的,导致二进制文件也被打在了asar文件中,就不能再被调用,所以我们要让二进制文件不被处理。

首先我们要知道程序的打包步骤

webpack打包 => electron-builder打包asar打包 => exe打包

考虑到不同平台需要打入的ffmpeg不同,我们可以在webpack打包过程中筛选使用的文件,过程如下:

如果软件不考虑多平台,那么可以直接下载对应平台的ffmpeg放在项目根目录下的core文件夹中,下面的第一步可以跳过不用配置。

第一步

安装ffmpeg-staticcopy-webpack-plugin

1
2
npm install --save-dev ffmpeg-static
npm install --save-dev copy-webpack-plugin

配置vue.config.js

把对应平台的文件放在项目根目录的core文件夹中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const path = require("path");
const CopyWebpackPlugin = require("copy-webpack-plugin");
module.exports = {
publicPath: process.env.NODE_ENV === "production" ? "./" : "/",
outputDir: "webapp",
assetsDir: "assets",
filenameHashing: false,
lintOnSave: true,
productionSourceMap: false,
configureWebpack: config => {
const plugins = [];
// 打包不同平台的 ffmpeg 到 app
const ffmpegBasePath = "node_modules/ffmpeg-static/bin/"; // ffmpeg-static
const { platform } = process;
const ffmpegPathMap = {
darwin: "darwin/x64/ffmpeg",
win32: "win32/ia32/ffmpeg.exe",
win64: "win32/x64/ffmpeg.exe",
linux32: "linux/ia32/ffmpeg",
linux64: "linux/x64/ffmpeg"
};
const ffmpegPath = ffmpegBasePath + ffmpegPathMap[platform];
plugins.push(
new CopyWebpackPlugin([
{
from: path.join(__dirname, ffmpegPath),
to: path.join(__dirname, "core"),
ignore: [".*"]
}
])
);
config.plugins = [...config.plugins, ...plugins];
}
};

第二步

在配置electron-builder打包过程,让ffmpeg放在指定位置

还是配置vue.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
module.exports = {
pluginOptions: {
electronBuilder: {
builderOptions: {
appId: "cn.psvmc",
productName: "星火智慧课堂",
icon: "./app.ico",
files: ["**/*", "static/*"],
asar: true,
win: {
icon: "./app.ico",
target: ["nsis"],
extraResources: {
from: "./core/",
to: "./core/",
filter: ["**/*"]
}
},
nsis: {
oneClick: false,
allowElevation: true,
allowToChangeInstallationDirectory: true,
installerIcon: "./app.ico",
uninstallerIcon: "./app.ico",
installerHeaderIcon: "./app.ico",
createDesktopShortcut: true,
createStartMenuShortcut: true,
license: "./LICENSE.txt"
}
}
}
}
}

主要就是添加了

1
2
3
4
5
extraResources: {
from: "./core/",
to: "./core/",
filter: ["**/*"]
}

第三步

调用时对应的路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { resolve } from "path";
let ffmpegPath = "";
// 更新打包后 ffmpeg 路径
if (process.env.NODE_ENV === "production") {
ffmpegPath = resolve(__dirname, "../core/ffmpeg");
} else {
const { platform } = process;
const ffmpegPathMap = {
darwin: "darwin/x64/ffmpeg",
win32: "win32/ia32/ffmpeg.exe",
win64: "win32/x64/ffmpeg.exe",
linux32: "linux/ia32/ffmpeg",
linux64: "linux/x64/ffmpeg"
};
ffmpegPath = resolve(
__dirname,
"../../../../..",
"ffmpeg-static/bin",
ffmpegPathMap[platform]
);
}

获取Windows音视频输入设备

1
ffmpeg -list_devices true -f dshow -i dummy