简介

因为国内的网络环境,我们构建 Electron 项目时,容易失败,通常遇到这种情况,我便粗暴的使用科学上网的方式来解决,对 Electron 安装过程中发生了什么一无所知,为了增强把控感,本文便来分析一下安装 Electron 时,发生了什么。

容易安装失败的原因

通常,我们会根据官方提示,通过下面方式来构建 Electron 开发环境:

npm init -y
npm install electron --save-dev

先提一下 --save-dev。一般为工程项目安装 Electron 依赖时,都会将其安装为开发依赖 (--save-dev),当我们后续通过 electron-builder 打包时,electron-builder 会将生产依赖复制到安装包内,用户安装后,应用目录下会有相应的 node_modules 目录,安装为生产依赖的包会被放置在这个目录下,对于 Electron 而言,electron-builder 在打包时,会帮我们下载目标平台的 Electron 可执行文件,比如你要打包到 win32 平台,那么 electron-builder 就会去下载 win32 对应的 Electron 可执行文件并将其放置到正确的位置,如果我们以生产依赖安装 Electron,那么我们打出的包中,通常会有两份 Electron,一份是 electron-builder 为你准备的,一份在 node_modules 中,用户不需要多出的 Electron 文件,而这也增大了包的体积。

如果你没有任何科学上网的设置,那么在安装过程中,很容易出 504 (Gateway Time-out) 的错误,但我们安装其他 npm 包时,却不容易出现,为什么?

因为,Electron 是复合型的依赖包,与大多数 Node.js 依赖包不同,安装 Electron 的过程中,npm 还额外执行了 Electron 依赖包中配置文件定义的脚本代码,这些脚本会去 Github 仓库内下载相应的 Electron 可执行文件,这些文件都在国外的网站,大小在 80M 左右,因为国内的网络环境,就容易出现 504 的问题。

复合型依赖包的实现

Electron 是怎么将自己弄成一个复合型依赖包的?通过下面命令再安装一下并将安装过程的日志打印出来,因为刚刚使用 npm 安装过,目录脏了,最好清空目录文件,重新再来一次。

rm -rf *
npm init -y
npm install electron --save-dev --timing=true --loglevel=verbose

--timing=true --loglevel=verbose 会将安装的细节打印出来,从输出中可以发现,npm 一开始会通过 http fetch 从 https://registry.npmmirror.com/electron (因为配置过环境,所以你打印出了的不一定是这个 URL,但返回内容是一样的) 中获取一个 json 文件,这个 json 文件中中记录着 Electron 所有历史版本,用于校验用户要安装的版本,如果用户没有指定版本,则从该 json 文件中获取最新的版本进行安装。

npm 会将 Electron 相关的包文件和依赖从 npmmirror 服务器下载到 node_modules 目录中,npm 工具会检查 nodel_modules\electron\package.json,查看文件中是否有配置 npm 钩子脚本。

进一步讨论前,先理一下 npm 钩子的知识,很多人不知道,我们可以在 package.json 的 scripts 中定义 npm 钩子,这些钩子会在 npm 安装依赖包时被 npm 执行,npm 执行的钩子函数有:

  • preinstall: 依赖包安装之前执行

  • postinstall: 依赖包安装之后执行

  • preuninstall: 依赖包卸载之前执行

  • postuninstall: 依赖包卸载之后执行

  • poststart: 当 npm start 执行后触发

  • poststop: 当 npm stop 执行后触发

  • posttest: 当 npm test 执行后触发

看到 nodel_modules\electron\package.json 中的 scripts,如下图所示:

从图中可知,scripts 中有 postinstall,其对应的脚本为 node install.js,npm 安装完 Electron 依赖包后,会执行 node install.js,如下图所示:

简单而言,npm 安装完 Electron 依赖后,npm 还需要继续执行 install.js,install.js 会去下载 Electron 可执行文件,我们前面看见的安装错误,通常在这里抛出。

在 install.js 中,下载 Electron 可执行文件的关键方法如下:

const { downloadArtifact } = require('@electron/get');// downloads if not cached
downloadArtifact({version,artifactName: 'electron',force: process.env.force_no_cache === 'true',cacheRoot: process.env.electron_config_cache,checksums: process.env.electron_use_remote_checksums ? undefined : require('./checksums.json'),platform,arch
}).then(extractFile).catch(err => {console.error(err.stack);process.exit(1);
});

downloadArtifact 方法由 @electron/get 提供,@electron/get 是 Electron 包依赖的 npm 包(从 electron 的 package.json 便可知),而 @electron/get 也有相应的依赖,npm 会通过递归的方式去安装这些依赖。

在 npm3.x 之前,npm 通过嵌套式的文件结构来管理依赖包。例如,当前工程中安装了 package A 包,这个包又依赖于 package B,此时 npm 会在 package A 目录下创建 node_modules 文件夹并将 package B 放在其中,这种方式可能会出现多次下载同一依赖包的情况,比如多个包都依赖 package B,那么多个包的 node_modules 下都会有 package B(即多次下载),但却可以避免让人头疼的依赖版本问题,比如都依赖 package B,但依赖 package B 的版本不同,此时不同的目录天然的解决了版本问题。

但这里有个致命的问题便是 Windows 系统限制了文件路径的最长长度为 260 字符,npm3.x 之前的这种方式可能会出现嵌套很深的目录结构,从而被 Windows 限制,最终导致依赖包无法安装成功。

npm3.x 之后,npm 改为扁平结构,即无论是当前工程的依赖包,还是依赖包的依赖包,都会优先安装到当前工程的 node_modules 中,如果安装过程中,npm 发现了当前工程的 node_models 已经存在相同版本的依赖包,则跳过安装,只有在版本不同的情况下,才会安装新的依赖包。

npm 扁平结构处理依赖包的方式解决了重复安装和目录树过深的问题,但引出了依赖包版本冲突的问题,在 npm5.x 引入了 package lock 机制来解决这个问题,随着 npm 机制的复杂化,也出现了新的包管理工具,如 yarn,挑战当前 npm 的地位。

将注意力放回 downloadArtifact 方法,该方法会传入 JSON 对象作为参数,JSON 对象中的字段含义很直观,其中 force 属性表示是否要强制下载 Electron 二进制文件,如果当前程序的环境变量 (process.env) 的 force_no_cache 为 true,则无论本地是否有缓存,都会从 Electron 服务器下载相应的文件。

这个 JSON 对象最终组成一个 URL,用于下载相应版本的 Electron,其具体的逻辑在 node_modules/@electron/get/dist/cjs/index.js 的 downloadArtifact 方法中,除了下载 Electron 二进制文件,该方法还会下载 SHASUMS256.txt 用于校验 sha256,避免下载文件被篡改。

镜像与缓存

阅读 Electron 中关于安装的文档:https://www.electronjs.org/docs/latest/tutorial/installation

我们可以通过设置下载镜像或先下载 Electron 作为缓存文件来跳过 install.js 的下载。

先看缓存文件的解法,不同系统,存放缓存文件的位置不同:

  • Linux: ~/.cache/electron/ 或 $XDG_CACHE_HOME

  • MacOS: ~/Library/Caches/electron/

  • Windows: ~/AppData/Local/electron/Cache 或 $LOCALAPPDATE/electron/Cache

因为我使用 Windows 系统,所以缓存文件夹如下图所示:

上图中,因为我过去也使用过 electron,所以不太干净,阅读 Electron 文档,目前最新的形式是 [checksum]/[filename] 的形式,如下:

├── a91b089b5dc5b1279966511344b805ec84869b6cd60af44f800b363bba25b915
│   └── electron-v15.3.1-darwin-x64.zip

缓存文件夹中有 Electron 后,再次通过 npm 安装 electron 时,如果版本相同,便会跳过下载 Electron 的环节(除非设置了强制下载的环境变量)。

客观而言,这种方式还是有点麻烦的,每次下载不同版本的 Electron 都需要手动操作一下,所以我建议使用镜像的方式,既然国外网站慢,那么就通过国内镜像网站下载,其唯一的缺点可能就是不那么实时(最新版本可能需要等待镜像网站同步)。

相比于通过 npm 命令在本地设置使用的镜像,更建议使用.npmrc 的方式来设置镜像,创建.npmrc 文件,在其中输入如下内容:

electron_mirror=https://cdn.npm.taobao.org/dist/electron/
ELECTRON_BULLDER_BINARIES_MIRROR=http://npm.taobao.org/mirrors/electron-builder-binaries/
registry=https://registry.npm.taobao.org

npm 会基于.npmrc 中的内容从指定的镜像地址下载资源,在团队开发时,将.npmrc 加入 git 管理,这样团队中的每个人,都会使用相同的镜像环境去下载 Electron。

此外,更重要的一个意义是,通过.npmrc 我们实现了环境变量的共享,这样在写代码时,涉及环境变量的逻辑也是一致的。

运行 Electron

安装完 Electron 后,在项目的 package.json 中添加如下脚本:

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

然后创建 index.js 并添加如下代码:

const {app, BrowserWindow} = require('electron')let win
app.whenReady().then(() => {// 新建窗口win = new BrowserWindow({width: 800,height: 600,})// 加载网站win.loadURL('http://hackpython.com')
})

在命令行工具中输入 npm run dev,Electron 便会正常启动并载入相应的网站,效果如下:

至此,Electron 便安装完成了。

npm run dev 的背后

为什么通过 npm run dev 命令可以拉起 electron?背后的原理是啥?

当我们运行 npm run dev 指令时,npm 会新建一个命令环境并将当前项目下的 node_modules/.bin 目录加入到这个命令环境的环境变量中,然后再执行 scripts 配置中 dev 节点对应的命令,命令执行完后,会将 node_modules/.bin 从环境变量中删除。

在当前项目的 node_modules/.bin 目录中,有 3 个与 Electron 有关的文件:electron、electron.cmd、electron.ps1,这些文件都会加入命令环境中的环境变量中,所以我们可以执行 electron ./index.js,该命令的其背后其实是 electron.cmd ./index.js(Windows 系统中可以省略.cmd 扩展名)。

3 个文件中,electron 为 Linux 或 MacOS 提供,electron.cmd 是 Windows 批处理脚本,electron.ps1 是 Windows powershell 脚本。

我们通过 npm 安装 Electron 时,npm 处理会检查 npm 钩子脚本外,还会检查 package.json 中是否配置 bin 指令,Electron 的 package.json 中有 bin 指令,其内容如下:

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

npm 发现这个配置后,会自动在 node_modules/.bin 目录中注入上面提及的 3 个 Electron 相关文件。

以 electron 脚本为例(shell 命令易读一些),其代码如下:

#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")case `uname` in*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esacif [ -x "$basedir/node" ]; thenexec "$basedir/node"  "$basedir/../electron/cli.js" "$@"
else exec node  "$basedir/../electron/cli.js" "$@"
fi

electron 脚本的作用是,通过 Node.js 执行 Electron 依赖包中的 cli.js 文件,并将用户输入的所有命令都传递给 cli.js,简单而言 electron ./index.js 相当于 node /node_modules/electron/cli.js ./index.js,这便是我们执行 npm run dev 时,背后真正执行的命令。

cli.js

cli.js 才是启动 Electron 的关键,cli.js 中,关键代码如下:

const proc = require('child_process');// 创建子进程,通过子进程运行electron可执行文件,并将命令参数传递给electron
const child = proc.spawn(electron, process.argv.slice(2), { stdio: 'inherit', windowsHide: false });
child.on('close', function (code, signal) {if (code === null) {console.error(electron, 'exited with signal', signal);process.exit(1);}process.exit(code);
});

cli.js 中,利用 Node.js 的 child_process 库新建一个子进程,让子进程执行 Electron 的可执行文件,并将当前进程命令中参数传递给子进程,注意,传递的参数从第 3 位开始取,其中第 1 个参数:Node.exe 路径,第 2 个参数:JS 文件路径,即 cli.js 路径,这两个参数没有意义,所以不用传递给 electron,而第 3 个参数是./index.js,需要传递给 electron。

至此, electron.exe 变会去解释执行 inde.js 中的 JS 代码了,后面的逻辑也都交由 electron 完成。

结尾

这便是 electron 安装时背后的细节以及通过 npm 启动 electron 时背后的细节,后续会分享更多 electron 的文章,希望大家喜欢。

我是二两,下篇文章见。

安装 Electron 的门道相关推荐

  1. 如何在64位Ubuntu16.04系统下安装electron

    一.Electron简介 Electron[官网https://electronjs.org/]是由Github开发,基于Chromium和Node.js,使用JavaScript.HTML和CSS等 ...

  2. 2021 npm安装Electron失败解决方法

    npm直接安装Electron时,直接报如下错误 PS D:\Git\0.Repo\0.Gitee\electron-third-party-applications-notify> npm i ...

  3. npm安装electron时报Error: EPERM: operation not permitted, scandir.....

    刚弄完vue脚手架,准备安装electron写个桌面应用,运行命令vue add electron-builder一直报错 Error: EPERM: operation not permitted, ...

  4. npm安装electron时卡死 ,而利用cnpm安装electron时失败(等一系列幺蛾子)

    按照这个教程 ,安装cnpm 并执行 cnpm install -g electron 时出现 post install failure (部署失败) 原因是cnpm只会安装electron的js脚本 ...

  5. 安装electron项目的时候老是报错:Command failed.

    问题描述 安装electron项目的时候,在下载包的时候报错 错误描述主要是 错误1:socket hang up error E:\theia\node_modules\electron: Comm ...

  6. Electron教程2_Windows上安装Electron

    操作系统要求 Windows 7 以上 安装Nodeji V10 以上 安装Electron 克隆项目仓库 git clone https://github.com/electron/electron ...

  7. 安装electron时安装失败解决

    错误描述: 在安装 electron 的时候,使用官方推荐的如下命令: npm install --save-dev electron 结果报错如下 npm ERR! code 1 npm ERR! ...

  8. cnpm安装Electron

    cnpm安装Electron 国内使用npm安装Electron可能会报错,所以使用cnmp安装Electron 安装npm:brew install node 安装cnpm:npm install ...

  9. npm安装electron

    先安装cnpm,再安装electron npm install cnpm -g --registry=https://registry.npm.taobao.org /usr/local/bin/bi ...

最新文章

  1. C#协变和逆变 - 译
  2. python 如何把小数变成百分数格式
  3. java xwork_xwork-core-2.3.4源码 - 源码下载|通讯/手机编程|J2ME|源代码 - 源码中国
  4. android 手机内存其他文件夹里,手机储存里的其他是什么以及应该怎样删除
  5. 赫夫曼树编码的算法及应用习题--数据结构
  6. 华中数控预计继续亏损 拟靠关联交易增收存疑
  7. “/etc/profile“ 无法保存 E212: Can‘t open file for writing
  8. 【电子书】Head First Java
  9. 上万网友自发利用“蔚蓝地图”报污染源
  10. 040-云E办_学习和安装FastDFS以及安装Nginx
  11. Crazy Binary String
  12. 百度地图滑动后获取屏幕的中心点百度坐标
  13. 基于数据指纹的增量式
  14. 群晖Bitwarden内网穿透https反向代理
  15. 国内开源镜像网站汇总
  16. Java新手小白入门篇 项目 - 深海杀手
  17. android系统 通知,Android中通知的使用-----Notification详解
  18. spring boot车辆管理系统毕业设计源码031034
  19. 分享一些Linux下监控软件
  20. 黎明觉醒火种测试服务器维护,黎明觉醒3月25日开启火种测试

热门文章

  1. android商城首页布局,Android使用分类型RecyclerView仿各大商城首页
  2. 功利性地去多读书(一年300本书)
  3. ILSpy中baml转化为xaml的改进(五)
  4. 怎样提升工单管理效率?
  5. 一位北大博士的自白:假如大学可以重来,我一定这样上!
  6. 灰度共生矩阵及matlab实现
  7. 基于腾讯云实时音视频(TRTC)的web端 多人人脸识别小游戏
  8. 弘辽科技:抖音最大对手,有了新变化
  9. amt是什么意思_变速箱中的at、mt和amt是什么意思啊?
  10. Pygame:动画精灵