2026年Node项目依赖瘦身:5个可删除的npm包及替代方案
记得我刚写 Node 项目的时候新项目第一件事就是npm i axios dotenv rimraf uuid chalk。这几个包几乎成了标配HTTP 请求、环境变量、删目录、生成 ID、终端配色每一项都像“必须自己装”才能干活。但到了 2026 年再回头看你项目里的package.json你会发现这套“经典组合”已经变得很冗余——不是这些包不好而是 Node 和浏览器把该干的事都干完了而且干得足够好。这篇文章我想聊 5 个现在完全可以移除的 npm 包以及它们背后的官方替代方案。适合正在维护 Node/前端项目、想给项目瘦身、或者新开始一个项目不想再把node_modules堆成山的开发者。我会尽量把原理、代码、踩坑点都说清楚让你看完可以直接照着改。1. 为什么要清理依赖2026 年的原生能力地图1.1 原生运行时已经替你“安装”了什么先抛开具体包名看一个更本质的问题为什么以前我们离不开这些 npm 包因为很长一段时间里Node 和浏览器的标准库确实“缺货”。没有fetch的时候要发 HTTP 请求只能拼http模块或者上axios没有递归删除目录能力的时候只能靠rimraf没有生成 UUID 的标准 API 时只能用第三方包终端 ANSI 颜色更是没有官方 API只能手写转义字符。但这几年原生 API 补得非常快。到 2026 年主流 JavaScript 运行时的能力已经足够覆盖很多日常场景Node 18 开始fetch成为内置全局函数不用再引入node-fetch更不用为了舒服一点引入axios。Node 20.6 开始.env文件可以靠node --env-file直接加载。Node 14.14 开始fs.rm和fs.rmSync就支持递归删除目录了。Node 14.17 开始crypto.randomUUID()就能直接生成标准 UUID v4。Node 20.12 开始util.styleText()给终端彩色输出提供了官方入口。这不是什么“未来趋势”而是今天都可以直接用的能力。2026 年的项目如果再为一两个 API 去引入一个全新的依赖树性价比真的很低。1.2 删依赖不是炫技是维护成本的现实问题很多人觉得“多一个包有什么大不了”但npm install拉回来的不是一个文件而是一个依赖树。举个真实例子axios本身看起来不重但如果你项目里还混着老版本follow-redirects、form-data、proxy-from-env这些传递依赖npm audit报出来的一堆漏洞其实就是这些边角料。你未必真的用了它们但因为装了整个包就必须一起承担升级压力。依赖越多等于把一部分代码正确性外包给了别人。你需要持续关注上游版本的 breaking change需要忍受npm audit里真假难辨的告警需要在每次npm ci时多花时间解析依赖。而当你需要的功能只是“发个 GET 请求”“读一下.env”“删掉 dist 目录”的时候这些代价就显得尤其不划算。我并不是说所有第三方库都该删。像 React、Vue、Express 这类框架级依赖删掉它们意味着重写整个项目不现实。我们要清理的是那些“标准库已经覆盖”的工具型依赖删完之后不影响功能代码反而更好掌控。2. 5 个可以删掉的 npm 包与官方替代方案2.1 axios用原生 fetch 加一个小封装axios是这个列表里最值得聊的。它曾经是前端发请求的事实标准也确实是好工具自动 JSON 转换、请求/响应拦截器、取消请求、超时处理……但问题在于浏览器和 Node 现在都有原生fetch而这些高级能力中最常用的部分自己写一个很薄的小封装就能覆盖。原生fetch和 axios 最大的体验差异有四个fetch不会自动把响应转成 JSON需要手动res.json()。fetch默认没有超时需要结合AbortController。fetch对非 2xx 状态码不会抛异常需要手动判断res.ok。fetch没有response.data拿到的是一整个Response对象。这些差异很好解决。我在项目里一般抽一个request.jsasync function request(url, { method GET, body, headers {}, timeout 5000 } {}) { const controller new AbortController(); const timer setTimeout(() controller.abort(), timeout); try { const response await fetch(url, { method, headers: { Content-Type: application/json, ...headers, }, body: body ? JSON.stringify(body) : undefined, signal: controller.signal, }); if (!response.ok) { const error new Error(HTTP ${response.status}: ${response.statusText}); error.status response.status; error.response response; throw error; } const text await response.text(); return text ? JSON.parse(text) : null; } catch (error) { if (error.name AbortError) { throw new Error(Request timeout after ${timeout}ms: ${url}); } throw error; } finally { clearTimeout(timer); } } export { request };这个封装做下来不到 30 行日常 CRUD 完全够用。如果团队需要拦截器可以用函数组合的方式统一处理 token、日志、错误上报不必依赖 axios 的拦截器机制。什么情况下可以保留 axios如果你的项目里有大量历史代码依赖它或者你需要非常复杂的取消机制、上传下载进度、自定义适配器迁移成本大于收益。但从 2026 年新项目来看直接删掉axios用原生fetch起步是一个很舒服的起点。2.2 dotenv用node --env-file直接加载环境变量dotenv是我以前每个 Node 项目的必装包因为.env文件解析确实方便。但现在 Node 已经内置了这个能力不需要再为它加依赖。最推荐的方式是在启动命令里声明node --env-file.env src/index.js这样启动应用时Node 会把.env文件里的KEYVALUE加载到process.env。和dotenv的默认行为一致如果系统环境变量里已经存在同名变量以系统环境变量为准文件里的值不会覆盖它。如果你需要在代码内部显式加载也可以用process.loadEnvFileimport { loadEnvFile } from node:process; loadEnvFile(.env); console.log(process.env.DATABASE_URL);loadEnvFile从 Node 20.12 开始提供在 2026 年的主流 LTS 版本上运行毫无压力。迁移的时候要注意以前dotenv可以让你在任何位置调用require(dotenv).config()而原生方案更倾向于在进程启动阶段加载。如果你有特殊逻辑比如根据环境变量动态选择加载不同的.env文件可以在入口文件顶部调用loadEnvFile。还有一个常见坑如果你只是在 npm script 里写了dev: node src/index.js改成dev: node --env-file.env src/index.js时要确保 CI 或生产环境也有对应的.env文件。如果某些环境没有.env文件会启动报错。Node 22 提供了一个--env-file-if-exists参数允许“有就加载没有就跳过”适合灵活的部署场景。2.3 rimraf用fs.rm做跨平台递归删除rimraf的流行原因很单纯早期 Node 没有原生递归删除 APIfs.rmdir对非空目录会报错。但现在完全没必要再用它了因为fs.rm和fs.rmSync已经解决了这件事。如果你想在代码里删除dist目录import { rm } from node:fs/promises; await rm(dist, { recursive: true, force: true });同步版本import { rmSync } from node:fs; rmSync(dist, { recursive: true, force: true });很多项目并不是在代码里调用 rimraf而是在package.json的 scripts 里写scripts: { clean: rimraf dist }这个也可以直接改成scripts: { clean: node -e \require(node:fs).rmSync(dist, { recursive: true, force: true })\ }这样在 Windows、macOS、Linux 上都能正常工作完全跨平台。recursive: true表示递归删除目录里的内容force: true表示目标不存在时不报错。这两个参数加起来基本就是rimraf最常见的使用方式。需要特别提醒的是删除目录是危险操作不要在脚本里写rmSync(/, ...)也不要把路径变量拼错。建议在删除前先打印一下要删的路径或者至少用path.resolve把路径限制在项目根目录下。2.4 uuid用crypto.randomUUID()生成标准 UUID v4以前做数据库主键、会话 ID、临时文件名第一个念头可能都是npm i uuid。现在的原生 API 已经覆盖了最常用的 v4 场景。Node 端import { randomUUID } from node:crypto; const id randomUUID(); // 例6b8e3e8e-1a9f-4e2e-9e62-3d8e9128f4a6浏览器端const id crypto.randomUUID(); // 要求 HTTPS 或 localhost 环境注意crypto.randomUUID()不是Math.random()的替代它使用系统提供的 CSPRNG加密安全伪随机数生成器。对于 Token、密钥这类需要不可预测性的场景用Math.random()是错误选择但用crypto.randomUUID()是正确的。迁移成本非常低uuid包 v4 输出也是标准 UUID 格式和原生randomUUID()完全一致。如果你之前保存过 UUID 到数据库不需要做任何格式转换直接替换生成逻辑即可。唯一要注意的兼容性问题是浏览器crypto.randomUUID()要求安全上下文如果你的站点部署在普通 HTTP 上可能拿不到这个 API。但 2026 年的现代项目基本全站 HTTPS这个问题影响很小。万一真的需要在非 HTTPS 环境生成 UUID还是可以用uuid包作为 fallback。2.5 chalk用util.styleText做终端彩色输出终端彩色输出以前绕不开chalk。它提供了很优雅的链式写法import chalk from chalk; console.log(chalk.red(error)); console.log(chalk.bgGreen.black(success));但是 Node 从版本 20.12 / 21.7 开始提供了util.styleText()基本覆盖了最简单、最常用的终端样式需求。import { styleText } from node:util; console.log(styleText(red, error)); console.log(styleText(green, success));如果需要组合样式可以传数组console.log(styleText([bold, red], Fatal error));这跟chalk.red.bold()的效果是等价的。为什么这一项在 2026 年特别值得提因为新版 Node LTS 已经普遍跑在 22、24 上styleText早已不是实验性功能。从维护角度删掉chalk能少一棵依赖树从实际体验看如果你只是想让终端里的错误和信息有点颜色原生能力完全够。当然chalk在复杂场景依然有优势模板字符串、256 色/真彩色支持、链式嵌套、自动检测 TTY 等。如果你的 CLI 工具要做非常复杂的交互式输出保留chalk没问题。但对于应用项目里的“log 红一下绿一下”styleText已经足够了。3. 迁移实操如何安全地从 package.json 里移除这些包3.1 迁移前的依赖核查清单不管替换方案多简单直接npm uninstall都可能出问题。我在动手之前会先做一次“依赖体检”主要看三件事。第一直接依赖和传递依赖。用npm ls axios dotenv rimraf uuid chalk可以看到这些包到底在依赖树里出现在哪里。如果某个包只是作为传递依赖被别的包引入而你直接卸载根目录的包不一定能让依赖树变干净。我们要删除的是“我们自己在代码里import的那个”。第二代码里的引用点。只靠搜索关键词可能漏掉。我习惯先用grep -r from axios src/批量查一遍再顺手看下npm ls --depth0。如果项目里有axios封装文件迁移后会失效提前标记出来。第三npm scripts 里的命令。很多工具依赖并不是写死在代码里而是在package.json的 scripts 里使用比如rimraf dist、dotenv -e .env node ...。这类引用光搜import搜不到要重点检查 scripts 字段。3.2 分模块替换而不是一键删除我的建议是一次只替换一个包提交一次跑一遍测试再继续下一个。不要上午把所有包换完下午项目跑不起来了才发现不知道哪个改动出问题。具体顺序可以是先换uuid因为它最简单搜出所有uuid.v4()改成randomUUID()测试删除。再换rimraf改 scripts测试。接着换dotenv改启动参数。最后处理axios和chalk因为这两个可能涉及代码逻辑需要多跑几轮回归。替换代码时不要怕改动多。例如把 axios 全局实例替换成自己封装的时候原来request.get(/api/user)这种调用可能需要改成request(/api/user)。如果项目里有很多页面都引用了 axios 实例建议先封装一层兼容函数而不是让所有调用点一次性改完。3.3 CI/CD 与锁文件处理迁移不只是改本地代码。CI/CD 里的 Dockerfile 或工作流脚本如果直接跑npm run build通常没问题但如果你在 CI 里用了类似npx rimraf的命令或者显式安装了dotenv-cli那也要一起改。删除包之后一定要重新生成锁文件npm uninstall axios dotenv rimraf uuid chalk npm install或者手动编辑package.json后执行npm install让package-lock.json同步更新。如果不重新生成锁文件别人拉代码执行npm ci时可能会报 lock 文件和 package.json 不匹配。提交后最好做一次干净环境验证删掉整个node_modules用npm ci重新安装然后跑一遍npm run build npm test。这一步能确认项目真正摆脱了这几个包。4. 常见问题与排查技巧实录4.1 版本与兼容性速查表迁移时最常遇到的问题是“我的 Node 版本到底够不够”。下面这张表建议直接收藏要删的包官方替代能力最低要求axios原生fetchNode 18 / 现代浏览器dotenv--env-fileNode 20.6dotenvloadEnvFileNode 20.12rimraffs.rm/fs.rmSyncNode 14.14uuidcrypto.randomUUIDNode 14.17 / 浏览器安全上下文chalkutil.styleTextNode 20.12 / 21.7如果你的项目还在维护 Node 16 或更老的版本那确实不建议强行迁移先升 Node 再说。2026 年还跑在 Node 16 上本身的安全风险比依赖包更大。4.2 工作区里还有“幽灵依赖”一个很容易被忽略的情况是你把axios从项目的直接依赖里删了但项目里的某个子包或 scripts 还在使用它而且因为 Node 的node_modules扁平化结构代码依然能跑起来。这种“幽灵依赖”特别坑因为本地能跑不代表新环境下还能跑。解决办法是删包之前先尽量找出所有引用点删包之后用npm ls axios确认依赖树里已经没有任何相关包。如果只是留在锁文件里的传递依赖可以视情况通过npm dedupe或升级父依赖来处理。4.3npm : 无法加载文件 ... npm.ps1这类报错要不要管这个话题虽然不直接是“删包”的问题但确实和 npm 环境有关。Windows 上运行npm命令如果报无法加载文件 ...npm.ps1因为在此系统上禁止运行脚本通常不是 npm 本身坏了而是 PowerShell 执行策略限制。解决方式很简单以管理员身份打开 PowerShell执行Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser然后再执行npm命令。这个操作和本文的迁移无关但如果你的团队里有同事在做完npm uninstall后遇到类似的 PowerShell 脚本问题可以顺手提醒一句。4.4 实用避坑心得除了技术细节还有几个我踩过坑的经验值得分享。不要为了“删包”而删包。原生fetch虽然好用但 axios 还有取消请求、上传进度、拦截器等成熟 API。如果项目里大量依赖这些高级能力强行换成 fetch 封装会让代码变得复杂。我更推荐新项目直接用原生能力老项目则按模块逐步迁移。删包后要更新团队的开发习惯。项目里少了dotenv新同事可能还会习惯性地写下import dotenv/config。建议在项目 README 的“开发环境”部分写清楚启动命令是node --env-file.env ...让所有人保持一致。要注意依赖瘦身不是一劳永逸。你把axios删了过段时间换个新功能又有人安装一个类似库这是常态。我的做法是在 PR 模板里加一项“是否真的需要新增依赖”提醒自己和团队先想想标准库能不能解决。写在最后说实话我第一次看到 Node 内置fetch、内置styleText、内置loadEnvFile的时候第一反应是“那我以前安装的那些包算什么”。后来想明白了第三方包的作用本来就是填补生态空白。当原生 API 补上来了我们作为开发者也应该适时把维护成本卸下来。我个人在实际操作中的体会是删包最难的不是写替代代码而是改变团队的习惯。我现在的新项目模板已经是零axios、零dotenv、零rimraf、零uuid、零chalk了。每次有同事准备npm i axios的时候我会让他先写一个 fetch 小封装5 分钟搞定之后他就再也没回头。2026 年的 JavaScript 生态已经足够强大给你的项目做一次减负收益远比想象中明显。