从源码解析Electron的安装为什么这么慢
前言
Electron作为一款跨平台的桌面应用端解决方案已经风靡全球。作为开发者,我们几乎不用关心与操作系统的交互,直接通过Web前端技术与Electron提供的API就可以完成桌面应用端的开发。
然而,为什么国内使用Electron的踩坑文章数不胜数,主要原因是Electron为了支持跨平台,为不同的操作系统平台进行了适配,将chromium内核与node集成到了一起,屏蔽了底层操作系统的细节,所以在不同的平台上有着不同的二进制基座。在开发的过程中,我们必须要下载对应的平台的基座,才能正常开发。也就是说,我们npm install electron -D
的时候,一定是下载了Electron的二进制基座的。那么这个下载的过程在哪里?为什么速度这么慢呢?本文将通过Electron的安装源码一一说明。
安装Electron
在安装之前,我们先模拟一下没有配置任何关于Electron二进制镜像的npm配置文件,在~/.npmrc
里面,只有一些默认的配置:
# ~/.npmrc文件
registry=https://registry.npm.taobao.org/
prefix=D:\Programs\nodejs\global_modules
cache=D:\Programs\nodejs\cache_modules
python=D:\Programs\Python39\python.exe
然后,创建一个名为electron-install-example
的文件夹作为本此测试的Demo项目目录,并在进入该目录后执行npm init
初始化node项目。
最后,使用命令行安装Electron:npm install electron -D
。在短暂的npm包安装后,我们会发现会卡在一个地方:
这时候,很多开发者就会开始在网络上搜索:'安装Electron卡住',并且也很容易得到解决方案:
在
~/.npmrc
文件中,单独设置Electron的镜像electron_mirror="https://npm.taobao.org/mirrors/electron/"
于是我们按照搜来的解决方案重新配置我们的.npmrc
文件:
# ~/.npmrc文件
registry=https://registry.npm.taobao.org/
prefix=D:\Programs\nodejs\global_modules
cache=D:\Programs\nodejs\cache_modules
python=D:\Programs\Python39\python.exe
# 单独设置Electron的镜像
electron_mirror="https://npm.taobao.org/mirrors/electron/"
设置完成后,重新进行npm install
,发现能够很快完成下载并继续开发。通过本文,我们深入细节,看看为什么Electron设置了单独的镜像后,就能够正常且快速完成下载安装。
深入下载细节
进入项目根目录下/node_modules/electron/
(后续除特殊情况外,提到的目录路径都是统一相对于项目根目录)目录中,查看package.json文件中的scripts脚本节点:
了解npm的朋友们知道,postinstall
中的脚本会在npm包完成安装后执行。
也就是说,npm install -D electron
完成以后,会在node_modules/electron
目录中立刻执行node install.js
。所以,我们进一步查看install.js文件,看看它到底执行了什么。核心代码如下:
代码特别容易理解:在没有缓存文件的时候,会使用@electron/get
提供的downloadArtifact
函数,进行Electron二进制制品的下载。
于是,我们又将目标转移到@electron/get
。这是个什么东西呢?查询官方仓库:官方仓库,就能够大概知道该工具的功能了:提供一定的参数来向远端下载文件。
找到@electron/get
的模块入口node_modules/@electron/get/dist/cjs/index.js
,也很容易从中找到downloadArtifact
的函数定义:
该函数的文档:下载Electron发行制品,并且返回下载后的制品的绝对路径。而函数内部主要流程如下:
解析要下载的制品对应的操作系统和平台。例如是Windows还是Linux,架构是x86还是AMD64。
解析要下载的制品的版本。
解析要下载的制品的具体文件名。例如要下载Windows下的64位的Electron制品,那么默认文件名称是:
electron-v11.0.2-win32-x64.zip
解析要下载的制品所在的远端URL是多少(与本文相关的重点)。
处理本地缓存。
本文主要解析下载以及从本地缓存制品两个环节。
远端下载的URL
从上面的源码图中,我们会看到远端的URL来自于artifact_utils_1.getArtifactRemoteURL(artifactDetails)
这个的返回,而该函数在@electron/get/dist/cjs/artifact-utils.js
中进行定义:
该函数的定义也不难,主要流程如下:解析得到base
变量,解析得到path
变量,解析得到file
变量,组合为${base}${path}/${file}
。当然,你也可以在mirrorOptions
中定义resolveAssetURL
函数来返回自定义的地址。
在上面的处理流程中,能够看到一个频繁出现的函数:mirroVar
。该函数也在该文件中定义,其函数定义如下:
function mirrorVar(name, options, defaultValue) {
// Convert camelCase to camel_case for env var reading
const lowerName = name.replace(/([a-z])([A-Z])/g, (_, a, b) => `${a}_${b}`).toLowerCase();
return (process.env[`NPM_CONFIG_ELECTRON_${lowerName.toUpperCase()}`] ||
process.env[`npm_config_electron_${lowerName}`] ||
process.env[`npm_package_config_electron_${lowerName}`] ||
process.env[`ELECTRON_${lowerName.toUpperCase()}`] ||
options[name] ||
defaultValue);
}
该函数主要返回参数name
相关的变量值,以name = 'mirror'
为例,获取过程为:
customDir
进行下划线分割转换,得到const lowerName = 'mirror'
(name为'customDir'则转换为'custom_dir')。
依次检查如下环境变量值:
- NPM_CONFIG_ELECTRON_MIRROR
- npm_config_electron_mirror
- npm_package_config_electron_mirror
- ELECTRON_MIRROR
- options['mirror']
上述任意变量存在值则直接使用,否则,使用默认值defaultValue
。
读到这里,也许有读者疑惑了,我明明是在.npmrc
文件中配置的ELECTRON_MIRROR
变量,而这里读取的明明是环境变量里面的值,怎么会有呢?如果直接使用node作为入口,那么确实不会有这些变量,但是通过npm运行就不一样了。这里用一个小例子来说明。
首先在一个node项目中编写一个脚本env-test.js:
console.log(process.env);
我们通过使用node运行该js脚本:
node env-test.js
看到命令行的输出,只会有当前机器的环境变量:
但是一旦通过npm进行运行,又会不一样:
运行命令npm run dev
,会得到如下的结果,这里本人使用IDEA的断掉调试,会更加清晰的看到env的值:
通过npm run
的方式,我们发现我们在~/.npmrc
文件中配置的一些参数,都能在这里得到,并且是以npm_config_
作为开头的。可能还有读者有疑惑,上面读取的变量,都是同意大小写的,这里是npm_config_ELECTRON_MIRROR
,能读取到吗?事实上,env的读取是忽略大小写的:
综合目前的研究,相信读者已经清楚了为什么通过配置ELECTRON_MIRROR在.npmrc
能够达到加快Electron二进制基座的下载速度的目的了,至于一些其他的配置变量,可以阅读附录的官方文档翻译。
本地缓存机制
有的读者看了上述的远端下载可能会说,我的机器就在内网环境,内网也没有镜像让我来写,我该怎么下载呢?实际上,@electron/get
也不会完全从远端下载制品。它在下载的过程,会优先进行本地缓存文件的查找,如果已经存在了缓存好的制品,自然也就不会从远端下载了。那么这个查找缓存的过程是怎样的呢?或者说,@electron/get
会从本地哪个目录去查找呢?让我们回到@electron/get/dist/cjs/index.js
脚本的downloadArtifact
函数中,看该部分:
在url
变量获取的下一行,构建了一个Cache缓存对象,继续往下,通过判断不进行强制从远端下载的标志,会进入getPathForFileInCache
函数返回一个本地的缓存文件路径,如果路径不为空则使用它。所以,我们只需要让这个函数能够返回一个合法的缓存文件路径就能让@electron/get
不进行远端下载,而是使用本地的缓存文件。所以我们跟到该函数中:
函数最终会使用上一节中的url
变量形成一个本地的缓存路径,至于代码中的url.format
以及filenamify
的效果,读者可以自行编写Demo验证。
最后,路径还使用到了this.cacheRoot
,查看Cache的构造函数,发现如果没有传递cacheRoot
,则使用defaultCacheRoot
,该值在该脚本文件上面有定义:
通过一段脚本输出该路径:
const env_paths_1 = require("env-paths");
const defaultCacheRoot = env_paths_1.default('electron', {
suffix: '',
}).cache;
console.log(defaultCacheRoot);
// 在本人的机器上输出:
// C:\Users\w4ngzhen\AppData\Local\electron\Cache
所以在Windows机器下,默认的缓存目录在~/AppData/Local/electron/Cache/
,在本人的机器上,已经缓存的文件如下:
源码个人认为也不用继续解析了,读者结合文件夹名称应该能够很容易分析。
附录:@electron/get 官方Wiki翻译
下载Electron发行版制品
使用
基础方式:下载一个Electron二进制ZIP
import { download } from '@electron/get';
// NB: Use this syntax within an async function, Node does not have support for
// top-level await as of Node 12.
const zipFilePath = await download('4.0.4');
进阶:下载macOS下带有调试符号的Electron文件
import { downloadArtifact } from '@electron/get';
// NB: Use this syntax within an async function, Node does not have support for
// top-level await as of Node 12.
const zipFilePath = await downloadArtifact({
version: '4.0.4',
platform: 'darwin',
artifactName: 'electron',
artifactSuffix: 'symbols',
arch: 'x64',
});
指定镜像
下列选项可以用来指定从其他的地方下载Electron资源:
mirrorOptions
Object(JavaScript对象)mirror
String (可选) - 下载资源的镜像地址的基础URL。nightlyMirror
String (可选) - Electron nightly-specific版本的镜像URL。customDir
String (可选) - 下载资源的目录名称,通常由版本号来设定。customFilename
String (可选) - 将要下载的资源的文件名称。resolveAssetURL
Function (可选) - 允许通过编程方式来进行资源下载的函数回调。
下载资源的URL进行如下的分解,每一项都来可以映射到mirrorOptions
:
Example:
import { download } from '@electron/get';
const zipFilePath = await download('4.0.4', {
mirrorOptions: {
mirror: 'https://mirror.example.com/electron/',
customDir: 'custom',
customFilename: 'unofficial-electron-linux.zip'
}
});
// 上述将会从如下URL下载:
// https://mirror.example.com/electron/custom/unofficial-electron-linux.zip
const nightlyZipFilePath = await download('8.0.0-nightly.20190901', {
mirrorOptions: {
nightlyMirror: 'https://nightly.example.com/',
customDir: 'nightlies',
customFilename: 'nightly-linux.zip'
}
});
// 上述将会从如下URL下载:
// https://nightly.example.com/nightlies/nightly-linux.zip
customDir
参数可以使用{{ version }}
占位符来设置版本(务必注意:{}
括号之间一定要有空格,否则会解析失败,即,{{[空格]version{空格}}}
),这个占位符将会由所下载的资源的版本(没有首字符v
)来动态替换。例如:
const zipFilePath = await download('4.0.4', {
mirrorOptions: {
mirror: 'https://mirror.example.com/electron/',
customDir: 'version-{{ version }}',
platform: 'linux',
arch: 'x64'
}
});
// 将会从如下的URL下载:
// https://mirror.example.com/electron/version-4.0.4/electron-v4.0.4-linux-x64.zip
使用环境变量来指定镜像选项
镜像配置选项也可以通过如下的环境变量来指定:
ELECTRON_CUSTOM_DIR
- 指定资源下载的自定义目录。ELECTRON_CUSTOM_FILENAME
- 指定资源下载的自定义文件名。ELECTRON_MIRROR
- 指定如果版本没有使用nightly的时候,服务器的下载URL。ELECTRON_NIGHTLY_MIRROR
- 指定如果版本使用nightly的时候,服务器的下载URL。
重写下载的资源版本
所下载的资源的版本可以通过设置``ELECTRON_CUSTOM_VERSION 环境变量来进行覆盖。设置该版本将会覆盖传入
download或是
downloadArtifact`函数的version参数。
它是如何运行的
下载Electron资源到操作系统中已知的位置,并且缓存该资源的模块,用于便于在将来请求同一个资源的时候能够立刻完成并返回。缓存路径如下:
- Linux:
$XDG_CACHE_HOME
or~/.cache/electron/
- MacOS:
~/Library/Caches/electron/
- Windows:
%LOCALAPPDATA%/electron/Cache
or~/AppData/Local/electron/Cache/
默认情况下,该模块使用 got
作为下载器。因此,您可以通过downloadOptions
使用与get
相同的选项(options)来进行下载。
进度条
默认情况下,下载工件超过30秒时会显示进度条。若要禁用,请将ELECTRON_GET_NO_PROGRESS
环境变量设置为任何非空值,或设置downloadOptions
中的quiet
为true
。如果您需要通过API自己监视进度,请设置downloadOptions
中的getProgressCallback
回调,其函数签名与got
的downloadProgress
event callback相同。
代理
下游软件包应利用 initializeProxy
功能来添加HTTP(S)代理支持。如果设置了环境变量ELECTRON_GET_USE_PROXY
,则会自动调用它。根据使用的Node版本,使用不同的代理模块.因此,设置代理环境变量的方式略有不同。对于Node 10及更高版本,使用global-agent
。否则,将使用global-tunnel-ng
。请参阅相应的链接模块以确定如何配置代理支持。
从源码解析Electron的安装为什么这么慢的更多相关文章
- Fabric1.4源码解析:客户端安装链码
看了看客户端安装链码的部分,感觉还是比较简单的,所以在这里记录一下. 还是先给出安装链码所使用的命令好了,这里就使用官方的安装链码的一个例子: #-n 指定mycc是由用户定义 ...
- Fabric1.4源码解析:链码实例化过程
之前说完了链码的安装过程,接下来说一下链码的实例化过程好了,再然后是链码的调用过程.其实这几个过程内容已经很相似了,都是涉及到Proposal,不过整体流程还是要说一下的. 同样,切入点仍然是fabr ...
- 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新
[原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...
- jQuery2.x源码解析(构建篇)
jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 笔者阅读了园友艾伦 Aaron的系列博客< ...
- jQuery2.x源码解析(设计篇)
jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 这一篇笔者主要以设计的角度探索jQuery的源代 ...
- 给jdk写注释系列之jdk1.6容器(4)-HashMap源码解析
前面了解了jdk容器中的两种List,回忆一下怎么从list中取值(也就是做查询),是通过index索引位置对不对,由于存入list的元素时安装插入顺序存储的,所以index索引也就是插入的次序. M ...
- Masonry1.0.2 源码解析
在了解Masonry框架之前,有必要先了解一下自动布局的概念.在iOS6之前,UI布局的方式是通过frame属性和Autoresizing来完成的,而在iOS6之后,苹果公司推出了AutoLayout ...
- vue UI库iview源码解析(2)
上篇问题 在上篇<iview源码解析(1)>中的index.js 入口文件的源码中有一段代码有点疑惑: /** * 在浏览器环境下默认加载组件 */ // auto install if ...
- iOS开发之Masonry框架源码解析
Masonry是iOS在控件布局中经常使用的一个轻量级框架,Masonry让NSLayoutConstraint使用起来更为简洁.Masonry简化了NSLayoutConstraint的使用方式,让 ...
随机推荐
- C++:继承
共有继承(public),私有继承(private),保护继承(protected): 1.public继承: 基类成员类型 作为派生类成员 在派生类中是否可见 对派生类对象的可见性 public p ...
- Spring系列之Mybatis动态代理实现全过程?回答正确率不到1%
面试中,可能会问到Spring怎么绑定Mapper接口和SQL语句的.一般的答案是Spring会为Mapper生成一个代理类,调用的时候实际调用的是代理类的实现.但是如果被追问代理类实现的细节,很多同 ...
- CUDA 矩阵乘法终极优化指南
作者:马骏 | 旷视 MegEngine 架构师 前言 单精度矩阵乘法(SGEMM)几乎是每一位学习 CUDA 的同学绕不开的案例,这个经典的计算密集型案例可以很好地展示 GPU 编程中常用的优化技巧 ...
- golang中的闭包_closure
闭包_Closure: 1.一般情况下,第一类对象都是独立的封闭的存在的,独立的封闭的起作用; 2.第一类对象可以被创建; 3.第一类对象可以作为参数传递给其他函数; 4.第一类对象可以赋值给变量实体 ...
- rune和byte在处理字符/字符串中的应用.
rune和byte在处理字符/字符串中的应用. 定义: rune是int32的别名,-2147483648->2147483647,常用来表示UNICODE字符集,可以用来处理包含中文/非中文的 ...
- .Net core 的热插拔机制的深入探索,以及卸载问题求救指南.
.Net core 的热插拔机制的深入探索,以及卸载问题求救指南. 一.依赖文件*.deps.json的读取. 依赖文件内容如下.一般位于编译生成目录中 { "runtimeTarget&q ...
- JS预编译过程
GO和AO 变量的预编译 实例1 console.log(a); var a=1; console.log(a); 实际编译过程: 将a存入预编译对象中,赋值为undefined: 真正的赋值语句当程 ...
- Apache Dolphin Scheduler - Dockerfile 详解
Apache DolphinScheduler 是一个分布式去中心化,易扩展的可视化 DAG 工作流任务调度系统.简称 DS,包括 Web 及若干服务,它依赖 PostgreSQL 和 Zookeep ...
- 动态规划精讲(一)A单串
单串 单串 dp[i] 线性动态规划最简单的一类问题,输入是一个串,状态一般定义为 dp[i] := 考虑[0..i]上,原问题的解,其中 i 位置的处理,根据不同的问题,主要有两种方式: 第一种是 ...
- 自己实现一个Controller——精简型
写在最前 controller-manager作为K8S master的其中一个组件,负责众多controller的启动和终止,这些controller负责监控着k8s中各种资源,执行调谐,使他们的实 ...