新闻详情

新闻详情

首页 / 资讯中心 / 详情

清理 package.json:5 个可安全删除的 npm 依赖及迁移指南

发布时间:2026/9/20 11:49:12来源:尧图网络
清理 package.json:5 个可安全删除的 npm 依赖及迁移指南
1. 为什么现在可以动手清理 package.json 了如果你维护过超过两年的前端项目打开package.json的那一刻大概率会有一种翻旧仓库的感觉有些依赖你根本想不起来当初为什么装有些依赖的 GitHub 仓库已经两年没提交还有些依赖你每次npm install都会看到一行黄色的 deprecated 警告但一直懒得处理。更麻烦的是这些“僵尸依赖”并不会安静地待着它们会拖慢安装速度、撑大node_modules、在 CI 里制造偶发失败甚至在安全扫描报告里给你添几条高危告警。我最近花了几个晚上把手上三个项目的依赖树从头到尾过了一遍删掉了十几个包其中有一批是“曾经必须、现在多余”的典型代表。这篇文章就围绕其中最有代表性的 5 个 npm 包展开讲清楚它们当年为什么被装进来、现在为什么可以删、删掉之后用什么替代、以及删除过程中容易踩的坑。适合所有还在维护 JavaScript 项目的人看不管你是刚接手老项目的新人还是想给依赖做减法的老手都能直接照着操作。需要先说明一点这里说的“可以删”不是绝对的而是指在现代运行时环境和主流构建工具链下这些包提供的功能已经被语言本身或平台原生能力覆盖。如果你的项目还跑在非常老的 Node 版本上或者有特殊的兼容性要求那删除前需要自己评估。下面每一节我都会把判断依据讲清楚你可以对照自己的项目决定。2. 五个可以优先清理的 npm 包逐个拆解2.1node-domexception被运行时原生能力取代的典型如果你在终端里见过这行警告npm warn deprecated node-domexception1.0.0: Use your platforms native DOMException instead那你项目里就躺着这个包。它的作用非常简单在 Node 环境里提供一个DOMException的实现。早年 Node 没有全局的DOMException而一些 Web API 风格的库比如某些 fetch 实现、流处理库需要抛出符合规范的异常对象于是就有人做了这个 polyfill。现在的情况是Node 17 开始就已经内置了全局DOMException主流 LTS 版本18、20、22全都有。也就是说这个包的存在意义已经被运行时本身接管了。它通常不是你直接安装的而是某个上游依赖的依赖所以你在package.json里可能根本找不到它需要在node_modules里往上追。判断方法很简单在项目根目录执行npm ls node-domexception如果输出显示它是某个包的传递依赖那你要做的不是直接删而是升级那个上游包。我遇到的情况是某个老版本的fetch相关库锁死了旧依赖升级到最新版之后node-domexception就自动消失了。这里有个经验不要手动去node_modules里删文件夹那样下次安装又会回来而且可能破坏依赖树。正确做法是通过npm dedupe或者升级上游包来解决。注意如果你的项目需要支持 Node 16 及以下版本那这个包可能还有存在价值。但 Node 16 已经停止维护继续支持它的成本远高于收益建议借这个机会把运行时版本要求提上去。2.2core-js的全量引入从“保险”变成“负担”core-js本身不是垃圾包它是 JavaScript 标准库 polyfill 的中流砥柱。问题出在引入方式上。很多项目在入口文件里写一行import core-js或者用babel的useBuiltIns: entry配置把整个 polyfill 库全量打进来。这一下就是几百 KB 的代码而你的项目可能只用到其中不到 5% 的特性。现在可以删的理由有两个。第一现代浏览器对 ES2015 的支持已经非常完整Promise、Array.from、Object.assign、展开运算符这些当年需要 polyfill 的东西现在全都是原生支持。第二构建工具的能力上来了babel/preset-env配合useBuiltIns: usage可以做到按需注入只为你实际用到的 API 引入对应的 polyfill而且browserslist配置能精确控制目标环境。我自己的做法是先把browserslist更新到只覆盖最近两年的主流浏览器然后把core-js的全量引入改成按需模式最后跑一遍构建对比产物大小。实测一个中等规模的项目JS 产物从 480KB 降到了 310KBgzip 后也有明显下降。如果你的项目压根不需要支持 IE 或老版本 Safari甚至可以直接把core-js从依赖里移除让构建工具根据browserslist自动决定要不要注入。具体操作上检查babel.config.js或.babelrc// 改造前 presets: [ [babel/preset-env, { useBuiltIns: entry, corejs: 3 }] ] // 改造后 presets: [ [babel/preset-env, { useBuiltIns: usage, corejs: 3 }] ]同时把入口文件里的import core-js删掉。改完之后一定要跑完整的回归测试因为按需注入偶尔会漏掉一些动态调用的场景比如通过字符串拼接调用的方法。2.3moment体积与不可变性的双重问题moment曾经是日期处理的默认选择但它的问题这些年被讨论得很充分体积大压缩后约 70KBgzip 后约 20KB、不支持 tree-shaking、对象可变导致容易写出 bug、时区处理需要额外加载数据。更重要的是官方自己已经在文档里建议新项目考虑其他方案。现在替代方案很成熟。如果只是做简单的格式化和计算date-fns是首选它按函数导入tree-shaking 友好而且所有操作都是纯函数不会意外修改原对象。如果项目对时区有强需求Luxon或者Day.js配合时区插件都能覆盖。Day.js的 API 和moment高度相似迁移成本最低体积却只有 2KB 左右。迁移的时候有个细节要注意moment的format占位符和date-fns不完全一样。比如moment用YYYY-MM-DDdate-fns也是yyyy-MM-dd大小写有区别。我建议写一个小的适配层把项目里常用的格式化、解析、比较操作封装成统一函数底层换实现这样业务代码改动最小。// 适配层示例 import { format, parseISO, differenceInDays } from date-fns; export function formatDate(date, pattern yyyy-MM-dd) { return format(typeof date string ? parseISO(date) : date, pattern); }这样迁移的时候只需要改这一个文件业务代码里的调用方式保持不变。实测一个用了moment三年多的项目半天时间就完成了替换产物里直接少了一个大块头。2.4request早已停止维护的 HTTP 客户端request这个包在 Node 生态里曾经是神一样的存在下载量长期霸榜。但它在 2020 年就已经进入维护停止状态官方明确表示不再添加新功能只做最低限度的安全修复。现在继续用它等于把一个不再更新的网络层放在项目最核心的位置。替代方案在 Node 18 里非常直接全局fetch已经可用不需要任何依赖。如果项目需要更细粒度的控制比如拦截器、超时重试、代理配置undici是 Node 官方团队维护的底层实现axios也依然活跃且生态成熟。我的建议是新代码一律用原生fetch老代码逐步迁移。迁移request的难点在于它的 API 风格和fetch差异较大。request用回调配置项叫uri、qs、form而fetch用 Promise配置项是url、searchParams、body。我通常写一个薄封装来抹平差异async function httpGet(url, options {}) { const res await fetch(url, { method: GET, ...options }); if (!res.ok) throw new Error(HTTP ${res.status}); return res.json(); }然后把项目里所有request.get(...)替换成httpGet(...)。这个过程比较机械但收益很明显少了一个不再维护的依赖网络层的行为也更符合现代标准。2.5tslint被 ESLint 完全取代的 lint 工具tslint是 TypeScript 早期的 lint 工具但它的使命已经结束。TypeScript 官方在 2019 年就宣布转向 ESLinttslint进入废弃状态。现在typescript-eslint提供了完整的规则集而且能和 ESLint 生态里的其他插件React、Vue、import 排序等无缝配合。如果你的项目还在用tslint迁移到 ESLint 是迟早的事。好消息是官方提供了tslint-to-eslint-config工具可以自动转换大部分配置。操作步骤大致是先安装eslint、typescript-eslint/parser、typescript-eslint/eslint-plugin然后运行转换工具生成.eslintrc最后逐条检查规则映射有些tslint规则在 ESLint 里没有直接对应需要手动调整或放弃。我迁移过一个中型项目转换工具覆盖了大约 80% 的规则剩下的 20% 里有一部分是tslint特有的格式规则现在交给 Prettier 处理更合适。迁移完成后tslint.json和tslint依赖都可以删掉CI 里的 lint 步骤也统一成一条eslint命令维护成本明显下降。3. 删除依赖前的安全操作流程3.1 先摸清依赖树的真实结构直接打开package.json删行是最危险的做法因为你看到的只是直接依赖真正的问题往往藏在传递依赖里。动手之前先用几条命令把依赖树摸清楚。# 查看某个包为什么被安装 npm ls package-name # 查看所有过时的包 npm outdated # 查看所有 deprecated 的包 npm ls --all 21 | grep -i deprecatednpm ls的输出会告诉你这个包是被谁引入的是直接依赖还是传递依赖。如果是传递依赖你要做的是升级上游包而不是直接删。我习惯把输出重定向到文件里慢慢分析因为大型项目的依赖树可能有几千行。另外npm outdated能帮你发现哪些包有新版本可用。很多时候升级一个上游包就能顺带解决好几个 deprecated 的传递依赖这比逐个手动处理高效得多。3.2 用锁文件和 CI 保证删除不破坏构建删除依赖之后package-lock.json必须同步更新。我的习惯是删完package.json里的条目后执行一次干净的安装rm -rf node_modules package-lock.json npm install这样能确保锁文件反映真实的依赖关系不会残留已经删除的包的记录。然后跑一遍完整的构建和测试包括单元测试、集成测试、以及如果有的话端到端测试。CI 在这里的作用很关键。我会在删除依赖的 PR 里特意观察 CI 的安装耗时和产物大小变化。通常删掉几个大包之后npm install能快十几秒构建产物也能小几十 KB。这些数据可以作为删除决策的佐证也能在代码评审时说服同事。提示如果项目用了 monorepo 或者 workspace删除依赖时要检查所有子包的package.json别只改了根目录。我踩过一次坑根目录删了moment结果某个子包还在用CI 直接报模块找不到。3.3 灰度发布与回滚预案依赖变更属于基础设施层面的改动一旦出问题影响面比较大。我的做法是先把改动合并到一个独立分支部署到预发环境跑一两天观察错误监控和性能指标。确认没问题再合并到主分支。回滚预案也要提前准备好。因为package-lock.json也变了回滚的时候不能只 revert 代码还要把锁文件一起恢复。我通常会在 PR 描述里写清楚回滚步骤比如“revert 这个 commit 后执行npm ci重新安装”。这样万一线上出问题值班的人能快速操作。4. 替代方案选型与迁移实操4.1 日期库迁移从 moment 到 date-fns 的完整对照日期处理是迁移工作量最大的部分因为moment的 API 太灵活了。我把常见的操作整理成对照表迁移的时候可以直接查。操作moment 写法date-fns 写法当前时间moment()new Date()格式化moment(d).format(YYYY-MM-DD)format(d, yyyy-MM-dd)解析字符串moment(2026-01-01)parseISO(2026-01-01)加减天数moment(d).add(7, days)addDays(d, 7)差值moment(a).diff(b, days)differenceInDays(a, b)是否之前moment(a).isBefore(b)isBefore(a, b)注意date-fns的所有函数都是纯函数不会修改传入的日期对象。这其实是好事能避免很多隐蔽的 bug但迁移时要检查业务代码里有没有依赖moment可变性的写法。比如有些代码会先const m moment()然后多次调用m.add(1, day)并期望m本身变化这种写法在date-fns里必须改成接收返回值。4.2 HTTP 客户端迁移request 到 fetch 的注意事项fetch和request最大的行为差异在于错误处理。request默认把 4xx 和 5xx 也当作正常响应返回需要你自己判断statusCode而fetch只有在网络层面失败时才 rejectHTTP 错误状态码不会触发 catch。所以迁移时一定要显式检查res.ok。另一个差异是超时。fetch没有内置的超时配置需要用AbortController实现async function fetchWithTimeout(url, options {}, timeout 5000) { const controller new AbortController(); const timer setTimeout(() controller.abort(), timeout); try { const res await fetch(url, { ...options, signal: controller.signal }); return res; } finally { clearTimeout(timer); } }还有一点fetch不会自动带上 cookie需要设置credentials: include。如果原来的request配置里有jar: true迁移时别忘了这一项否则登录态会丢失。这些都是我实际迁移时踩过的坑写出来供你参考。4.3 lint 工具迁移tslint 到 typescript-eslint 的配置转换tslint的配置文件是tslint.json规则名用驼峰或短横线ESLint 用.eslintrc.js或eslint.config.js规则名用斜杠分隔。官方转换工具能处理大部分映射但有几类规则需要特别注意。格式类规则比如max-line-length、quotemark、semicolon建议直接关掉交给 Prettier 处理。类型检查类规则比如no-unnecessary-type-assertion在typescript-eslint里有对应实现但需要配置parserOptions.project指向tsconfig.json否则规则不生效。还有一类tslint特有的规则比如member-ordering在 ESLint 里也有但配置格式不同需要手动调整。迁移完成后记得把tslint从devDependencies里删掉同时删除tslint.json。CI 配置里的tslint命令也要换成eslint。我建议在迁移的 PR 里单独提交配置文件的改动和业务代码分开这样评审的时候更清晰。5. 常见问题与排查技巧实录5.1 删了包之后构建报错怎么办最常见的原因是漏改了引用。比如删了moment但某个工具函数里还有import moment from moment。排查方法是全局搜索包名grep -r from moment src/ grep -r require(moment) src/如果搜索结果显示还有引用要么改掉要么暂时保留这个包。另一种情况是传递依赖被破坏比如你升级了某个包它依赖的另一个包版本变了导致 API 不兼容。这时候看构建报错的堆栈定位到具体是哪个包抛的错然后查它的 changelog 找破坏性变更。还有一种比较隐蔽的情况某些包在运行时动态加载静态搜索搜不到。比如通过字符串拼接的require或者配置文件里写的包名。这种只能靠完整的回归测试来覆盖所以删除依赖后一定要跑全量测试不能只跑单元测试。5.2 npm install 变慢或报错怎么处理删除依赖后如果npm install反而变慢通常是锁文件没有正确更新导致 npm 在解析依赖树时做了多余的工作。解决办法是删掉node_modules和package-lock.json重新安装一次。如果用的是 npm 7 以上版本可以试试npm install --legacy-peer-deps看是不是 peer 依赖冲突导致的。如果报错信息里有ERESOLVE说明依赖树里有版本冲突。这时候不要盲目加--force而是用npm ls 冲突的包找到冲突源头然后决定是升级还是降级。我遇到过一次ERESOLVE追下去发现是两个包分别依赖了同一个库的不同大版本最后通过升级其中一个包解决了。5.3 如何判断一个包是否真的可以删我总结了一个简单的判断清单你可以对照使用判断维度可以删的信号需要保留的信号维护状态官方标记 deprecated 或两年无更新近期有活跃提交功能覆盖运行时或标准库已原生支持功能独特无替代使用频率全局搜索引用少于 3 处核心链路大量使用体积占比压缩后超过 20KB 且可替代体积小且无替代安全记录有未修复的高危漏洞无已知漏洞按这个清单过一遍大部分“僵尸依赖”都能识别出来。我的经验是一个项目里真正必需的依赖通常不超过直接依赖总数的一半剩下的要么是历史遗留要么是可以用更轻量方案替代的。5.4 删除依赖后如何验证没有副作用验证分三层。第一层是静态检查跑eslint和tsc --noEmit确保没有类型错误和未解析的导入。第二层是单元测试和集成测试覆盖核心业务逻辑。第三层是端到端测试模拟真实用户操作路径。如果项目没有端到端测试至少要在预发环境手动过一遍关键流程。我通常会列一个检查清单包括登录、核心功能、支付如果有、数据展示等逐项确认。另外观察错误监控平台一到两天看有没有新增的异常。这些步骤看起来繁琐但比起线上出故障再回滚成本低得多。6. 把依赖清理变成常规动作依赖清理不应该是一次性的运动而应该变成常规动作。我现在每个季度会花半天时间做一次依赖审查流程固定下来之后其实很快。先跑npm outdated和npm ls --all | grep deprecated把候选名单列出来然后按上面的判断清单逐个评估能删的删能升级的升级。这样做的好处是每次改动量都不大风险可控不会像攒了三年再一次性清理那样牵一发而动全身。而且依赖树保持精简之后npm install快、构建快、安全扫描干净整个开发体验都会好很多。最后分享一个我常用的小技巧在package.json里加一个engines字段明确声明项目支持的 Node 版本范围。这样团队成员和 CI 环境都会用统一的运行时避免因为 Node 版本差异导致某些 polyfill 包“看起来需要、实际上不需要”的误判。配合.nvmrc文件新同学克隆项目后一条nvm use就能对齐环境省去很多沟通成本。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Java+Vue构建可解释羽毛球技战术分析系统 2026/9/20 12:40:23

Java+Vue构建可解释羽毛球技战术分析系统

简介:这是一份面向Java与Vue全栈开发者、体育数据分析研究者及高校相关专业学生的智能体育系统实战项目,聚焦羽毛球落点预测与技战术深度分析,解决赛事复盘低效、训练决策缺乏数据支撑、国产智能分析工具稀缺等实际问题。资源为1个85KB的docx…

阅读更多 →
免费硬盘分区工具怎么选?5款实测推荐与避坑指南 2026/9/20 12:40:23

免费硬盘分区工具怎么选?5款实测推荐与避坑指南

很多人问我硬盘分区到底用什么工具好,尤其在新装了固态硬盘、买了新电脑、或者C盘爆红的时候。市面上的分区工具确实不少,但有些收费,有些界面复杂得让人头大,还有些捆绑了一堆流氓软件。这篇文章我直接把我自己常用、也给身边朋友…

阅读更多 →
批量doc转docx工具开发与实战指南 2026/9/20 12:40:23

批量doc转docx工具开发与实战指南

1. 项目概述:为什么需要批量doc转docx?在日常办公场景中,我们经常会遇到需要处理大量旧版Word文档(.doc格式)的情况。这些文档可能来自十年前的项目归档,或是不同部门交接的历史文件。与新版.docx格式相比&…

阅读更多 →
Handsontable 破坏性变更策略全指南:从 API 弃用到向后兼容的工程实践 2026/9/20 12:40:23

Handsontable 破坏性变更策略全指南:从 API 弃用到向后兼容的工程实践

前端UI组件 【免费下载链接】handsontable JavaScript Data Grid / Data Table with a Spreadsheet Look & Feel. Works with React, Angular, and Vue. Supported by the Handsontable team ⚡ 项目地址: https://gitcode.com/gh_mirrors/ha/handsontable 点击…

阅读更多 →
Collections.singletonList 和 Arrays.asList 的区别 2026/9/20 12:40:23

Collections.singletonList 和 Arrays.asList 的区别

如下示例&#xff1a; List<String> list1 Collections.singletonList("小狗");List<String> list2 Arrays.asList("小狗", "小猫");可看出两者都可以直接放入元素&#xff0c;创建并初始化一个 list。 Collections.singletonList…

阅读更多 →
Fast Sparse ConvNets 稀疏卷积模型库实战指南:TF-Lite 模型下载、稀疏模式设计与性能解读 2026/9/20 12:37:23

Fast Sparse ConvNets 稀疏卷积模型库实战指南:TF-Lite 模型下载、稀疏模式设计与性能解读

Fast Sparse ConvNets 稀疏卷积模型库实战指南&#xff1a;TF-Lite 模型下载、稀疏模式设计与性能解读 【免费下载链接】google-research Google Research 项目地址: https://gitcode.com/gh_mirrors/go/google-research 导读 Fast Sparse ConvNets 是 Google Research…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞