供应链安全实战:从依赖管理到构建防护的完整指南
优化前:卡得不行
上周上线一个新功能,用户反馈页面加载慢得离谱。我本地跑起来也是一样——首页白屏 5 秒多,F5 刷新后还经常卡死。打开 DevTools 看 Network,好家伙,光第三方 JS 就加载了 30 多个,总大小快 4MB。最离谱的是,其中几个脚本还是从 npm 安装的 UI 组件库里带出来的,根本没用到,但构建时全被打包进来了。
更糟的是,我们用了几个开源图表库,版本管理混乱,有的是 ^1.2.0,有的是 ~1.3.5,甚至还有直接从 CDN 引的。结果某天其中一个依赖悄悄更新了子依赖,导致整个页面在低版本浏览器上直接报错。性能差 + 不稳定,这哪受得了。
找到瓶颈了!
我先用 Chrome 的 Performance 面板录了一次加载过程,发现主线程被大量解析和执行第三方脚本占满,尤其是两个体积超大的组件库(一个 800KB,一个 600KB),实际只用了里面两个按钮和一个弹窗。
接着用 Webpack Bundle Analyzer 扫了一下构建产物,果然,node_modules 占了 70% 以上的体积。再查 package.json,发现有些依赖根本没用过,比如 lodash 被某个老插件引入,但我们代码里完全没调用;还有 moment.js,早该换成 dayjs 了,但一直没动。
最关键的是,我们没做任何依赖的审核机制。开发随便 npm install 一个包,CI/CD 也不检查,直接上线。这哪是供应链安全,简直是“供应链裸奔”。
核心优化:砍依赖、换方案、加校验
折腾了两天,试了几种方案,最后定下来三招:
- 按需引入 + Tree Shaking:把那些“全家桶”式组件库换成支持 ES Module 按需加载的
- 锁定依赖版本 + 校验完整性:用
package-lock.json锁死,再加一层 integrity hash - 移除无用依赖:直接删掉没用的包,别犹豫
先说第一个,以 Ant Design 为例。以前我们是这样写的:
// 优化前:全量引入
import { Button, Modal } from 'antd';
import 'antd/dist/antd.css';
结果整个 antd 的 JS 和 CSS 都被打包进来。改成按需加载后:
// 优化后:按需引入
import Button from 'antd/es/button';
import Modal from 'antd/es/modal';
import 'antd/es/button/style/css';
import 'antd/es/modal/style/css';
配合 Webpack 的 sideEffects: false 和 Babel 插件 babel-plugin-import,打包体积直接少了 600KB。亲测有效,这里注意我踩过好几次坑:一定要确认组件库是否真正支持 tree-shaking,有些库虽然写了 ES Module,但内部有副作用代码,Webpack 会保守处理,不敢删。
第二个,依赖版本锁定。以前我们只提交了 package.json,没锁 package-lock.json,导致不同人安装的依赖版本不一致。现在强制要求 lock 文件必须提交,并在 CI 中加入校验:
# CI 脚本中加入
npm ci --prefer-offline
同时,对关键的 CDN 资源加上 SRI(Subresource Integrity)校验。比如从 CDN 引入的 polyfill:
<!-- 优化前:无校验 -->
<script src="https://cdn.jsdelivr.net/npm/core-js@3.29.0/minified.js"></script>
<!-- 优化后:带 integrity -->
<script
src="https://cdn.jsdelivr.net/npm/core-js@3.29.0/minified.js"
integrity="sha384-xxx...xxx"
crossorigin="anonymous"
></script>
这个 hash 值可以用 openssl dgst -sha384 -binary file.js | openssl base64 -A 生成。一旦 CDN 被劫持或内容被篡改,浏览器会直接拒绝加载,安全又防抽风。
第三个,狠心删依赖。跑了个脚本扫项目,发现 lodash 只在一处用了 _.get,直接替换成可选链:
// 优化前
const value = _.get(obj, 'a.b.c', 'default');
// 优化后
const value = obj?.a?.b?.c ?? 'default';
类似地,moment.js 换成 dayjs,体积从 300KB 降到 2KB。别小看这些零散依赖,积少成多,最后省了快 1MB。
性能数据对比
改完后重新跑 Lighthouse,结果如下:
- 首屏加载时间:从 5.2s 降到 820ms
- JS 总体积:从 3.8MB 降到 1.1MB
- 第三方脚本数量:从 32 个降到 9 个
- Lighthouse 性能分:从 42 分提升到 89 分
最明显的是,用户反馈“页面一下就出来了”,再也不用盯着白屏发呆。而且自从加了 SRI,再也没出现过因为 CDN 被污染导致的线上报错。
一点不完美的细节
当然,也不是百分百完美。比如按需引入后,开发体验稍微差了点——要手动写路径,不过配个 VSCode 插件也能自动补全。另外,有些老组件库确实不支持 tree-shaking,只能忍痛替换,或者自己封装轻量版。
还有一个小问题:SRI 的 hash 得每次更新依赖时重新生成,目前是手动操作,后续打算写个脚本自动更新。但整体来看,这些代价完全值得。
以上是我个人在供应链安全方面的性能优化实战,核心就是:**别让依赖拖垮你的性能,也别让未知的代码毁掉你的稳定性**。有更优的实现方式欢迎评论区交流,比如你们怎么自动化管理 SRI?或者有没有更好的按需加载方案?

暂无评论