如何用 deno 加快 npm 镜像源的切换
npm 镜像源工具速度对比
动机
我在日常的项目管理需要频繁地切换 npm
的镜像源。
而 nrm 的 bug
很多,速度很慢,并且已经不维护了;新一点的 nnrm 和 mini-nrm 也基本都需要 2s 以上的切换时间。
这在需要频繁的切换镜像源的场景下,体验非常糟糕。
所以有了 dnrm,一个 deno 实现的 nrm,每次切换源都在 100ms 内,速度超级快,开发体验拉满。
使用
安装
1. 模块安装
deno install --allow-read --allow-write --allow-env --allow-net -rfn dnrm https://deno.land/x/dnrm/mod.ts
npx deno-npx install --allow-read --allow-write --allow-env --allow-net -rfn dnrm https://deno.land/x/dnrm/mod.ts
在一些不想装 deno 的临时场景下 👇
# 注意: 这种使用方式仍然很慢 (切换时间在 200ms 左右),因为加载 deno 垫片需要时间,不过仍然比其他的包快很多
npm i deno-nrm -g
2. 本地安装
-
下载该项目到本地
-
在项目根目录下执行命令
deno task install
cli
# 查看当前源
dnrm
# 切换 taobao 源
dnrm use taobao
# 查看所有源
dnrm ls
# 测试所有源
dnrm test
# 设置源在本地
dnrm use taobao --local
# 查看帮助
dnrm -h
# 查看版本号
dnrm -V
优化原理
接下来进入正文,具体说说里边用到的优化原理。
deno
首先我们用了 deno,在绝大多数情况下,deno 的冷启动比 nodejs 要快。
例如简单执行 👇
console.log("hello, world")
runtime | ms |
---|---|
deno | 37 |
nodejs | 48 |
另外是 deno 允许我们引入依赖的具体某个模块,而不需要引入具体的依赖再引入模块,减少了解析脚本的时间。
例如 👇
import { exists } from "https://deno.land/std@0.192.0/fs/exists.ts";
// 最终解析脚本只会解析 exists.ts 及其依赖的脚本,而不会解析一整个 fs 包
正则匹配
npm
的配置文件 .npmrc
其实是个类 .env
文件,这意味着你可以用类似 dotenv 的解析器来解析配置,但这带来了序列化和反序列化的时间消耗。
而在 dnrm 中直接进行正则匹配来读取和写入配置,同时不需要引入任何依赖,速度超级快。
热路径查询
在 go 语言中,因为结构体第一个字段的地址跟结构体的指针是相同的,所以第一个字段的访问速度比其他字段要快,我们称它为 hot path
例如 👇
type Foo {
bar uint32 // 这个字段的访问速度速度更快
jack uint32
}
而在 js
当中是没有这种规则的,但我们仍然可以先预设一个常用的对象字段来处理。
// 常规热路径
export const hotUrlRegistrys: Record<
string,
string
> = {
"https://registry.npmjs.org/": "npm",
"https://registry.npmmirror.com/": "taobao",
};
// 镜像源
export const registrys: Registrys = {
npm: "https://registry.npmjs.org/",
yarn: "https://registry.yarnpkg.com/",
github: "https://npm.pkg.github.com/",
taobao: "https://registry.npmmirror.com/",
npmMirror: "https://skimdb.npmjs.com/registry/",
tencent: "https://mirrors.cloud.tencent.com/npm/",
};
// 镜像的 key
export const registryKeys = Object.keys(registrys);
// 获取镜像源
export function getConfigRegistry(configText: string) {
const [url = ""] = registryReg.exec(configText) || [];
// 热路径先处理,多数情况下可以跳过循环
return hotUrlRegistrys[url] ??
registryKeys.find((k) => registrys[k] === url);
}
直接配置替换
多数的 npm
镜像源切换工具喜欢调用子进程来执行 npm config set registry=...
,这会跑超级多的 npm
内部分支,也是卡的主要原因。
dnrm 直接读写目标配置文件,省去了这部分开销。
按需处理
配置文件
多数情况下,我们只是想简单地看看目前是什么镜像源,这时不需要创建文件了,可以直接跳过这个步骤
参数解析
cli
命令行的参数解析是超级费时间的,特别是你需要有一个友好的 help
信息或者参数校验时。
但我们在日常使用当中,这些都是低频的操作,所以也应该做按需 👇
import { getConfig } from "./src/config.ts";
import {
printListRegistrys,
printListRegistrysWithNetworkDelay,
printRegistry,
} from "./src/registrys.ts";
if (import.meta.main) {
const { args } = Deno;
// 简单的使用应该提前执行,并避免耗时的参数解析和模块加载
if (args.length === 0) {
const { configRegistry } = await getConfig();
printRegistry(configRegistry);
Deno.exit(0); // 执行成功后直接退出,跳过参数解析
}
// ....
// 复杂的查看 help 信息和参数校验,应该后置并按需导入以提高性能
const { action } = await import("./src/cli.ts");
await action();
}
按需加载依赖
像上边的例子 import("./src/cli.ts")
来按需引入参数解析模块,如果没有用到,则可以免去该模块及其背后依赖的解析时间。
另一个就是刚刚讲 deno
时说的,我们可以引入依赖内部对应的模块 👇
import { exists } from "https://deno.land/std@0.192.0/fs/mod.ts";
// × 不应该这么做,这会解析 fs 下所有的模块
import { exists } from "https://deno.land/std@0.192.0/fs/exists.ts";
// √ 最终解析脚本只会解析 exists.ts 及其依赖的脚本,而不会解析一整个 fs 包
以上就是 dnrm 内部做的所有优化,可以前往 👉 dnrm 查看详情,如果喜欢,欢迎 star
,也欢迎 issue
和 pr
😋
转载自:https://juejin.cn/post/7247324653839073339