前言
影子设备本地部署桌面端开发使用的是Electron技术,本文汇总了笔者在开发中的踩坑以及如何优化项目可执行文件的体积等关键技术内容。
Electron重要特性介绍
Electron的主进程和渲染进程:
注意主进程和渲染进程都被node.js所包裹,所以他们都可以自如的使用node.js。
进程通讯
通讯(渲染进程和主进程之间的通讯是通过ipcMain这个模块来做的)
通讯的目的是为了让双方访问不属于自己进程的api.
Electron踩坑
1.electron通讯踩坑
通讯的情况:项目中请求接口,如果报错说不在登陆状态,那么这个时候渲染进程会去通过在前端执行命令行语句请求获取cookie的第三方工具,获取到涂鸦内部登陆页面地址,由于这个登陆页面是jquery编写的,和我们项目的window的配置是不兼容的,所以我们需要重新开一个window,这个时候就需要主进程去打开新的window显示登录页面了。
踩坑事项一:
如果窗口内代码是使用jquery编写,那么窗口的配置nodeIntegration和contextIsolation是不做配置的,但是我们react项目窗口配置了nodeIntegration为true,contextIsolation为false,如果登陆页面(使用jquery便携的)和react页面共用一个窗口,会导致登陆页面的二维码无法正常显示,所以为了显示登录页面,我们必须另外开一个窗口。
主进程代码:
import {
ipcMain} from 'electron'
ipcMain.on('message', (event, msg) => {
if (msg.topic === 'url') {
// jquery页面的BrowserWindow的配置如下
sideWindow = new BrowserWindow({
height: 800,
width: 1200,
})
sideWindow.loadURL(msg.content)
mainWindow.hide()
}
})
}
渲染进程代码:
踩坑事项二:
注意渲染进程引入ipcRenderer模块的时候,需要在前面添加window才能正常引入,否则会报错说找不到对应的模块。
const Store = window.require('electron-store')
const path = window.require('path')
const execSync = window.require('child_process').execSync
const exec = window.require('child_process').exec
// 注意渲染进程引入ipcRenderer模块的时候,需要在前面添加window
const {
ipcRenderer } = window.require('electron')
const app = window.require('electron')
const store = new Store()
export const header = {
sso_token: `SSO_USER_TOKEN=${
store.get('token')}` }
export const openLoginCount = {
count: 0 }
export const checkSSOAndGetToken = (res): Promise<any> => {
if (!res.success && res.code === '101') {
if (res.message && openLoginCount.count === 0) {
openLoginCount.count += 1
// 当发现不在登陆状态的时候,去执行获取cookie的第三方工具
const filepath = path.resolve(__dirname, '../../../../', 'res/mac')
const ls = exec('./SamCMD', {
cwd: filepath })
ls.stdout.on('data', function (data) {
const urlIndex = data.indexOf('Login page')
if (urlIndex !== -1) {
const pageUrl = data.slice(urlIndex + 12)
const obj = {
topic: 'url',
content: pageUrl,
}
// 渲染进程这里将获取的登陆地址传递给主进程
ipcRenderer.send('message', obj)
}
})
ls.stderr.on('data', function (data) {
console.log(`stderr: ${
data}`)
})
ls.on('exit', function (code) {
console.log(`child process exited with code ${
code}`)
})
}
return Promise.reject(res)
}
return Promise.resolve(res)
}
2.在前端使用node执行命令行语句踩坑
由于我们引入了第三方工具来获取cookie,那么我们有了在前端去执行命令行语句执行该工具获取cookie的需求,我们通过调用node的child_process模块去实现在前端执行命令行语句。我们知道不论是主进程还是渲染进程,都是可以使用node的,但是在渲染进程引入node模块需要在引入的require前加上window,并且window做相应的配置,我们才能够正常使用node模块。
渲染进程引入node模块的方式:
const path = window.require('path')
const exec = window.require('child_process').exec
export const checkSSOAndGetToken = (res): Promise<any> => {
//....
const filepath = path.resolve(__dirname, '../../../../', 'res/mac')
const ls = exec('./SamCMD', {
cwd: filepath })
ls.stdout.on('data', function (data) {
//...
}
ls.stderr.on('data', function (data) {
console.log(`stderr: ${
data}`)
})
ls.on('exit', function (code) {
console.log(`child process exited with code ${
code}`)
})
}
主进程关于window的配置:
需要同时配置nodeIntegration为true,以及contextIsolation为false,这样才能正常使用node模块。
const createWindow = (): void => {
const mainWindow = new BrowserWindow({
height: 800,
width: 1200,
webPreferences: {
// 将 nodeIntegration 设置为 true, 代表着在 Browser 中可以使用 node 的 API。
// 文档在这里:
//https://www.electronjs.org/docs/api/browser-window#new-browserwindowoptions
nodeIntegration: true,
contextIsolation: false,
},
})
3.为什么渲染进程引入node模块要加window?
在渲染进程中把require改成window.require,加上window,使得webapck不截胡,不打包不bundle,不去node_modules下找fs模块,因为它会找不到fs这个原生模块的,找不到就会报错了。
如果加了window,那么 webpack会忽略它,不打包,把控制权交给nodejs的运行环境,这样就能成功在渲染进程引入原生模块。
4.Electron打包实现一键安装时选择对应的插件踩坑
影子设备本地部署需要打包三种不同操作系统和架构下的可执行文件:
1.x64架构的window操作系统
2.arm64架构的mac操作系统
3.x64架构的mac操作系统
Electron打包有三种工具可以选择:
1.electron-forge自身提供了脚本命令语句来打包
2.electron-builder插件打包
3.electron-packager插件打包
electron-forge打包存在的问题:
只能打包出来与本操作系统本架构一致的安装包,如果需要打包适用于其他平台其他架构的安装包,那么需要在其他平台其他架构的电脑上拉取我们的项目代码进行代码兼容处理和打包,所以笔者就弃用了electron-forge来打包。
electron-packager插件打包存在的问题:
可以在本操作系统本架构的电脑上打包出来适用其他平台其他架构的安装包, 支持输出多个平台(Linux、mac、Windows)的可执行文件,非常方便。但是存在的问题是,打包windows操作系统安装包的时候,每次都会打包出来一堆文件,打包出来的.exe文件并不能独立运行,其实electron-packager可以理解为 “程序封装” 步骤的自动化工具,严格意义上并不是输出安装包。
为了能打包出来可单独运行的.exe可执行文件,笔者也弃用了electron-packager这个插件,改用了electron-builder。
electron-builder插件存在的问题:
只能打包出来本平台一致的可执行文件,如我们的电脑是mac平台的,我们可以打包出来适用mac平台的任意架构的可执行文件。但是如果要打包出来其他平台(windows操作系统)的可执行文件,那么我们就只能另外找一台windows操作系统的电脑,拉取项目代码,进行打包的操作。
但是由于electron-builder打出的包更为轻量,并且可以打包出不暴露源码的可独立运行的setup安装程序,所以笔者权衡三种打包工具的利弊后,选择了electron-builder插件进行打包。
5.运行可执行文件电脑报错macOS版本过低的解决办法
笔者在使用electron-builder打包了mac平台的可执行文件后,交付给TL做测试,但是TL的10.15macOS版本的mac电脑提示说,macOS版本过低,无法正常打开可执行文件。经过笔者查阅,electron确实不支持低版本的macOS,不过是自electron4.0.0起不支持10.9版本的macOS。而TL的电脑是10.15,理应是支持的。
那么我们让TL去更新macOS的版本也并不合适,但是electron-builder也没有相关的配置项让我们兼容不同版本的macOS.
最后,笔者发现其实问题并不在于macOS的版本问题,在于架构的问题,TL的电脑是x64的架构,笔者打包的可执行文件是适用于arm64架构的,所以笔者重新打包了一份适用于x64的架构的可执行文件给TL后,电脑就没有再报错了,就可以正常打开应用了。
6.引入第三方工具打包注意事项
因为我们的项目中涉及一个获取cookie的第三方工具,那么我们如何保证打包项目后,这个第三方工具不被压缩不被处理,完好的打包到项目中以便我们能正常的请求这个工具,以及我们如何能知道打包后的该工具的相对于我们渲染进程代码的位置以便我们在渲染进程代码里可以去请求这个工具。
electron-builder是为此专门提供了相关的配置,让我们打包指定资源到安装目录的。
如下图所示,我们把第三方工具放在res文件夹下,同时在package.json文件中如下配置extraResources配置项。
但是最后electron-builder把第三方工具打包到哪里去了呢,我们在代码里怎么获取这个工具的相对位置呢?
我们在打包好的app文件处右击,点击“显示包内容”。
我们打开content下的Resources文件夹,然后我们会看见两个最重要的文件,res就是我们的第三方工具文件所在的文件夹了。app.asar文件就是我们的源代码压缩包了,asar是electron特有的压缩包,用来保护源代码不被别人看到。
我们可以在启动应用后在控制台打印出渲染进程代码所在的位置,如下图所示,是在红框框起来的位置,那么我们可以以此推断出来res第三方文件夹的相对位置,从而在代码中去执行这个第三方工具。
7.优化项目打包体积的重点事项
参考链接: electron-builder打包大小优化
electron-builder打包大小优化
双package.json打包:
笔者在打包项目的时候,首先使用electron-forge make脚本命令打包出来.webpack文件,然后更换一个package.json文件,在其中指定需要打包的文件夹。
并且该package.json文件中的依赖是只有打包需要用到的依赖以及主进程里需要用到的依赖,渲染进程里的依赖是不需要的。
将之前的依赖全部删除后,重新yarn安装依赖,并且打包,去除了不必要的依赖后,安装包的体积就小了很多。
8.electron-builder踩坑-因webpack的output配置导致打包白屏问题
第一次使用electron-builder打包的时候,打包出来的文件一直是白屏,并且资源面板显示No resource with given URL found。
谷歌出来的解决办法,一般都是说使用的主窗口的入口的配置有问题,笔者检查了下入口配置,是没有问题的。
而且其实我们项目使用的是electron-forge结合electron-forge/plugin-webpack的技术去配置的主窗口入口,因为技术使用的非常新,网上基本找不到相同的技术的问题解决方案,网上的项目大多只是使用electron,即便有使用electron-forge的,都鲜有用到electron-forge/plugin-webpack这个插件的。
最后笔者是如何解决这个白屏的bug的呢,笔者在github上拉取了一个技术完全相同的简单项目,顺利使用electron-builder打包成功后,观察打包出来的文件,发现其打包出来的.webpack文件夹下是有main和renderer两个文件夹的。
而我们项目打包出来,只有main一个文件夹,缺失了renderer这个文件夹,或者说,我们打包出来的,比对github上的项目,除了main文件夹,剩下的文件应该是放在renderer文件夹里的。
那么此时我去查看了github文件的webpack的output配置,发现它并没有配置这一项,也就是说,electron-forge/plugin-webpack内置了这个output的默认配置,而我们的项目配置的是把文件打包到.webpack文件夹下,所以说,很可能是因为我们显性的配置和默认的配置是不一致的,所以笔者把我们的output的path配置从/.webpack改成了/.webpack/renderer后,就解决了白屏问题。