electron-vue不要再用了,版本太老作者也不更新,而且electron11.0才开始支持Apple Silicon(m1)机型。
近期我自己也在开发一些electron + vue的跨平台项目,本文主要记录一下新起一个项目的时候需要安装哪些工具与步骤
安装系统全局组件Vue CLI用于创建vue项目
npm install -g @vue/cli
创建标准化Vue项目
vue create vueapp
然后根据Vue CLI提示选择自己的常用工具并建立一个vue网站项目
具体步骤
添加electron插件
首先进入刚才建好的vueapp文件夹,安装以下插件
vue add electron-builder
然后选择最新的版本,等待安装完成即可。
这里可能会非常慢,最终导致超时失败。这是因为需要根据系统来下载electron的基础库,由于网络原因,建议此处使用设置npm proxy来进行下载。不建议使用其他镜像源下载,可能会导致最终打包失败,遇到过好几次问题了。
npm config set proxy http://127.0.0.1:58592
npm config set https-proxy http://127.0.0.1:58592
还有一种方式是把electron的源换成淘宝的:
npm config set registry https://registry.npmmirror.com
npm config set ELECTRON_MIRROR https://npmmirror.com/mirrors/electron/
启动项目查看是否正常
此时项目src文件夹下就多了一个background.js,这里就是electron主进程相关代码,负责和我们的vue页面(渲染进程)进行交互。
执行以下代码启动项目
npm run electron:serve
此时应当能弹出一个应用程序界面,并且还有我们熟悉的Vue:
可喜可贺!你可以正常开发了,添加npm插件就正常使用npm install xxx即可。
如果此时出现electron安装错误请重新安装的提示,那么就按以下步骤操作issue:
- 在node_modules\electron文件夹下新建一个path.txt
- txt中写入:electron.exe
- 前往https://npmmirror.com/mirrors/electron/根据自己的版本下载对应平台的zip包
- 将zip包解压到node_modules\electron\dist 这样即可修复安装错误问题。
进行一些必要配置
- electron升级
由于刚才安装的electron版本已经过低,我们需要先进行升级。
在命令行中卸载当前electron:
npm uninstall electron
然后安装最新的electron和remote模块:
npm install -S electron
npm install -S @electron/remote
【可选步骤】由于vue-cli-plugin-electron-builder已经长年不更新了,在electron20+、mac系统上具有大量兼容性bug,因此需要升级到3.0.1(这个库可能对element兼容不太好,会丢字体):
@matthijsburgh/vue-cli-plugin-electron-buildernpm install -D @matthijsburgh/vue-cli-plugin-electron-builder
另外需要注意,background.js中引用createProtocol也要更改为这个包:
import { createProtocol } from '@matthijsburgh/vue-cli-plugin-electron-builder/lib'
然后进行electron配置,background.js:
//background.js
//文件头部,引用增加ipcMain用于通信
import { app, protocol, BrowserWindow, ipcMain } from 'electron'
import { createProtocol } from '@matthijsburgh/vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
const isDevelopment = process.env.NODE_ENV !== 'production'
//窗口启动增加选项
const win = new BrowserWindow({
width: 800, //窗口默认宽度
height: 600, //窗口默认高度
useContentSize: false,
frame: true, //取消window自带的关闭最小化等
resizable: false, //禁止改变主窗口尺寸
transparent: false, //透明
hasShadow: true, //窗口阴影
maximizable: true, //是否允许最大化
webPreferences: {
enableRemoteModule:true, //在渲染进程启用remote模块
nodeIntegration: true, //在渲染进程启用Node.js
contextIsolation:false,
webSecurity: false,
backgroundThrottling: false, //程序在最小化时渲染进程不冻结
}
})
//由于渲染进程中electron.remote已废弃,需要手动引入,并在每一个browserwindow中启用remote
require('@electron/remote/main').initialize()
require("@electron/remote/main").enable(win.webContents);
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')
}
- Vue相关配置,这样就可以在使用electron的API,main.js:
//main.js
const electron = window.require('electron') //引用electron
const fs = window.require('fs') //引用Node模块fs
const remote = window.require('@electron/remote') //引用remote模块用于通信、打开文件弹框等
Vue.prototype.$electron = electron
Vue.prototype.$fs = fs
Vue.prototype.$remote = remote
具体页面中,xxx.vue:
//在页面中使用相关API
this.$electron.ipcRenderer.sendSync('testMsg',data); //与主进程通信
this.$remote.getCurrentWindow().minimize(); //最小化窗口
this.$fs.existsSync("C:/test.js"); //调用nodejs方法查找文件是否存在
3.添加必要文件
- gitignore忽略输出文件夹build、dist_electron
// .gitignore
/build
/dist_electron
- 新建vue.config.js,添加打包相关配置项,具体配置的作用在electron-builder官网有详细说明,下文请自行替换appid、name之类的字段
module.exports = {
pluginOptions: {
electronBuilder: {
"customFileProtocol": "./", //增加此项让css中相对引用的文件能正常访问,否则一些css库中的字体会无法显示
"builderOptions": {
"extraResources": [
"./extraResources/**", //这里指定外部资源文件夹,你可以把一些外部程序放在./extraResources中,比如ffmpeg等,打包时electron将直接原样拷贝
"./node_modules/@electron/remote/**",//必须添加这一行否则remote会引用不到
],
"productName": "your app name",
"appId": "your.app.appId",
"copyright":"Copyright © your name 2021",// 版权信息
"directories": {
"output": "./build" //输出文件夹
},
"afterSign": "./notarize.js", //签名文件
"dmg": { //输出mac的dmg时图标位置
"contents": [
{
"x": 410,
"y": 150,
"type": "link",
"path": "/Applications"
},
{
"x": 130,
"y": 150,
"type": "file"
}
]
},
"mac": {
"icon": "./icons/icon512.icns",//mac图标,必须至少包含512*512尺寸的图标
"target":[{
"target": "dmg", //设置输出dmg安装包
"arch": ["arm64", "x64"], //arm64是apple silicon机器专用的包;x64是intel版的包,两种电脑都能用,不过x64运行启动会慢一点;这里你可以填universal,这样会生成一个arm64+x64打出来然后装一起的一个包(体积也是两种应用之和),运行时会自动选择版本
}],
"identity": "your name",
"entitlements": "./entitlements.mac.plist", //签名必须
"entitlementsInherit": "./entitlements.mac.inherit.plist",//签名必须
"entitlementsLoginHelper": "./entitlements.mas.loginhelper.plist",//如果需要mac的mas版打包,则需要此项
},
"win": {
"icon": "./icons/icon256.ico",//win打包图标
"target": [{
"target": "nsis",// 利用 nsis 制作安装程序
"arch": [
"x64",//64 位
]
}]
},
"nsis": {
"oneClick": false, // 是否一键安装
"allowElevation": true, // 允许请求提升. 如果为 false, 则用户必须使用提升的权限重新启动安装程序.
"allowToChangeInstallationDirectory": true, // 允许修改安装目录
"installerIcon": "./icons/icon256.ico",// 安装图标
"uninstallerIcon": "./icons/icon256.ico",// 卸载图标
"installerHeaderIcon": "./icons/icon256.ico", // 安装时头部图标
"createDesktopShortcut": true, // 创建桌面图标
"createStartMenuShortcut": true,// 创建开始菜单图标
"shortcutName": "DNG自动转换工具", // 图标名称
"perMachine": true
},
}
}
},
configureWebpack: config => {
return {}//webpack相关配置
}
}
- 项目下新建icons文件夹用于存放图标,内部放置至少2个图标,分别为mac的icon512.icns和用于win的icon256.ico,打包时要用到。从普通图片创建图标可以使用IconWorkshop。
- 项目下新建extraResources文件夹,里面存放需要使用的外部程序,比如ffmpeg等。electron在打包时将不会编码该文件夹下的文件,直接原样拷贝,安装时也会直接放置于安装目录中。
- 接下来,如果你是mac,需要打包成dmg安装包,则需要安装这个@electron/notarize (electron-notarize已废弃)
//安装公证组件
npm install @electron/notarize --save-dev
项目下新建.env文件,这里存放在mac开发者中心成为开发者时你的appleID及苹果提供相关密码,注意这个密码不要写你的appleID密码,可以在苹果账号中心中手动添加一个app专用密码,填写在这里。teamId是你apple开发者账号下找到。也可以使用钥匙串,具体参见此文mac下electron签名及公证教程
//.env
appleId=youraccount@test.com
appleIdPassword=xxxx-xxxx-xxxx-xxxx
teamId=xxxxx
项目下新建entitlements.mac.plist文件,固定写死,直接拷(编译arm64的应用时,务必添加com.apple.security.cs.allow-jit权限,否则程序很可能崩溃)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
</dict>
</plist>
项目下新建notarize.js文件,固定写死,直接拷
require('dotenv').config()
const {notarize} = require('@electron/notarize')
exports.default = async function packageTask (context) {
const appName = context.packager.appInfo.productFilename
const {electronPlatformName, appOutDir} = context
if (electronPlatformName !== 'darwin') {
return
}
let appPath = `${appOutDir}/${appName}.app`
let {appleId, appleIdPassword, teamId} = process.env
console.log("notarize")
console.dir({appleId, appleIdPassword, teamId})
return await notarize({
appPath,
appleId,
appleIdPassword,
teamId,
})
}
@electron/notarize使用的是notarytool,旧版altool的公证将会在2023年11月停止访问。新版工具将会访问aws的地址,很可能连不上,需要自行准备魔法
打包你的App
按上面的操作一步步来的话应该已经可以打包成exe或者dmg了
npm run electron:build
打包这一步也会非常慢,同样是墙的问题,请不要使用cnpm,而是用本文一开始的set proxy方式
npm config set proxy http://127.0.0.1:58592
npm config set https-proxy http://127.0.0.1:58592
如果还是出现这种网络原因导致的报错: Get "https://github.com/electron-userland/electron-builder-binaries/releases/download/winCodeSign.7z" A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
可以手动进入提示上的网址将文件下载下来并解压
- nsis和nsis-rescources放置到 C:\Users\username\AppData\Local\electron-builder\Cache\nsis中;
- winSodeSign放置到 C:\Users\username\AppData\Local\electron-builder\Cache\winSodeSign中
- electron-vx.x.x-win32-x64.zip放置到 C:\Users\username\AppData\Local\electron\Cache中
开发相关坑点解读 Electron 常见问题
1. 无法使用require引用
当你在项目中使用require引用一些模块(比如jQuery),会导致Uncaught TypeError: $ is not a function
这个问题是的直接原因是require无法引用,根本原因node中的require覆盖了webpack的require,这就导致了打包失败
- 解决方案一:
在渲染进程中禁用Node.js。在主进程background.js中,将nodeIntegration选项设置为false
//background.js
const win = new BrowserWindow(
webPreferences: {
nodeIntegration: false //该选项在渲染进程中禁用Node.js
}
})
这样就可以正常使用require了。副作用是无法访问一些底层的参数比如__dirname,当引用的组件使用这些参数时会报错。
- 解决方案二:
当不可避免的想要使用Node.js时,比如想引用fs来读取某个文件,那么在主进程background.js中,将nodeIntegration选项设置为true
//background.js
const win = new BrowserWindow(
webPreferences: {
nodeIntegration: true //该选项在渲染进程中启用Node.js
}
})
在require组件时,必须在main.js中引用,并且必须使用window.require
//main.js
const electron = window.require('electron')
Vue.prototype.$electron = electron
这样就可以在页面中通过this.$electron访问到electron的API了
- 解决方案三
将require重命名。在index.html中添加以下代码,放置在所有script标签之前
<!-- index.html -->
<script>
window.nodeRequire = require;
delete window.require;
delete window.exports;
delete window.module;
</script>
然后就可以在main.js中使用nodeRequire 引用模块了(不能在页面中使用)
//main.js
const fs = nodeRequire('fs')
Vue.prototype.$fs = fs
//App.vue
this.$fs.existsSync("C:/test.js")
2. 在mac下,启动项目时会报一个fsevents的错误:
ERROR Failed to compile with 1 errors
error in ./node_modules/fsevents/fsevents.node
Module parse failed: Unexpected character '�' (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process his file. See https://webpack.js.org/concepts#loaders
(Source code omitted for this binary file)
@ ./node_modules/fsevents/fsevents.js 13:15-41
@ ./node_modules/chokidar/lib/fsevents-handler.js
@ ./node_modules/chokidar/index.js
@ ./src/background.js
@ multi ./src/background.j
fsevents是mac系统的一个必须要有的二进制模块,如果你想开发跨平台程序,一定绕不开他。
这个报错的原因是fsevents.js中使用了require,导致项目无法启动。
需要点击./node_modules/fsevents/fsevents.js 13:15,找到该文件13行代码,按照下文修改即可
// ./node_modules/fsevents/fsevents.js
const Native = require("./fsevents.node")
//更改为
const Native = window.require("./fsevents.node")
3. 在mac下,electron-builder打包项目时会报一个ENOENT的错误:
spawn /usr/bin/python ENOENT
产生原因是macos ventura中移除了python2,因此electron-builder在打包dmg时将无法读取这个地址。
解决方案:卸载默认的vue-cli-plugin-electron-builder2.0.0,安装 @matthijsburgh/vue-cli-plugin-electron-builder 即可。
我们也采用手动安装的方式解决。
打开终端,使用conda创建一个python2.7的环境
conda create -c 'https://repo.continuum.io/pkgs/free/osx-64' -n py2 python=2.7
这里没有直接conda create -ns是因为新版anaconda已无法搜索到py2.7,因此需要手动指定channel,否则会提示“ackagesNotFoundError: The following packages are not available from current channels python=2.7”
创建完成后进入anaconda图形界面,找到py27的环境,打开terminal,运行以下代码找到你的python2.7执行文件的地址
which python
# /Users/mordom/anaconda3/envs/py2/bin/python
记下这个地址,稍后将会用到
回到我们的electron项目的地址,定位到以下两个文件
/node_modules/vue-cli-plugin-electron-builder/node_modules/dmg-builder/out/dmg.js
/node_modules/dmg-builder/out/dmg.js
前者找到261行:
await builder_util_1.exec(process.env.PYTHON_PATH || "/usr/bin/python", [path.join(dmgUtil_1.getDmgVendorPath(), "dmgbuild/core.py")], {
cwd: dmgUtil_1.getDmgVendorPath(),
env,
});
将这里的"/usr/bin/python"替换为我们刚才记录下的地址"/Users/mordom/anaconda3/envs/py2/bin/python",这样在打包时electron-builder将调用我们手动下载的python2.7了。
后者同样搜索“/usr/bin/python”,并替换为我们的地址即可。
至此问题解决。
4.打包后在部分windows系统上出现白屏闪退问题
该问题由chromium的沙盒机制造成,详细见进程沙盒化
解决方案
//background.js
import { app } from 'electron'
app.commandLine.appendSwitch('--no-sandbox'); //该行应在app.on('ready')前面执行