ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

2026年值得删除的5个npm包:用原生API替代axios、lodash等

2026/9/19 5:01:34 拓冰建站 浏览量
2026年值得删除的5个npm包:用原生API替代axios、lodash等 1. 为什么 2026 年是时候清理这些臃肿依赖了先说个现象我最近接手了一个维护了三年的中后台项目第一件事不是看业务代码而是打开package.json扫了一眼 dependencies。好家伙总共 87 个直接依赖其中至少有 10 个属于“放进去之后就没再管过”的状态。更典型的是项目里明明用的是原生fetch但 axios 还在明明只用了lodash的三个方法整个包却安安静静躺了 20MB 的 node_modules 里。这件事让我认真思考了一个问题到了 2026 年前端生态里到底还有多少包是我们习惯性安装、但事实上已经从“必需品”变成“心理安慰”的如果你在前端行业待过三五年应该能感觉到最近两年的趋势——浏览器原生能力在疯狂追赶第三方库。ES 规范每年更新TypeScript 类型越来越完善现代浏览器的内置 API 覆盖面已经远超 IE 时代。2026 年这个时间点很多过去“必须引入”的 npm 包其实已经可以从项目里彻底删掉了。这篇文章我会直接列出 5 个我认为可以明确删除的 npm 包每个包都给出为什么可以删、用什么替代、迁移时需要注意什么。目标读者是那些正在维护中大型前端项目、想给依赖做一次“瘦身手术”的开发者。如果你刚从后端转前端或者还在用 jQuery 时代的思维方式写代码这篇文章也能帮你建立一套“能用原生就绝不引包”的判断力。2. 五个可以放心删除的 npm 包逐一拆解2.1 axios —— 原生 fetch 已经足够覆盖 95% 的请求场景axios 大概是前端历史上最成功的 HTTP 客户端库之一。我见过很多项目哪怕只是一个登录接口和一个数据列表接口也要先npm install axios。但站在 2026 年回头看axios 的核心优势——Promise API、请求拦截器、响应拦截器、JSON 自动序列化——这些能力原生fetch配合少量封装代码完全可以实现。你可能会问axios 不是还有取消请求、超时控制、上传进度这些功能吗拆开看超时控制AbortControllerAbortSignal.timeout()原生支持不需要引入任何包。取消请求同样是AbortControlleraxios 的cancelToken底层实现也是基于这个机制。上传进度fetch的ReadableStream完全可以读取上传流。实际上现代浏览器对fetch的支持度已经包括duplex: half处理流式上传完全可行。拦截器这其实是业务逻辑不是 HTTP 客户端的核心能力。你封装一个request.js文件几十行代码就能搞定同等的请求/响应处理。关键是axios 本身的体积不小打包后 mingzip 大约在 5KB 左右。听起来不大但如果你还有一套基于 axios 的二次封装、一套基于 fetch 的逻辑两套代码混在一个大项目里维护成本远远超过那 5KB。我的建议是新项目直接用原生 fetch 一个 100 行以内的封装函数不要再用 axios。老项目也完全可以渐进式替换先集中在一个业务模块里做试点确认没有依赖 axios 特有行为比如transformResponse里做了自定义处理再把默认的请求工具函数切换到 fetch。实测下来的体积收益可能在个位数 KB但对代码整洁度和可控性的提升是长远的。示例代码一个够用的 fetch 封装// request.js const API_BASE /api; async function request(path, options {}) { const controller new AbortController(); const timeoutId setTimeout(() controller.abort(), options.timeout ?? 10000); try { const response await fetch(${API_BASE}${path}, { ...options, signal: controller.signal, headers: { Content-Type: application/json, Authorization: localStorage.getItem(token) ? Bearer ${localStorage.getItem(token)} : , ...options.headers, }, }); if (!response.ok) { const error new Error(HTTP ${response.status}); error.status response.status; throw error; } const data await response.json(); return data; } finally { clearTimeout(timeoutId); } } export const get (path, options) request(path, options); export const post (path, body, options) request(path, { ...options, method: POST, body: JSON.stringify(body) });这段代码覆盖了我在业务里 90% 的用法不需要 axios 也能完成同样的工作。2.2 lodash —— ES2024 原生 API 已经覆盖日常 90% 的方法lodash 是一个两极分化极其严重的包。有人爱它爱到骨子里有人觉得它早就该死。站在 2026 年我的态度很明确如果你还在项目里全量引入 lodash是时候停下来删掉了。为什么这么说因为 ES2024 规范落地之后JS 原生新增的很多方法就是冲着“取代 lodash 常用工具函数”去的。我们逐个对一下_.debounce/_.throttle现在手写一个也就十几行而且你可以精确控制主动取消、立即执行等行为。唯一的理由是以前手写容易踩到this绑定和定时器清理的坑但这是老黄历了。_.deepClone/_.cloneDeepstructuredClone()这个 API 在 2022 年就入了标准2024 年的浏览器支持率已经接近全覆盖。它不仅完美处理深拷贝连Date、Map、Set、RegExp、ArrayBuffer这些特殊类型都能正确复制。_.get可选链?.加上空值合并??几乎完全替代了嵌套取值的场景。_.uniqBy/_.groupBy这些逻辑用Map 一行代码就能实现。_.debounce的替代还有原生新增的AbortSignal.timeout()配合addEventListener的signal选项很多人可能没有意识到这个组合可以用来处理“停止监听”的场景。如果你的项目目前是通过import _ from lodash全量引用的体积影响是肉眼可见的——完整版 lodash 压缩前大约 70KBgzip 后也有 20KB 左右。哪怕你用的是按需引用lodash/debounce每次安装时 npm 仍会解析整个包node_modules 的体积和安装耗时都会增加。更好的做法是彻底移除 lodash把业务里用到的函数逐个用原生代码替换。我统计过自己的项目最常用的其实就debounce、cloneDeep、get、isEmpty这四个替代它们的代码加在一起不超过 60 行。如果你确实有一些复杂场景无法用原生覆盖那也请按需引用具体方法而不是全量导入// 不推荐 import _ from lodash; // 推荐但仍建议优先原生 import debounce from lodash/debounce;2.3 moment.js / day.js —— Intl API 与 Temporal 正在接管一切moment.js 的问题其实早有定论体积巨大、API 设计过时、官方在 2020 年就停止了积极维护。但它太深入人心了很多老项目里它依然存在。到了 2026 年如果项目里还有 moment.js我是完全不能接受的。替代方案分两层第一层是Intl API。Intl.DateTimeFormat、Intl.RelativeTimeFormat、Intl.NumberFormat这些内置 API 可以处理绝大多数格式化需求包括时区计算、多语言日期、相对时间“3天前”。举例来说你要在界面上显示“2026年1月15日 14:30”原生实现是const formatter new Intl.DateTimeFormat(zh-CN, { year: numeric, month: long, day: numeric, hour: 2-digit, minute: 2-digit, }); console.log(formatter.format(new Date())); // 输出2026年1月15日 14:30你要处理时区比如把 UTC 时间显示成上海时间const formatter new Intl.DateTimeFormat(zh-CN, { timeZone: Asia/Shanghai, dateStyle: full, timeStyle: medium, });这些都是浏览器原生能力不需要下载任何东西。第二层是Temporal API。这是 ECMAScript 历时多年推进的日期时间新规范旨在解决Date对象在时区计算、日期运算上的历史遗留问题。2026 年这个时间点Temporal 已经在主流浏览器中以“即将标准落地”的状态呈现很多现代项目已经开始配合 polyfill 使用。如果你有复杂日期运算需求比如“每个月最后一个工作日”“跨时区的定期日历排期”Temporal 会是一个足够现代、且正在标准化的方向。当然我实际项目中见过最多的情况是项目里除了学习代码时写过moment().format(YYYY-MM-DD)真实业务根本没有那么复杂的日历逻辑。大部分需求只是格式化、相对时间、与后端约定的时间字符串转换。这些任务全部可以用原生DateIntlAPI 完成装一个 60KB 的日期库算不算浪费你自己心里有数。我的迁移建议按两步走把moment替换为原生Intl.DateTimeFormat大多数展示型需求一天就能改完。如果确实需要复杂的日期运算研究 Temporal API 的 polyfill 方案而不是新引入一个库。注意day.js虽然比 moment 轻但它本质上还是“挂着一个库”。我在 2026 年项目的代码评审里会倾向直接让团队用原生方案而不是从一个第三方切换到另一个第三方。2.4 uuid ——crypto.randomUUID()原生方法 2022 年就能用了这个真的不用多解释。过去生成 UUID 你需要uuid包或自己拼随机数。现在浏览器原生支持crypto.randomUUID()这是一个同步方法直接返回一个符合 RFC 4122 版本 4 规范的 UUID 字符串。const id crypto.randomUUID(); // 输出类似90b1a2c3-4d5e-4f6a-8b7c-9d0e1f2a3b4c为什么这个包能删得这么理直气壮因为crypto.randomUUID()在现代浏览器中支持率早就突破了 90%2026 年的项目基本不用考虑兼容性问题。而且它不只是简单返回随机数而是使用了密码学安全的随机数生成器CSPRNG这比自研的Math.random()拼接方案要可靠得多。老项目里常见的一个坑是以前用uuid包的时候node 环境下需要require(crypto).randomUUID浏览器环境下又不同。现在统一了一个 API 两端通用省心很多。迁移方式很简单——全项目搜索uuid导入全部替换成crypto.randomUUID()。如果代码里用了v4()之外的版本比如v1()基于时间戳或v5()基于命名空间确实需要额外适配。但我可以负责任地说绝大多数业务场景用的是 v4直接替换无压力。2.5 rimraf —— Node.js 原生fs.rm足以完成递归删除rimraf是跨平台递归删除文件夹的经典工具包。用 Node 写脚本的开发者肯定见过这一行const rimraf require(rimraf); rimraf.sync(dist);但在 2022 年的 Node.js 14.14.0 版本里官方就已经加入了fs.rmSync(path, { recursive: true, force: true })功能上完全覆盖了 rimraf 最常见的同步删除需求。更关键的是fs.rm本身就是 Node 原生 API不依赖任何第三方的跨平台支持稳定性远超后来的实习生在 npm 上重新造的轮子。所以到了 2026 年项目里的构建脚本、测试脚本里如果还在调用 rimraf删掉就好了// 旧写法 const rimraf require(rimraf); rimraf.sync(dist); // 新写法 const fs require(fs); fs.rmSync(dist, { recursive: true, force: true });需要注意的细节是recursive和force两个选项recursive: true递归删除目录及其所有内容。force: true如果路径不存在不会抛出异常。这个正好替代 rimraf 中不报错的行为。顺带一提如果你用的是fs.promises.rm的异步版本记得同样把recursive和force传上。异步版本适合在大型脚本中替代rimraf的回调模式避免阻塞事件循环。这个包删除的门槛是最低的。我唯一见过的例外是有些工具链内部仍依赖 rimraf 作为临时目录清理器但那是依赖包的行为不需要你显式安装。如果是你直接npm install rimraf进来的可以理直气壮地删掉。3. 依赖瘦身实操从清单到迁移的完整流程3.1 先做一次全项目依赖体检在动手删任何包之前先给项目做一次全面体检别凭感觉删。我自己的习惯是分四步走第一步区分直接依赖与传递依赖。打开package.json看 dependencies 和 devDependencies。你会发现很多“看起来在用”的包其实是直接依赖但代码里压根没有 import还有一些包是你没有主动安装但因为某个工具依赖它所以在 node_modules 里躺着的。前者要判断是否可以删除直接声明后者不用管让 npm 自己解析就行。第二步扫描实际引用。用 IDE 的全局搜索功能逐一搜索包名在源码中的引用情况。推荐直接使用npx knip或depcheck这类工具扫描未使用依赖。我个人用下来knip的准度更高它能分析出哪些导出没人用、哪些文件没有被任何模块引用。第三步检查打包体积。用vite或webpack的构建分析工具如rollup-plugin-visualizer看看每个包占打包产物的体积。这一步能直观看到你删掉某个包后对首屏性能的影响。有时一个包看起来不大但它依赖了其他几个重型包实际体积早就被放大了。第四步标记可删除项。把前面几步的结果汇总成一张清单按“确认可安全删除”“需要代码改造后删除”“暂时无法删除”三个类别整理。下面是我做这一类检查时使用的示例清单包名引用状态替代方案处理优先级预估收益axios所有请求都通过 request.js 封装使用原生 fetch 封装高体积 代码统一性lodash仅 4 个方法被使用原生 API高移除完整包依赖moment2 个模块做了格式化Intl.DateTimeFormat中减少 60KBuuid1 处生成 UUIDcrypto.randomUUID()高直接替换rimraf1 个构建脚本fs.rmSync低构建脚本更健壮3.2 渐进式迁移不要一把梭依赖瘦身最忌讳“大爆炸式重写”。我见过一个同事在一个周五下午决定把 axios 全部替换成 fetch结果下周一收到十几个 bug 反馈。正确做法是渐进式迁移先挑影响小的包动手。比如 uuid、rimraf 这种替换成本极低的先做。一天之内完成提交一个干净的 PR让团队看到度量。再处理有行为差异的包。lodash 的cloneDeep替换成structuredClone时要注意structuredClone无法复制函数和 DOM 节点如果你的业务代码里有这类对象被深拷贝就不能直接替换。axios 替换成 fetch 时最大的差异在请求头、错误处理和拦截器里不要放任response.data来自动解包的地方爆掉。最后动复杂场景的包。日期类库替换最难因为涉及时区、国际化、边缘日期的运算建议写一个专门的测试用例集来做回归验证。每个包删除后跑一遍完整测试。单元测试、集成测试、E2E 都跑一遍。如果项目测试覆盖不全至少要把核心用户流程手动过一遍。3.3 建立持续化的依赖管理机制一次清理完了不叫结束。2026 年的前端项目新依赖的引入应该更克制。我给自己和团队立了三条规定新依赖必须满足一个硬性条件原生 API 确实无法实现或者实现成本高于引入和维护成本。每次装新包前要在 PR 描述里写清楚用途和退出机制。不是为了卡流程是为了让后来者知道这个包为什么在这里。每季度做一次依赖体检用npm updateknip扫描把不再需要的包清理掉。这几条规则看似简单但能有效防止项目在三五年后重新变成“全家桶”模式。前端依赖的问题从来不是“某个包好不好”而是“我们应不应该无脑地给每一个小需求都引入外部依赖”。4. 删包过程中踩过的坑与排查技巧4.1 版本兼容性不是拍脑袋判断出来的很多人替换原生 API 之前最担心的就是兼容性。我的建议是不要凭记忆判断用数据说话。具体做法是打开caniuse.com输入你打算用的 API 名称查看当前浏览器支持版本。一般来说2026 年的项目只需要兼容最近两个大版本的 Chrome/Edge/Firefox/Safari。在这个前提下structuredClone、crypto.randomUUID、fetch、AbortSignal.timeout都是安全的。如果你需要支持更老的环境最简单的兜底方案是写一个特征检测函数而不是引入一个完整 polyfill 包const randomUUID () crypto.randomUUID ? crypto.randomUUID() : xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx.replace(/[xy]/g, function (c) { const r (Math.random() * 16) | 0; const v c x ? r : (r 0x3) | 0x8; return v.toString(16); });这种小函数在项目里出现几次问题不大。但如果你发现自己在很多文件中都写了同样的兼容代码这时候才值得考虑提取为一个共享工具函数甚至引入一个专门的 polyfill 包。4.2 行为差异导致的隐蔽 bug替换第三方库最容易翻车的地方是“你以为两者行为一样其实完全不一样”。我举三个真实踩过的例子第一个是 axios 和 fetch 的 JSON 处理差异。axios 在响应数据已经是 JSON 字符串时会自动帮你 parsefetch 则需要手动调response.json()。如果你原本的代码是const data response.data切换成 fetch 后很容易漏掉await response.json()。这个 bug 不会报错只会让页面渲染出[object Promise]或者直接 undefined。第二个是 lodash 的_.get和可选链的差异。_.get(obj, a.b.c, defaultValue)在a.b不存在时返回默认值可选链则在每一层判断。看起来差不多但_.get对数组下标、空字符串 key 的处理和可选链不一样。从_.get迁移到可选链时要仔细核对边界场景。第三个是 moment 与时区处理的坑。很多老代码直接moment().format(YYYY-MM-DD)它用的是本地时区和服务器传过来的 UTC 时间比对时经常出现“差一天”的问题。如果你改成用Intl.DateTimeFormat默认行为却是跟随用户的时区看起来一样的代码结果却不同。在这种场景下一定要显式指定timeZone: UTC或本地时区不要依赖默认值。4.3 测试覆盖不够时怎么确保迁移安全旧项目往往测试覆盖不全这几乎是常态。我在做这类迁移时会在没有 E2E 测试的关键模块上手动构建一个“冒烟清单”专门覆盖容易出现行为差异的路径登录和鉴权流程——涉及到请求拦截器、token 刷新最容易踩 axios/fetch 差异的坑。日期列表页——涉及日期格式化、相对时间展示、时区换算。数据表格的排序和筛选——涉及 lodash 的orderBy、groupBy迁移。把这三个路径在本地手动跑通基本就能过滤掉 80% 的迁移 bug。剩下的交给 CI 流水线里的自动化测试跑一遍看到绿灯就算安全。我还会在代码里临时加一些console.warn输出替换前后的值对比。虽然这种打印在生产代码里不能久留但迁移期用来排查差异非常有效。比如在 fetch 封装里临时打印一下 status code和旧接口日志对比能快速发现错误处理逻辑的问题。5. 一些个人的看法写到最后我想聊几句跟技术无关但跟工程习惯有关的话。2026 年还在纠结要不要删 axios、要不要删 lodash说明项目里可能有一个更根本的问题依赖的引入缺少一个清晰的决策机制。很多团队在写业务时根本没想过“这个库是不是必要的”而是习惯性地“别人都装我也装”或者“上次项目用过就继续用”。这不是某个开发者的错而是行业节奏太快大家默认“现成的轮子比自研更可靠”。但事实是现代浏览器的原生能力已经足够强大了。2026 年的前端真正稀缺的不是“会用更多 API”而是“能够精确判断哪些场景不需要引入任何东西”。复杂业务当然需要合适的第三方库但简单需求真的可以把工具函数塞进自己的代码库而不是每行代码都寄希望于 npm。我个人在实际操作中体会最深的一点不是节省了多少字节而是这种审视依赖的习惯让整个团队开始更关注“为什么写这行代码”。当你在一个 PR 里写清楚“把 uuid 替换成 crypto.randomUUID()因为原生 API 已够用”其他成员自然会开始思考自己的代码里有没有同样可以清理的依赖。这种正向循环比任何一次技术分享都更有说服力。所以与其等到 2026 年再看不如今天就把这 5 个包从你的项目里拎出来挨个审一遍。你会发现删掉它们之后代码不仅跑得更快心理负担也少了一大截。