结论
推荐大家有条件的话还是上科技,配置仓库总会因为其同步策略的时延或者各种各样的同步故障导致你使用的时候并没有那个版本的依赖,如果要用的话那就不要选最新版本,而是倒推几个版本。
即便是这样,也有可能存在恰巧有故障的可能,还是不要过于信任仓库,出了问题不要当作electron 版本bug
配置方式
我最常用的两个仓库是 阿里云
和 华为云
(mirrors.huaweicloud.com/electron/) 了,这两者我推荐阿里云,就electron
而言其同步效率比华为云
好一些。
注意: 很多教程都只告诉你配置electron_mirror,然而这种方式是错误的,因为官方目前的逻辑时 各版本的存储路径是带有v前缀 的,而私服大多都是仅有版本号,如果不配置这个变量的话,你下载是会报 404、401等各种错误的,详细介绍参见 electron 配置代理的介绍 ,也可以看后面的分析过程
.npmrc
阿里云配置
.npmrc
electron_mirror=https://npmmirror.com/mirrors/electron/
electron_custom_dir={{ version }}
华为云配置
.npmrc
electron_mirror=https://mirrors.huaweicloud.com/electron/
electron_custom_dir={{ version }}
env
properties
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/
ELECTRON_CUSTOM_DIR={{ version }}
分析过程
- 本文编写时间: 2024年1月31日
- 本文所用相关工具/依赖:
- npm: 10.2.4
- nodejs: v20.11.0
- yarn: 1.22.21
- pnpm: 8.15.1
- electron@26.6.7
安装Electron
的时候会去动态下载其运行时,而默认策略又是从Github 仓库地址 https://github.com/electron/electron/releases/download/
下载, 我们访问不太稳定,经常容易因为网络问题无法成功,在没有科技或者仓库的情况下经常发现以下报错:
从上图分析可知,安装是在执行electron
的脚本 node install.js
时失败的,我们打开项目 .\node_modules\electron\install.js
脚本发现执行了以下逻辑:
js
#!/usr/bin/env node
// ....
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);
});
由于引入的时候没有指定具体的js,因此我们打开.\node_modules\@electron\get\cjs\index.js
发现执行了如下代码(别问为什么是cjs不是esm下的,我对node的导包不咋了解):
js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
// ....
const artifact_utils_1 = require("./artifact-utils");
// ....
async function downloadArtifact(_artifactDetails) {
// ....
// 拼接地址
const url = await artifact_utils_1.getArtifactRemoteURL(artifactDetails);
const cache = new Cache_1.Cache(artifactDetails.cacheRoot);
// ....
return await utils_1.withTempDirectoryIn(artifactDetails.tempDirectory, async (tempFolder) => {
const tempDownloadPath = path.resolve(tempFolder, artifact_utils_1.getArtifactFileName(artifactDetails));
const downloader = artifactDetails.downloader || (await downloader_resolver_1.getDownloaderForSystem());
d(`Downloading ${url} to ${tempDownloadPath} with options: ${JSON.stringify(artifactDetails.downloadOptions)}`);
// 执行下载
await downloader.download(url, tempDownloadPath, artifactDetails.downloadOptions);
await validateArtifact(artifactDetails, tempDownloadPath, downloadArtifact);
return await cache.putFileInCache(url, tempDownloadPath, fileName);
});
}
打开 .\node_modules\@electron\get\cjs\artifact-utils.js
查看代码逻辑:
js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("./utils");
const BASE_URL = 'https://github.com/electron/electron/releases/download/';
const NIGHTLY_BASE_URL = 'https://github.com/electron/nightlies/releases/download/';
function getArtifactFileName(details) {
utils_1.ensureIsTruthyString(details, 'artifactName');
if (details.isGeneric) {
return details.artifactName;
}
utils_1.ensureIsTruthyString(details, 'arch');
utils_1.ensureIsTruthyString(details, 'platform');
utils_1.ensureIsTruthyString(details, 'version');
return `${[
details.artifactName,
details.version,
details.platform,
details.arch,
...(details.artifactSuffix ? [details.artifactSuffix] : []),
].join('-')}.zip`;
}
exports.getArtifactFileName = getArtifactFileName;
function mirrorVar(name, options, defaultValue) {
// Convert camelCase to camel_case for env var reading
const snakeName = name.replace(/([a-z])([A-Z])/g, (_, a, b) => `${a}_${b}`).toLowerCase();
return (
// 1.1 使用 npm/yarn/pnpm install 执行时,默认从`用户目录下/.npmrc`读取配置并加前缀 `npm_config_`
// 具体配置文件可以通过给 install 加参数指定,各种包管理工具也有其生效、覆盖规则,在此不展开了
// .npmrc
process.env[`npm_config_electron_${name.toLowerCase()}`] ||
process.env[`NPM_CONFIG_ELECTRON_${snakeName.toUpperCase()}`] ||
process.env[`npm_config_electron_${snakeName}`] ||
// 1.2 如果 .npmrc 未配置仓库 则检查 `package.json`的 脚本中有没有传递变量,由于 `electron` 运行的是 `node install.js`,自然读取不到变量,跳过
// package.json
process.env[`npm_package_config_electron_${name}`] ||
process.env[`npm_package_config_electron_${snakeName.toLowerCase()}`] ||
// env
// 1.3 如果以上两种途径都没有配置仓库地址,此时还可以设置系统变量来解决,
// 变量名称为`ELECTRON_MIRROR`、`ELECTRON_CUSTOM_DIR`等,设置方式根据自己的系统自行百度
process.env[`ELECTRON_${snakeName.toUpperCase()}`] ||
// 1.4 由于 `electron` 运行的是 `node install.js`,执行逻辑中未传递 `mirror`变量,跳过
options[name] ||
// 1.5 如果还是没有读取到仓库配置,则按调用方传递的默认值进行兜底处理
defaultValue);
}
async function getArtifactRemoteURL(details) {
const opts = details.mirrorOptions || {};
// 1. 读取镜像仓库配置
let base = mirrorVar('mirror', opts, BASE_URL);
if (details.version.includes('nightly')) {
const nightlyDeprecated = mirrorVar('nightly_mirror', opts, '');
if (nightlyDeprecated) {
base = nightlyDeprecated;
console.warn(`nightly_mirror is deprecated, please use nightlyMirror`);
}
else {
base = mirrorVar('nightlyMirror', opts, NIGHTLY_BASE_URL);
}
}
// 2. 读取版本分级路径
const path = mirrorVar('customDir', opts, details.version).replace('{{ version }}', details.version.replace(/^v/, ''));
// 3. 读取依赖包名称
const file = mirrorVar('customFilename', opts, getArtifactFileName(details));
// Allow customized download URL resolution.
if (opts.resolveAssetURL) {
const url = await opts.resolveAssetURL(details);
return url;
}
// 4. 拼接完整地址
return `${base}${path}/${file}`;
}
上述分析过程基于
yarn install --prefer-offline
进行 debug
延伸学习
node
技术栈读取/配置环境变量的方式有以下几种,
纯 node
使用 node --env-file=.env test.js
传递,
.env 配置文件格式为:
KEY=VALUE
package.json
在不使用框架的情况下, 一个基础的 node
项目也可以通过在配置命令的时候传递变量 或者 变量文件使用,即:package.json
的 scripts
节点:
js
"scripts": {
# 需要安装依赖 cross-env,此方式未验证
"dev": "cross-env KEY1=VALUE1 node test.js",
"test": "node --env-file=.env test.js"
}
本例的
electron
下载运行时并不适用,这个方式仅用于给项目本身配置变量
环境变量
可以当前shell 或者修改配置文件,仅举例
ini
# windows powershell
$env:AA=1
# linux
export AA=1
others
在使用vue-cli
或者 vite
等其它工具时,该框架一般也会有自己的变量配置和读取规则,例如 vue-cli
会要求你的 .env
配置文件 变量基本要以 VUE_APP_
作为前缀才能读取(参考链接(模式和环境变量 | Vue CLI (vuejs.org))