一文搞懂Electron + Vue的开发

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

  1. 在node_modules\electron文件夹下新建一个path.txt
  2. txt中写入:electron.exe
  3. 前往https://npmmirror.com/mirrors/electron/根据自己的版本下载对应平台的zip包
  4. 将zip包解压到node_modules\electron\dist 这样即可修复安装错误问题。

进行一些必要配置

  1. 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-builder

npm 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')
  }
  1. 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。
icons文件夹
  • 项目下新建extraResources文件夹,里面存放需要使用的外部程序,比如ffmpeg等。electron在打包时将不会编码该文件夹下的文件,直接原样拷贝,安装时也会直接放置于安装目录中。
  1. 接下来,如果你是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')前面执行
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,406评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,976评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,302评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,366评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,372评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,457评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,872评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,521评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,717评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,523评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,590评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,299评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,859评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,883评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,127评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,760评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,290评论 2 342

推荐阅读更多精彩内容