likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 19
如何用 deno 加快 npm 镜像源的切换

dnrm

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

npm 镜像源工具速度对比

如何用 deno 加快 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

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

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. 本地安装

  1. 下载该项目到本地

  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")
runtimems
deno37
nodejs48

另外是 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,也欢迎 issuepr 😋

转载自:https://juejin.cn/post/7247324653839073339
评论
请登录