最近,我的个站由于用到了免费CDN(国外的因为访问问题,改成了国内的bootcdn)上的一个vuejs包,当时是为了加速,但是没想到它这些年变了,一些包路径变了/没有进行缓存,导致整个站点无法访问。我开始反思前端工程中的依赖管理问题,尤其是某些优化,我之所以使用公开的库,是为了增加命中率,让用户打开网站的速度快一些,而不是重新从我的服务器下载个一模一样的文件。在搜索相关问题的过程中,我发现早在几年前,NPM 上的 "left_pad" 包被作者删除时,整个前端社区的项目纷纷遭遇了崩溃,印象到了数千项目的部署。
今天,呼吁大家反思一下:我们的前端项目设计的过于复杂(超过实际的复杂)、依赖是不是太多了!
先发起一个呼吁:前端使用第三方依赖时,优选下0依赖的包。发布新包时,不妨造造轮子
NPM 依赖陷阱:循环、深层的依赖
下图清晰的说明了npm黑洞问题:
例如我随便打开了一个package.lock文件,就有5000行,这还只是一个只有一个页面的vue3+vite项目的单页应用,依赖也仅仅是包含:
"dependencies": {
"@ant-design/icons-vue": "^7.0.1",
"ant-design-vue": "4.x",
"dayjs": "^1.11.11",
"pinia": "^2.1.7",
"rxjs": "^7.8.1",
"vue": "^3.4.29",
"vue-i18n": "^9.13.1",
"vue-router": "^4.4.0"
},
"devDependencies": {
"@intlify/unplugin-vue-i18n": "^4.0.0",
"@types/node": "^20.14.9",
"@vitejs/plugin-vue": "^5.0.5",
"@vitest/ui": "^1.6.0",
"@vue/test-utils": "^2.4.6",
"autoprefixer": "^10.4.19",
"jsdom": "^24.1.0",
"postcss": "^8.4.39",
"sass": "^1.77.6",
"sass-loader": "^14.2.1",
"tailwindcss": "^3.4.4",
"typescript": "^5.2.2",
"unplugin-vue-components": "^0.27.2",
"vite": "^5.3.1",
"vitest": "^1.6.0",
"vue-tsc": "^2.0.21"
}
但是,实际安装的包有数百个,很多都是二次依赖、三次依赖。
11行代码影响上万个项目的发布事件
前端开发中,NPM 依赖的数量往往让人瞠目结舌。一个简单的前端项目可能引入数百个包,而每个包可能又有成百上千的二次依赖。这种依赖链的复杂性使得我们难以全面掌控项目中的所有外部模块,也增加了潜在的脆弱性。一个上游包的更新或删除,可能直接影响到数千个甚至上万个下游项目,正如 "left_pad" 事件所展示的那样。
"left_pad" 是一个只有 11 行代码的小工具,其代码如下:
module.exports = leftpad;
function leftpad (str, len, ch) {
str = String(str);
var i = -1;
if (!ch && ch !== 0) ch = ' ';
len = len - str.length;
while (++i < len) {
str = ch + str;
}
return str;
}
但它被删除后,立即导致了大量依赖它的项目无法正常工作。这样一个小工具的失效引发的连锁反应,不仅是对整个前端社区的警醒,更是让我们意识到我们对这些工具的过度依赖。
这个事件中,一位开发者因为对管理方式不满而选择删除了自己在 NPM 上发布的所有包,包括 "left_pad"。由于许多流行的前端库直接或间接依赖于这个小工具,导致数千个项目在构建过程中失败,甚至一些大型企业的服务也因此受到影响。这次事件让开发者意识到,尽管一个工具看起来非常小,但它在整个生态中的影响可能非常大。这样的事情让我们不禁感叹,前端生态系统在享受模块化开发带来便利的同时,也存在着巨大的隐患。
详细的可搜索 left pad事件 或者 查看 或许无法访问的维基百科
所有者当时的发言是:
这个库是我创建的,我有权删除
开源库的使用问题:所有权与维护压力
除了依赖的复杂性,开源库的所有权和维护问题也成为了前端社区不可忽视的隐患。许多清理类、工具类的小型开源库被大量使用,但这些库的所有权仍然掌握在个人开发者手中,这意味着开发者有完全的权利删除或停止维护这些库。例如 "left_pad" 的创建者认为这个库是他创建的,因此他有权利删除它。这种所有权问题让整个社区的稳定性变得不可预测。
此外,许多开源库虽然被大公司广泛使用,但它们的维护者可能只是一位个人开发者,并没有从中获得足够的收入或支持。这就导致了两个潜在的问题:
开发者维护压力大:个人开发者在面对成千上万用户的需求时,往往难以承担更新和维护的压力。他们没有足够的时间和资源去修复问题或引入新功能,这可能导致库长期得不到维护。这种状况不仅让依赖这些库的开发者处于被动地位,也让整个项目的稳定性面临风险。
维护者缺乏激励:许多开源项目的开发者并没有从他们的劳动中获得经济回报,当他们觉得自己的付出得不到应有的认可时,可能会选择放弃维护。这就让那些依赖这些库的项目处于危险之中,尤其是在需要快速响应的情况下。开源项目需要开发者的热情和动力,但如果缺乏有效的激励机制,这种热情往往难以持久。
更糟糕的是,这些开源库常常是整个前端生态中不可或缺的一部分。大公司将它们纳入自己的项目,而不一定会对其进行额外的测试或二次开发,导致对这些库的依赖一旦出现问题,就会对整个系统造成严重的影响。维护者在个人时间、资源和激励不足的情况下,很难对社区的庞大需求做出及时的响应,进一步加剧了这种不可控性。
NPM 的问题:依赖地狱与不可控性
NPM 生态虽然给开发者带来了极大的便利,但也伴随着一些深层次的问题:
过度依赖第三方包:很多时候,开发者会为了图方便而直接使用现成的库,即使它的功能实现非常简单。这不仅增加了项目的复杂性,还可能引入额外的安全隐患。一个简单的字符串补全工具,可能成为数千个项目的依赖,这种现象不仅增加了项目的重量,还使得项目的稳定性更加脆弱。
不可控的依赖链:NPM 中每个包都有可能依赖多个其他包,这些依赖又会再继续依赖下去,形成错综复杂的依赖链。这些依赖链中的任意一个包的更新、弃用或删除,都会影响整个项目的稳定性。这就像是一个庞大的多米诺骨牌,一旦有一个节点出现问题,整条依赖链都可能会受到冲击。开发者往往难以掌握这些复杂的依赖链,导致问题出现时束手无策。
版本管理的混乱:虽然语义化版本控制是前端社区的一个标准,但许多开发者在发布版本时并没有严格遵守这一规范,导致版本间的不兼容性成为常态。加之 "^" 或 "~" 号的使用,使得我们的依赖更新不再受自己控制。版本的不一致性往往会导致无法预料的问题,尤其是在某些库自动升级时,可能会引入新的 bug 或与其他依赖产生冲突。
如何避免依赖陷阱:减少依赖、重视模块设计
要避免依赖陷阱,我们需要对依赖进行更严格的管理,并重视模块的设计与代码的可控性:
减少不必要的依赖:在引入第三方库之前,应该首先考虑该功能是否可以自己实现。如果功能简单,自己编写代码通常比引入一个依赖更为稳妥。这样不仅可以减少对外部模块的依赖,还可以更好地掌控代码的质量和安全性。
管理依赖版本:尽量锁定依赖的具体版本,避免使用模糊版本号,以减少潜在的不兼容问题。同时可以利用工具(如 npm-shrinkwrap 或 yarn.lock)来管理依赖树,确保依赖的版本一致性和可控性。这种方式可以有效降低因为上游版本变动而带来的风险。
建立更好的依赖审查机制:公司和团队应当对项目中的依赖进行定期审查,尤其是对那些长期没有更新或维护的库要格外关注。可以考虑用自动化工具来扫描依赖的安全漏洞和更新状态,以便及时采取措施来替换不再可靠的依赖。
训练好的模型,敢于造轮子:由人来做重复的工作往往是被嫌弃的,都想最大程度的减少重复。但这在无形中增大了耦合度,或许让大模型来造轮子,是个更好的选择
总结
前端项目中的依赖管理问题需要引起更多开发者的重视。通过 "left_pad" 事件以及最近个站因 CDN 包被删除而崩溃的案例,我们应该意识到依赖管理中的风险与隐患。减少不必要的依赖、重视模块设计、合理管理版本,是提升前端项目稳定性的重要手段。同时,我们也应当关注开源库的所有权和维护问题,支持那些为社区作出贡献的开发者,让开源生态变得更加健康和可持续。
训练大模型,让它来造轮子,是一个持续正反馈的过程,也是值得做的,你觉得呢?