如何用 deno 加快 npm 镜像源的切换

转存失败,建议直接上传图片文件

dnrm

deno 实现的 nrm,每次切换源都在 100ms 内,速度超级快

npm 镜像源工具速度对比

微信截图_20230622154203.png

动机

我在日常的项目管理需要频繁地切换 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
deno install --allow-read --allow-write --allow-env --allow-net -rfn dnrm https://deno.land/x/dnrm/mod.ts
deno install --allow-read --allow-write --allow-env --allow-net -rfn dnrm https://deno.land/x/dnrm/mod.ts

如果你装了 node,却没有安装过
deno ?

npx deno-npx 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
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
# 注意: 这种使用方式仍然很慢 (切换时间在 200ms 左右),因为加载 deno 垫片需要时间,不过仍然比其他的包快很多 
npm i deno-nrm -g
# 注意: 这种使用方式仍然很慢 (切换时间在 200ms 左右),因为加载 deno 垫片需要时间,不过仍然比其他的包快很多 npm i deno-nrm -g

2. 本地安装

  1. 下载该项目到本地

  2. 在项目根目录下执行命令

deno task install
deno task install
deno task install

cli

# 查看当前源
dnrm
# 切换 taobao 源
dnrm use taobao
# 查看所有源
dnrm ls
# 测试所有源
dnrm test
# 设置源在本地
dnrm use taobao --local
# 查看帮助
dnrm -h
# 查看版本号
dnrm -V
# 查看当前源
dnrm

# 切换 taobao 源
dnrm use taobao

# 查看所有源
dnrm ls

# 测试所有源
dnrm test

# 设置源在本地
dnrm use taobao --local

# 查看帮助
dnrm -h

# 查看版本号
dnrm -V
# 查看当前源 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")
console.log("hello, world")
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 包
import { exists } from "https://deno.land/std@0.192.0/fs/exists.ts"; 

// 最终解析脚本只会解析 exists.ts 及其依赖的脚本,而不会解析一整个 fs 包
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
}
type Foo {
    bar uint32 // 这个字段的访问速度速度更快
    jack uint32
}
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);
}
// 常规热路径
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);
}
// 常规热路径 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 { 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 { 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/mod.ts"; 
// × 不应该这么做,这会解析 fs 下所有的模块
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 包
import { exists } from "https://deno.land/std@0.192.0/fs/exists.ts"; 

// √ 最终解析脚本只会解析 exists.ts 及其依赖的脚本,而不会解析一整个 fs 包
import { exists } from "https://deno.land/std@0.192.0/fs/exists.ts"; // √ 最终解析脚本只会解析 exists.ts 及其依赖的脚本,而不会解析一整个 fs 包

以上就是 dnrm 内部做的所有优化,可以前往 ? dnrm 查看详情,如果喜欢,欢迎 star,也欢迎 issuepr ?

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY3RRWnB' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片