STAY HUNGRY , STAY FOOLISH.

求知若饥,虚心若愚。

       浏览:

electron+hooks+ts实现互动直播大班课(二)

本篇文章概要:

  • 环境搭建
  • 安装跨平台的环境变量设置
  • 安装配置electron
  • 自定义”electron:start”命令
  • 自定义”electron”命令
  • 安装配置electron-builder
  • 自定义打包命令
  • 打dmg安装包
  • 实现安装包签名步骤
  • 打exe安装包
  • 查看asar包内容
  • 通过伪协议唤起客户端
  • 从伪协议中获取参数

1.搭建环境:

安装create-react-app:

1
2
npm uninstall -g create-react-app
npm install -g create-react-app

创建create-react-app脚手架项目:

1
npx create-react-app teacher-app --template typescript

自定义脚手架命令:

1
2
npm install react-app-rewired --save-dev
npm install

添加config-overrides.js,新增代码:

1
2
3
4
5
6
/* config-overrides.js */

module.exports = function override(config, env) {
//do stuff with the webpack config...
return config;
}

替换package.json里的react-scripts换成react-app-rewired:

1
2
3
4
5
6
7
8
9
10
11
  /* package.json */

"scripts": {
- "start": "react-scripts start",
+ "start": "react-app-rewired start",
- "build": "react-scripts build",
+ "build": "react-app-rewired build",
- "test": "react-scripts test",
+ "test": "react-app-rewired test",
"eject": "react-scripts eject"
}

2.安装跨平台的环境变量设置:

1
npm install cross-env --save-dev

修改package.json的react-scripts:
注意:如果需要自定义环境变量 必须以REACT_APP_开头

1
2
3
4
5
6
7
"start": "cross-env PORT=3031 REACT_APP_ENV=development react-app-rewired start",

"start:pro": "cross-env PORT=3032 REACT_APP_ENV=production react-app-rewired start",

"build": "cross-env REACT_APP_ENV=development react-app-rewired build",

"build:pro": "cross-env REACT_APP_ENV=production react-app-rewired build"

使用cross-env,通过PORT属性设置项目运行的端口,通过REACT_APP_ENV属性设置项目是开发环境还是生产环境

可自定义配置:
start:开发环境,本地调试端口3031
start:pro:生产环境,本地调试端口3032
build:打包开发环境
build:pro:打包生产环境

在项目中,可通过process.env.REACT_APP_ENV来访问当前环境


执行npm run start,调试web开发环境:

react_dev

执行npm run start:pro,调试web生产环境:

react_pro


3.安装配置electron:

安装加速:

1
2
3
4
# macOSOS
export ELECTRON_MIRROR="https://npm.taobao.org/mirrors/electron/"
export ELECTRON_CUSTOM_DIR="9.1.0"
export SASS_BINARY_SITE="https://npm.taobao.org/mirrors/node-sass/"

安装electron成功后,安装concurrently、wait-on:

1
npm install concurrently wait-on --save-dev

concurrently:并行执行多个命令
wait-on:等待异步命令执行完成

修改package.json的react-scripts:
注意:如果需要自定义ELECTRON环境变量 必须以ELECTRON_开头

1
2
3
4
5
"electron": "cross-env BROWSER=none ELECTRON_LOAD_URL=http://localhost:3031 concurrently \"npm run start\" \"wait-on http://localhost:3031 && npm run electron:start\"",

"electron:pro": "cross-env BROWSER=none ELECTRON_LOAD_URL=http://localhost:3032 concurrently \"npm run start:pro\" \"wait-on http://localhost:3032 && npm run electron:start\"",

"electron:start": "electron ./app"

4.自定义”electron:start”命令

首先自定义electron:start命令,该命令打开一个桌面应用
那么如何实现一个打开桌面应用的壳呢?
使用electron ./app实现。
在项目里面新增app目录,新建一个index.js和preload.js。
electron ./app会自动执行app/index.js里面的内容,我们在index.js里面去创建一个浏览器窗口。
具体实现可参考electron官网的项目:
https://github.com/electron/electron-quick-start

app/index.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
// Modules to control application life and create native browser window
const {app, BrowserWindow} = require('electron')
const path = require('path')

function createWindow () {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})

// and load the index.html of the app.
const startUrl = process.env.ELECTRON_LOAD_URL ||
`file://${path.resolve(
__dirname,
'../../app.asar/build'
)}/index.html`;
// and load the index.html of the app.
console.log('startUrl--------', startUrl)
mainWindow.loadURL(startUrl);
// mainWindow.loadFile('index.html')

// Open the DevTools.
// mainWindow.webContents.openDevTools()
}

// 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.whenReady().then(() => {
createWindow()

app.on('activate', function () {
// On macOSOS 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 (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})

// Quit when all windows are closed, except on macOSOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

app/preload.js

1
2
3
4
5
6
7
8
9
10
11
12
// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
}

for (const type of ['chrome', 'node', 'electron']) {
replaceText(`${type}-version`, process.versions[type])
}
})

5.自定义”electron”命令

electron命令如下:

1
cross-env BROWSER=none ELECTRON_LOAD_URL=http://localhost:3031 concurrently \"npm run start\" \"wait-on http://localhost:3031 && npm run electron:start\"

使用cross-env,通过BROWSER=none属性设置项目启动后不需要跳入浏览器,因为后续我们需要将项目启动的页面跳入electron壳内创建的浏览器。通过ELECTRON_LOAD_URL属性设置electron创建浏览器窗口后加载的本地连接(用于mainWindow.loadURL)。

使用concurrently,并行执行npm run startwait-on http://localhost:3031 && npm run electron:start命令。

使用wait-on,监听本地项目启动是否成功,等待项目启动成功后,也就是http://localhost:3031能够运行后,再去执行npm run electron:start命令。

使用electron命令,该命令能运行完本地项目后将其加载到electron浏览器里面,打开一个桌面应用

执行npm run electron,调试electron开发环境:

react_electron_dev

执行npm run electron:pro,调试electron生产环境:

react_electron_pro


6.安装配置electron-builder

1
npm install electron-builder --save-dev

在package.json新增配置build:
里面具体的参数配置
公共的配置,请访问:
https://www.electron.build/configuration/configuration
macOS的配置,请访问:
https://www.electron.build/configuration/win
win的配置,请访问:
https://www.electron.build/configuration/win

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
"homepage": ".",
"build": {
"appId": "com.doumeng.mlive",
"productName": "MLIVE",
"macOS": {
"target": [
"dmg"
],
"icon": "icons/favicon.png",
"artifactName": "${productName}-${version}.${ext}",
"category": "com.doumeng.mlive",
"entitlements": "entitlements.macOS.plist",
"hardenedRuntime": true,
"extendInfo": {
"NSMicrophoneUsageDescription": "MLIVE acquire your microphone permission",
"NSCameraUsageDescription": "MLIVE acquire your camera permission"
}
},
"win": {
"target": [
{
"target": "nsis",
"arch": [
"ia32"
]
}
],
"icon": "icons/favicon.png",
"artifactName": "${productName}-${version}.${ext}",
"publisherName": "com.doumeng.mlive"
},
"extraMetadata": {
"main": "build/index.js"
},
"files": [
"build/**/*"
],
"directories": {
"buildResources": "assets",
"output": "release"
}
}

注意:在package.json中新增homepage .,不然打包后的app无法找到编译后的js和css文件。
(将编译后的文件绝对路径换成相对路径)


7.自定义打包命令

修改package.json的react-scripts:

1
2
3
4
5
6
7
8
9
"pack:macOS": "npm run electron:build && electron-builder --macOS",
"pack:win": "npm run electron:build && electron-builder --win",

"pack:macOS:pro": "npm run electron:build:pro && electron-builder --macOS",
"pack:win:pro": "npm run electron:build:pro && electron-builder --win",

"electron:copy": "cpx ./app/**/*.js ./build",
"electron:build": "npm run build && npm run electron:copy",
"electron:build:pro": "npm run build:pro && npm run electron:copy"

安装cpx

1
npm install cpx --save-dev

使用cpx,通过cpx xxx1 xxx2命令可以将js文件复制到其他目录中。

使用pack:macOS命令,该命令编译完项目后,打包成dmg安装包。(macOS系统电脑使用)
使用pack:win命令,该命令编译完项目后,打包成exe安装包。(Windows系统电脑使用)

注意:在macOSbook电脑上是同时支持打dmg和exe安装包的,之前以为只能在各自平台打安装包,dmg必须macOS电脑,exe必须windows电脑。


8.打dmg安装包

默认如果没苹果开发者账号的话,在执行的过程会报以下警告:

release

signing is required for mas builds. Provide the osx-sign option, or manually sign the app later.
MAS构建需要签名。提供OSX签名选项,或稍后手动签名应用程序。

kipped macOSOS application code signing reason=cannot find valid “Developer ID Application” identity or custom non-Apple code signing certificate
跳过的macOSOS应用程序代码签名 原因=找不到有效的“Developer ID Application”标识或自定义非Apple代码签名证书


如果没苹果开发者账号,可禁用签名:

1
export CSC_IDENTITY_AUTO_DISCOVERY=false

如果有开发者苹果账号:
.bash_profile中写入CSC_LINKCSC_KEY_PASSWORD两个全局变量,具体可参考macOS签名:
https://www.electron.build/code-signing

注意:macOS上不签名也可以打包成功,但是涉及到自动更新等需要身份认证的功能则不能用,也不能发布到macOS app store中。所以说经过代码签名的macOS包才是完整的包。


9.实现安装包签名

1.在钥匙串中找到苹果开发者证书

apple_developer


2.右键导出密钥

export_key


3.保存密钥到本地

save_key


4.设置密钥密码

key_pwd


5.设置全局变量

1
2
3
4
5
6
7
8
9
10
11
12
// 编辑.bash_profile
sudo vim ~/.bash_profile

// 写入变量
export CSC_LINK=/Users/.../cat.p12 // 设置密钥的位置
export CSC_KEY_PASSWORD=xxxx // 设置密钥的密码

// 刷新全局变量
source ~/.bash_profile

// 查看全局变量
env

看到有CSC_LINK和CSC_KEY_PASSWORD就设置成功:
global_value


6.打包成功
执行npm pack:macOS,打包成功:

right_code


10.打exe安装包

执行npm pack:win,在打包exe的过程中,最坑的是下载依赖的安装包极慢
无论执行多少次,均无法顺利下载打包相关依赖(winCodeSign-2.6.0 nsis-3.0.4.1 nsis-resources-3.4.1 wine-4.0.1-macOS),执行打包操作。


1.下载安装包到本地
那如何下载这些包呢?要么耐心等,一遍又一遍的下载,要么翻墙
翻墙后,下载速度有明显提升。

安装包下载地址:
winCodeSign-2.6.0:
https://github.com/electron-userland/electron-builder-binaries/releases/download/winCodeSign-2.6.0/winCodeSign-2.6.0.7z

nsis-3.0.4.1:
https://github.com/electron-userland/electron-builder-binaries/releases/download/nsis-3.0.4.1/nsis-3.0.4.1.7z

nsis-resources-3.4.1:
https://github.com/electron-userland/electron-builder-binaries/releases/download/nsis-resources-3.4.1/nsis-resources-3.4.1.7z

wine-4.0.1-macOS:
https://github.com/electron-userland/electron-builder-binaries/releases/download/wine-4.0.1-macOS/wine-4.0.1-macOS.7z

安装包下载成功:

electron_builder_download

注意:macOS加上签名后,也可以打包成功,但是涉及到自动更新等需要身份认证的功能则不能用,也不能发布到macOS app store中。所以说经过代码签名的macOS包才是完整的包。


2.将安装包放到指定目录
找到electron-build目录:

1
2
3
4
5
6
7
// macOS electron目录
/Users/userName/Library/Caches/electron

// macOS electron-builder目录
cd /Users/userName/Library/Caches/electron-builder

open .

需要安装的依赖包如下:
electron_builder_win

将下载的安装包全部解压,按照图上格式放到electron-builder目录。


3.打包成功
执行npm pack:win,打包成功:

right_win_code


11.查看asar包内容

在electron中,asar是个特殊的代码格式。asar包里面包含了程序猿编写的代码逻辑
默认情况下,这些代码逻辑,是放置在resource/app目录下面的,明文可见,这样的话,也就有了代码加密(asar打包)的需求,默认electron是加密的,打包完成的安装包里都会有app.asar文件
windows的asar包在resouces/app里面:

asar_win

macOS的asar包在Contents/Resources里面:

asar_macOS

如何查看加密后的asar包呢?

1
2
3
4
5
// 全局安装asar
npm install -g asar

// 执行解压指令
asar extract app.asar ./app

解压成功:

asar_extract


12.通过伪协议唤起客户端

electron 支持注册自定义 url 协议,浏览器可通过伪协议这种 IPC 方式唤起本地的应用。
简单说就是类似从网页的百度网盘点击下载时弹出百度网盘客户端之类的东东
electron_protocols

点击打开BaiduNetdisk_macOS按钮,直接能唤起百度网盘客户端

逻辑分析:
1.安装应用时将协议注册/写入进主机中
2.监听用户打开携带应用协议的URL,并获取其中的参数
3.使用其中的参数来根据个人需求来进行不同使用

下面说下其electron实现:
1.macOS的实现
macOS的实现很简单,在package.json中的build里添加配置:

1
2
3
4
"protocols": [{
"name": "mlive",
"schemes": ["mlive"]
}],

2.windows的实现
windows的实现相对复杂一点,首先还是在package.json中的build里添加配置:

1
2
3
4
5
6
7
8
9
10
"nsis": {
"runAfterFinish": false,
"oneClick": true,
"permacOShine": true,
"allowElevation": true,
"allowToChangeInstallationDirectory": false,
"createDesktopShortcut": true,
"createStartMenuShortcut": false,
"include": "./installer.nsh"
},

重点是nsis的include属性,该属性是能将协议写入主机的脚本。
还有createDesktopShortcut属性,该属性是否创建桌面快捷方式。


项目根目录,创建installer.nsh文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
!macOSro customInstall
SetRegView 64
WriteRegStr HKCR "mlive" "" "MLIVE"
WriteRegStr HKCR "mlive" "URL Protocol" ""
WriteRegStr HKCR "mlive\shell" "" ""
WriteRegStr HKCR "mlive\shell\open" "" ""
WriteRegStr HKCR "mlive\shell\open\command" "" '"$INSTDIR\MLIVE.exe" "%1"'
SetRegView 32
WriteRegStr HKCR "mlive" "" "URL:mlive"
WriteRegStr HKCR "mlive" "URL Protocol" ""
WriteRegStr HKCR "mlive\shell" "" ""
WriteRegStr HKCR "mlive\shell\open" "" ""
WriteRegStr HKCR "mlive\shell\open\command" "" '"$INSTDIR\MLIVE.exe" "%1"'
!macOSroend
!macOSro customUnInstall
DeleteRegKey HKCR "mlive"
!macOSroend

文件大致意思是:
应用安装后将自定义mlive伪协议写入到注册表中,
应用卸载后将自定义mlive伪协议从注册表中删除。

至此两个系统都能通过mlive://的方式从浏览器唤起客户端。

mlive


13.从伪协议中获取参数

尽管通过协议,我们可以轻松唤起客户端,但更重要的是我们需要将协议后面的参数带入到electron中。那这个究竟该如何实现呢?
1.macOS的实现
首先参考一下electron官网faq的实例:

mlive_share_macOS

可以通过在主进程定义gloabl.sharedObject全局变量,然后在渲染进程中使用remote模块来访问它,来实现对主进程和渲染进程数据的共享。


明白上面知识点后,首先在app/index.js,electron主进程中实现以下代码:

1
2
3
4
app.on('open-url', (event, url) => {
event.preventDefault()
global.sharedObject = { url }
})

macOS下通过协议URL启动时,主实例会通过 open-url 事件接收这个 URL,然后将其放入全局变量中。

然后在src/App.tsx,electron渲染进程中实现以下代码:

1
2
3
4
5
6
7
8
9
10
11
useEffect(() => {
if (process.env.REACT_APP_RUNTIME_PLATFORM === 'electron') {
const shareObject = window.require("electron").remote.getGlobal("sharedObject")
if (shareObject) {
const url = shareObject.url
const params = url.substr(url.indexOf(':') + 3, url.length)
const data = Buffer.from(params, "base64").toString()
console.log('params:', JSON.parse(data))
}
}
}, [])

协议后面的参数是通过base64进行加密的,比如这样:

1
mlive://eyJhcHBpZCI6ICIzOTA0NzkxMjkiLCAidG9rZW4iOiAiZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SmxlSEFpT2pFMU9UTXdOell3T0RJc0luTjFZaUk2SWpNNU1EUTNPVEV5T1RveE5Ua3lPRGMwTkRnekluMC5kRDdzOV9CWHRoN2lXcXlDbW5xQ0VRUFV5OXRTX0ptaktSRWtzRU1Tenh3IiwgInJvb21faWQiOiAiNDk3MjFmMDM2NmRhNDBkNWFiZGIwOWQ2MWNmNjFmNmEiLCAidXNlcl9pZCI6ICI4NzA5NTU1NSIsICJyb29tX3R5cGUiOiAyLCAicm9sZSI6IDEsICJleHBpcmVfYXQiOiAxNTY4NDl9

获取完url后,需要将其解密出来,得到参数里的具体信息:

mlive_get_mac


2.windows的实现
windows无法从open-url回调中获取url,但是可以通过node 提供的process.argv获取得到运行时传递的参数。

mlive_windows_argv

直接在src/App.tsx,electron渲染进程中实现以下代码:

1
2
3
4
5
6
7
8
9
10
11
useEffect(() => {
const argvs = window.require("electron").remote.process.argv
if(argvs.length > 1) {
const url = argvs[1]
if(url) {
const params = url.substr(url.indexOf(':') + 3, url.length - 1)
const data = Buffer.from(params, "base64").toString()
console.log('params:', JSON.parse(data))
}
}
}, [])

注意点:
1.伪协议取process.argv中的第二项,因为第一项是当前包的全路径,并非伪协议。
2.通过process.argv获取到伪协议后,之前协议比如是mlive://xxx,会自动在后面加上一个/,变成mlive://xxx/,因此在截取url参数的时候,需要length-1。

获取完url后,需要将其解密出来,得到参数里的具体信息:

mlive_windows_get


从零搭建一个electron+hooks+ts项目,大致内容介绍完毕。下一篇将继续搭建electron+hooks+ts项目,尽情期待。(本篇重点electron技术栈,下篇重点react技术栈)