大家好,我是 xy。
前端包管理器又多了一个新选择,名字挺好记:把 npm 的 n 翻过来变成 u,就是 upm。
它用 TypeScript 编写,体积约 256 KB。官方介绍里还写了一句很吸引人的话:安装速度与 Rust 编写的包管理器相当。
npm、pnpm、Bun 都已经有了,它还能玩出什么新东西?
什么是 upm?
upm 是一个运行在 Node.js 上、面向 npm 仓库的轻量级包管理器。项目里的依赖照常安装,不需要换一套生态。
官方列出的特点很直接:
🟦 纯 JS: 使用 TypeScript 和 Node.js 内置能力构建,不需要额外安装其他运行时。 🪶 小巧: 磁盘占用约 256 KB,压缩后约 85 KB,足够小,可以直接打包进自己的工具。 🚀 速度快: 官方称,安装速度可以与 Rust 编写的包管理器相当,实际表现取决于项目和运行环境。 🧩 可编程: 每个命令都有对应的 JavaScript 函数,可以直接导入自己的程序。 🎯 简单: 不额外引入一套配置方式,沿用 .npmrc中的仓库、认证等配置。🔒 默认安全策略: 设置 1 天的版本发布等待期,并跳过依赖的生命周期脚本。 📦 兼容锁文件: 支持读取现有的 package-lock.json、pnpm-lock.yaml和bun.lock。
注意,256 KB 指 upm 工具本身,不包含 Node.js,也不是说项目的 node_modules 能缩到这么小。它优化的是安装依赖的过程,不能直接理解成项目运行、构建也会跟着提速。
为什么还要做一个包管理器?
作者在文档里专门回答了这个问题,对现有工具的评价也挺直接:
npm: 随 Node.js 一起提供,但他觉得安装速度慢。 pnpm: 速度快、设计好,不过体积、功能和可配置项都在不断增加。 Yarn Berry: 引入了不少自己的概念和使用要求,变得复杂。 Bun、Deno、Nub: 很快,但作者认为,同时采用它们的运行时会更合适。
这些是作者的取舍,不代表上述工具不能用于 Node.js 项目。
他想继续用 Node.js,又希望工具足够小、安装足够快,还能方便地嵌进其他程序。upm 就是从这几个需求出发的。
Node.js 已经有的能力,直接用
工作线程、zlib、fetch、文件系统,Node.js 已经提供了这些能力,没必要再随包管理器带上一整套实现。
upm 同时会在多个项目之间共享缓存,通过硬链接减少文件复制,并保存安装状态。依赖没有变化,重复安装时就尽量少做事。
所以它虽然用 TypeScript 编写,下载、解压、文件操作也不是全部拿 JavaScript 从头实现。真正能省下时间的,还有重复下载、复制和检查这些环节。
不想每次装包,都启动一个子进程
另一个出发点是 JavaScript API。
开发脚手架、项目生成器时,安装依赖往往需要单独启动命令,再处理输出和退出状态。作者希望把这些操作直接做成函数,工具本身也足够小,能够一起打包进去。
upm 最初就是一个实验:充分利用 Node.js,看看纯 TypeScript 的 npm 客户端能做到多小、多快。
如何使用?
需要 Node.js 22.3 +,官方提供的 npm 安装命令如下:
npm i -g upm --min-release-age 0
先建一个空项目,试试常用操作:
mkdir upm-demo
cd upm-demo
npm init -y
# 添加项目依赖
upm add vue@^3
# 添加开发依赖
upm add --dev vitest
# 安装依赖,也可以写成 upm i
upm install
# 删除依赖
upm remove vitest
项目里已经定义了 build 脚本,就可以用 upm run build 执行。它还带了一个 upx,用来临时执行 npm 包提供的命令,必要时先安装对应的包:
upx cowsay hello
使用 upm 自己的锁文件时,记得把 upm.lock 提交到仓库。CI 中执行:
upm install --frozen-lockfile
锁文件缺失或与项目不一致时直接报错,不会重新选择依赖版本。
已有项目,注意这几个地方
切换前,官方建议先清理旧的 node_modules,避免继续引用残留的包。原来的锁文件可以保留,但此时 upm 只负责按它安装,增删依赖仍需使用原工具。包含工作区、补丁、Git 或 file 依赖的外部锁文件,目前不支持。
另外,upm 目前还是预发布版本,默认不执行依赖的 install、postinstall 等脚本。项目中有靠这些脚本编译或下载文件的包,需要单独检查。前面说的“一天等待期”,也不限制已经锁定或明确指定的精确版本。
写脚手架的,可以重点看看 JS API
在自己的工具项目里安装 upm 依赖后,安装项目依赖就可以写成一次函数调用:
import { install } from 'upm';
await install({
dir: './my-project',
log: (message) => console.log(message),
});
函数会返回安装结果,进度和警告通过 log 接收。添加包、解析版本、生成锁文件、执行脚本,也都有对应接口。
比如做一个 Vue 项目脚手架,用户选好模板之后,生成文件、安装依赖、处理错误、输出启动提示,就能接在同一个程序里。
upm 还支持可插拔存储后端,可以接入团队共享缓存或键值存储,不必只使用默认的本地缓存。
只想日常装包,先拿一个小项目试试就够了。正在写脚手架、项目生成器的同学,可以多看看 JavaScript API 这一节:除了换一条安装命令,upm 还把装包这件事做成了一套可以直接调用的接口。
官网: https://upm.sh文档: https://upm.sh/docsGitHub: https://github.com/unjs/upm