一文搞懂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
[email protected]
appleIdPassword=xxxx-xxxx-xxxx-xxxx
teamId=xxxxx

项目下新建entitlements.mac.plist文件,固定写死,直接拷(编译arm64的应用时,务必添加com.apple.security.cs.allow-jit权限,否则程序很可能崩溃)




    
        com.apple.security.cs.allow-unsigned-executable-memory
        
        com.apple.security.cs.allow-jit
        
    

项目下新建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标签之前


然后就可以在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')前面执行

你可能感兴趣的:(一文搞懂Electron + Vue的开发)