跨平台桌面开发王器Electron:安装过程深入解析

跨平台桌面开发王器Electron:安装过程深入解析_第1张图片

1.1 安装 Electron 依赖包

开发者往往通过 npm install(或 yarn add)指令完成为 Node.js 工程安装依赖包的工作,安装 Electron 也不例外,下面是 npm 和 yarn 的安装 Electron 依赖包的指令:

npm install electron --save-dev
yarn add electron --dev

官方推荐我们把 electron 依赖包安装为开发依赖(devDependencies),这实际上是为了将来制作应用程序安装包时,避免把 electron 包和其可执行文件包装两次。这部分内容后文我们会详细讲解。

如果你希望观察 npm install 指令的具体执行细节,可以为其添加两个参数,如下所示:

npm install electron --save-dev --timing=true --loglevel=verbose

通过以上指令安装 Electron 依赖包,你会观察到整个安装过程的执行情况日志,这里我截取几个重要的日志分析一下。

npm http fetch GET 200 https://registry.npm.taobao.org/electron 125ms

这是 npm 通过 http 协议获取 electron 包的注册信息的日志,请求上述地址(我用的是 npm 淘宝源)将得到一个 json 响应,json 中包含了 electron 的所有版本的版本信息,如果安装时我们没有为 electron 指定版本号,将安装最新的版本。

[email protected] postinstall D:\ElectronDeepDive\capture1\install\node_modules\electron
> node install.js

这是 Electron 依赖包安装完成后,npm 执行其依赖包内定义的 postinstall 钩子的日志。npm 包管理文档为 npm 包定义了一系列的钩子,postinstall 钩子会在 npm 包安装完成后被执行,除了 postinstall 钩子之外,常用的还有如下这些钩子:

  • preinstall 包安装之前执行;

  • postuninstall 包被卸载之后执行;

  • preuninstall 包被卸载之前执行;

  • poststart 当 npm start 执行后触发;

  • poststop 当 npm stop 执行后触发;

  • posttest 当 npm test 执行后触发;

详细的文档请参阅:

https://docs.npmjs.com/misc/scripts

postinstall 钩子定义在 Electron 包内的 package.json 中,代码如下:

"scripts": {
"postinstall": "node install.js"
}

install.js 程序是 Electron 包内的一个重要程序,用于下载 Electron 的可执行文件及相关资源,下一小节我们将讲解 Electron 可执行文件的下载过程。

1.2 下载 Electron 的二进制文件

在 install.js 中,程序获取了当前操作系统的版本,并通过如下代码下载 Electron 的二进制文件与相应的资源:

downloadArtifact({
version,
artifactName: 'electron',
force: process.env.force_no_cache === 'true',
cacheRoot: process.env.electron_config_cache,
platform: process.env.npm_config_platform || process.platform,
arch: process.env.npm_config_arch || process.arch
}).then(extractFile).catch(err => {
console.error(err.stack)
process.exit(1)
})

downloadArtifact 方法是 @electron/get 包提供的,这个包是 Electron 包依赖的一个 npm 包,由于自 npm 3.x 以来,npm 把包管理方式从嵌套结构切换到了扁平结构,所以 @electron/get 位于当前工程的 node_modules 目录的根目录下。

拓展:在 npm 3.x 以前,npm 的包管理方式是嵌套结构的,也就是说一个工程安装的依赖包位于当前工程根目录下的node_modules目录中,假设其中一个依赖包又依赖了其他 npm 包,我们假设这个依赖包叫做 packageA,那么它的依赖包会被安装在 packageA 目录的node_modules目录下,以此类推。以这种方式管理依赖包会导致目录层级很深,在 Windows 操作系统中,文件路径最大长度为 260 个字符,目录层级过深会导致依赖包安装不成功。并且不同层级的依赖中可能引用了同一个依赖包,这种结构也没办法复用这个依赖包,而且这种情况非常常见,造成了大量的冗余、浪费。

自 npm 3.x 以来,npm 的包管理方式升级为了扁平解构,无论是当前工程的依赖包还是依赖包的依赖包,都会被优先安装到当前工程的 node_modules 目录下,在安装过程中如果 npm 发现当前工程的 node_modules 目录下已经存在了相同版本的某个依赖包,那么就会跳过安装过程,直接让工程使用这个已安装的依赖包,只有在版本不同的情况下,才会在这个包的 node_modules 目录下安装新的依赖包。这就很好的解决了前面两个问题。但也引来了新的问题,直到 npm 5.x 引入了 package lock 的机制后,才解决了新的问题,这已超出了本文的讨论范围,详情请参阅:

https://docs.npmjs.com/configuring-npm/package-lock-json.html

另外,npm 判断两个依赖包是否版本相同,是有一套复杂的规则的,这也超出了本文的讨论范围,详情请参阅:

https://docs.npmjs.com/about-semantic-versioning

downloadArtifact 方法的入参是一个配置对象,对象的 force 属性标记着是否需要强制下载 Electron 的二进制文件,如果环境变量 force_no_cache 的值为"true"则无论本地有没有缓存,都会从 Electron 的服务器下载相应的文件。

配置对象的 version 属性是需要下载的 Electron 可执行程序的版本号,这个版本号就是定义在 Electron npm 包的 package.json 内的版本号。platform 属性是当前的操作系统的名称,可能的值为"darwin"、"win32"或"linux"等,arch 是你当前操作系统的架构,可能的值为"x32"或"x64",这些信息都是帮你确定下载什么版本的 Electron 可执行文件的。

上述信息最终被组装成的下载地址可能是如下的样子(其中版本号视真实情况而定):

https://github.com/electron/electron/releases/download/v9.2.0/electron-v9.2.0-win32-x64.zip

如果处于 windows 操作系统内,上述文件会被首先下载到如下目录中:

C:\Users\ADMINI~1\AppData\Local\Temp

这个目录是 Node.js 通过 os.tmpdir() 确定的。文件下载完成后,程序会把它复制到缓存目录中以备下次使用,这个机制极大的节省了开发者的时间成本,下一小节我们将深入讲解 Electron 安装过程中的缓存和镜像机制。

缓存完成后,上述代码中的 extractFile 回调方法被执行,此方法会把缓存目录下的二进制文件压缩包解压到当前 Electron 依赖包的 dist 目录下:

[project]\node_modules\electron\dist

除了下载 Electron 二进制文件的压缩包外,downloadArtifact 还单独下载了一个 SHASUMS256.txt 文件,这个文件内记录了 Electron 二进制文件压缩包的 sha256 值,程序会对比一下这个值与压缩包文件的 sha256 值是否匹配,以避免用户请求被截获,下载到不安全的文件的情况(这方面的效用只能说聊胜于无),或者是下载过程意外终止,文件数据不完整的情况。

1.3 缓存与镜像策略

上文中我们提到 Electron 的二进制文件压缩包下载成功后,会复制一份到缓存目录,以备下次使用。在 Windows 环境下,默认的缓存目录为:

C:\Users\Administrator\AppData\Local\electron\Cache

这是通过 Node.js 的 os.homedir() 再附加了几个子目录确定的。你可以通过设置 electron_config_cache 环境变量来提供用户自定义缓存目录,在命令行下临时设置这个环境变量的方式为:

> set electron_config_cache=D:\ElectronDeepDive\capture1\cache

如果你是通过编程的方式使用 @electron/get 包,那么也可以通过如下方式把环境变量的设置写到代码里:

process.env.electron_config_cache="D:\\ElectronDeepDive\\capture1\\cache"

如果你希望一劳永逸的解决这个问题,还可以把这个环境变量配置到操作系统中去,如下图所示:

跨平台桌面开发王器Electron:安装过程深入解析_第2张图片图 1-1 Electron 缓存目录环境变量设置

在国内网络环境不理想的情况下,安装 Electron npm 包十有八九会失败,这就是 Electron 的二进制文件压缩包难以下载成功导致的,知道了缓存目录的位置之后,你就可以先手动把 Electron 二进制包安放到相应的缓存目录中,这样再安装 Electron npm 包时就毫无阻滞了。

你可以从同事的电脑上拷贝相应版本的 Electron 二进制包,也可以从淘宝的镜像源手动下载 Electron 的二进制包,淘宝 Electron 镜像源的地址为:

https://npm.taobao.org/mirrors/electron/

下载好的压缩包和哈希值文件一定要按照如下路径放置在缓存目录里:

// 二进制包文件的路径
[你的缓存目录]/httpsgithub.comelectronelectronreleasesdownloadv9.2.0electron-v9.2.0-win32-x64.zip/electron-v9.2.0-win32-x64.zip
// 哈希值文件的路径
[你的缓存目录]/httpsgithub.comelectronelectronreleasesdownloadv9.2.0SHASUMS256.txt/SHASUMS256.txt

路径中 [你的缓存目录] 下的子目录的命名方式看起来有些奇怪,这其实就是下载地址格式化得来的(通过一个叫做sanitize-filename的工具库,去除了 url 路径中的斜杠,使得其能成为文件路径),在我的电脑上,这两个路径是如下形式:

跨平台桌面开发王器Electron:安装过程深入解析_第3张图片图 1-2 二进制包文件的路径

跨平台桌面开发王器Electron:安装过程深入解析_第4张图片图 1-3 哈希值文件的路径

细心的读者可能已经注意到了,我的路径并不是 github 的 url 地址格式化得来的,而是 taobao 的镜像源地址格式化得来的。下面我们就介绍一下如何设置第三方镜像源。

@electron/get 库下载 Electron 二进制文件包的地址被人为的分割成了三部分:

  • 镜像部分:https://github.com/electron/electron/releases/download/

  • 版本部分:v9.2.0/

  • 文件部分:electron-v9.2.0-win32-x64.zip

这三部分联合起来最终构成了下载地址,每个部分都有其默认值,也有对应的重写该部分值的环境变量:

  • 镜像部分的环境变量:ELECTRON_MIRROR

  • 版本部分的环境变量:ELECTRON_CUSTOM_DIR

  • 文件部分的环境变量:ELECTRON_CUSTOM_FILENAME

一般情况下,我们只需要设置镜像部分的环境变量即可,比如要设置淘宝的镜像源,只需要把 ELECTRON_MIRROR 的环境变量的值设置为 https://npm.taobao.org/mirrors/electron/ 即可,设置方式与设置缓存目录的环境变量方式相同,此处不再赘述。

1.4 在 bin 目录下注入命令

Electron 依赖包安装完成后,npm 会自动为其在 node_modules/.bin 路径下注入命令文件,不带扩展名的 electron 文件是为 linux 和 mac 准备的 shell 脚本,electron.cmd 是传统的 windows 批处理脚本,electron.ps1 是运行在 windows powershell 下的脚本。

命令文件中的脚本代码不多,以 electron.cmd 为例,我们简单解释一下:

@ECHO off
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
"%_prog%"  "%dp0%\..\electron\cli.js" %*
ENDLOCAL
EXIT /b %errorlevel%
:find_dp0
SET dp0=%~dp0
EXIT /b

其中~dp0 指执行脚本的当前目录,SET 是为一个变量赋值,%* 是执行命令时输入的参数,整段命令脚本的意思是用 node 执行 Electron 包内的 cli.js 文件,并把所有命令行参数一并传递过去。关于 windows 批处理的更多细节请参阅:

https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/windows-commands

细心的读者会发现,npm 并不会为所有的依赖包注入命令文件,而且即使注入了命令文件的包也不一定存在 cli.js 文件,比如 npm 就没有为 core-js 包注入命令文件,却为 Mocha 注入了两组命令文件,Electron 或者 Mocha 的独特之处在于它们的 package.json 里都有类似如下这样的配置(Mocha 为 bin 对象配置了两个属性,所以 npm 为其生成了两组指令文件):

"bin": {
"electron": "cli.js"
}

npm 之所以在 node_modules/.bin 路径下添加命令文件,是因为很多包的作者都希望自己的脚本能放置在用户的环境变量里。npm 为这个需求提供了便利。npm 在执行一段脚本前,比如:npm run dev,会先自动新建一个命令行环境,然后把当前目录的 node_modules/.bin 加入到系统环境变量中,接着执行 scripts 配置节指定的脚本的内容,执行完成后再把 node_modules/.bin 从系统环境变量中删除。所以当前目录的 node_modules/.bin 子目录里面的所有脚本,都可以直接用脚本名调用,而不必加上路径。当然你的项目的 package.json 里要配置了 dev 对应的指令,示例的配置代码如下:

"scripts": { "dev": "electron ./index.js" }

有了上面的配置,你就可以通过运行 npm run dev 命令来启动你安装过的 Electron 了。下面我们就来看看 Electron 包内的 cli.js 是如何启动 Electron 的。

1.5 使用命令启动 Electron

当开发者在当前项目下执行 npm run dev 时,其实就是执行 electron.cmd 批处理文件,并传入了一个命令行参数./index.js(这个文件我们还没有创建,不过没关系,在这一小节里这个文件并不是重点)。我们知道 electron.cmd 批处理指令就是用 node 执行了 node_modules\electron\cli.js 文件,同时也把命令行参数复制过去了。那么我们就看看 cli.js 的执行逻辑。

cli.js 中最重要的逻辑代码如下(为了便于理解,我对这段代码略有改动):

var proc = require('child_process')
var child = proc.spawn(electronExePath, process.argv.slice(2), {
stdio: 'inherit',
windowsHide: false
})

这段代码就是使用 Node.js 的 child_process 对象创建了一个子进程,让子进程执行 Electron 的可执行文件,并把当前进程的命令行参数传递给了这个子进程。命令行参数之所以从第三位开始取,是因为按照 Node.js 的约定,process.argv 的第一个值为 process.execPath,第二个值为正被执行的 JavaScript 文件的路径,所以第三个值才是我们需要的./index.js。

值得注意的是 cli.js 文件的首行代码:

#!/usr/bin/env node

这行代码是一个 Shebang 行,是类 Unix 平台上的可执行纯文本文件中的第一行,通过 #! 前缀后面的命令行告诉系统将该文件传递给哪个解释器以供执行。虽然 Windows 不支持 Shebang 行,但因为这是 npm 的约定,所以这一行代码仍然是必不可少的。

至于 Electron 的可执行程序是如何接收这个参数,如何执行这个参数指向的程序文件的,我们后文会有详细描述。

1.6 Electron 的版本管理方式

自 Electron 2.0.0 以来,Electron 的版本管理方式遵循 semver 的管理规则,semver 是语义化版本规范(https://semver.org/lang/zh-CN/)的一个实现,这是一个由 npm 的团队维护的版本管理规范,它实现了版本和版本范围的解析、计算、比较。

semver 的版本号内容分为主版本号、次版本号和修订号三个部分,中间以点号分割,版本号递增规则如下:

  • 主版本号:当做了不兼容的修改时递增;

  • 次版本号:当做了向下兼容的功能性更新时递增;

  • 修订号:当做了向下兼容的问题修正时递增。

Electron 则在这个约束的前提下增加了如下递增规则:

跨平台桌面开发王器Electron:安装过程深入解析_第5张图片

推荐大家使用稳定状态的最新版本的 Electron,如果已经安装了老版本的 Electron 或者发现 Electron 有可用的更新(关注 Electron 官网的发布页面可获得更新信息),大家可以使用如下指令更新本地工程的 Electron 版本:

npm install --save-dev electron@latest

Electron 团队承诺只维护最近的三个大版本,比如本文发稿时 Electron 最新版本为 v9.2.0,那么 Electron 团队只会维护 v9.x.x,v8.x.x,v7.x.x。v6.x.x 则不再维护,当 v10.x.x 发布之后,v7.x.x 也不再维护了。而且目前 Electron 版本发布相当频繁,平均一到两周就会有一个新的稳定版本发布,大量的更新不仅仅带来了更多的新功能、解决了更多的问题,也意味着你所使用的版本即将成为无人理睬的版本了,这也是为什么我推荐大家紧跟官方团队版本发布步伐的原因。

我们通过 npm 包管理工具安装的 Electron 依赖包都是稳定版本,除稳定版本外,Electron 团队还维护着 beta 版本和 nightly 版本,这是 Electron 团队和一些激进的开发者的演武场,除非特别需要,不推荐在商业项目中使用这些版本。

另外,Electron 官方 github 仓储的 issue 页面也时常会置顶一些重要更新事项,与社区的开发者一起讨论更新方案的细节,等最终方案敲定后,则逐步推动更新落地。比如近期非常重要的移除 remote 模块的更新需求,就是置顶在这个页面的。这是开发者持续关注官方动向的最佳途径,一旦发现有不赞成(deprecate)的内容或破坏性(breaking)的更新被置顶在这个页面,就应该尽量在项目当中避免使用它们。

RECOMMEND

推荐阅读

跨平台桌面开发王器Electron:安装过程深入解析_第6张图片

《Electron实战:入门、进阶与性能优化》一书,以实战为导向,讲解了如何用Electron结合现代前端技术来开发桌面应用。不仅全面介绍了Electron入门需要掌握的功能和原理,而且还针对Electron开发中的重点和难点进行了重点讲解,旨在帮助读者实现快速进阶。

作者是Electron领域的早期实践者,项目经验非常丰富,本书内容得到了来自阿里等大企业的一线专家的高度评价。

本书遵循渐进式的原则逐步传递知识给读者,书中以Electron知识为主线并对现代前端知识进行了有序的整合,对易发问题从深层原理的角度进行讲解,对普适需求以实践的方式进行讲解,同时还介绍了Electron生态内的大量优秀组件和项目。

 

点击链接了解详情并购买

更多精彩回顾

书讯 |9月书讯(上)| 开学季,读新书

上新 | 一本书带你吃透Nginx应用与运维
书单 | 开学季——计算机专业学生必读的10本畅销经典

干货 | 巨详细!使用OpenCV和OpenVINO轻松创建深度学习应用

收藏 | 从被动到主动,换个角度看DB

赠书 | 【第19期】追MM的各种算法

跨平台桌面开发王器Electron:安装过程深入解析_第7张图片

点击阅读全文购买

你可能感兴趣的:(跨平台桌面开发王器Electron:安装过程深入解析)