依赖过山车:驾驭 NPM 主题公园
Source: Dev.to
抱歉,我无法直接访问外部链接获取文章内容。请您把需要翻译的文本粘贴在这里,我会为您翻译成简体中文。
开始一切的 “啊哈!” 时刻
我在实现一个新功能,感觉自己像个代码巫师 🧙♂️。提交 PR 后,我的 TL 留下了一条评论,听起来像是外语:
You need to add these as peer dependencies so the consuming app can install them.
等等,什么? 🤔
我盯着屏幕发呆。自从接触 Node 以来,我一直在使用 dependencies 和 devDependencies,但 peerDependencies 是什么?为什么我需要安装 react-hook-form,而我使用的库既没有把它列为直接依赖,我自己也没有直接使用它?
这种困惑把我带进了一个兔子洞,彻底改变了我对 npm 依赖生态系统的理解。事实证明,管理 package.json 很像运营一个游乐园。
Analogy: Think of your project as a world‑class theme park. To keep the rides running and the guests happy, you need different types of resources.
依赖 — 主要景点
{
"dependencies": {
"react": "^18.2.0",
"axios": "^1.6.0"
}
}
这些才是真正的过山车。 如果没有 “大环路” 包,公园就不是主题公园——只是一块空停车场。
- 何时使用: 任何你的代码直接 import 并在运行时需要的包。
- 规则: 这些会自动安装。如果缺失,公园(你的应用)就会关闭(代码在生产环境中无法运行)。
DevDependencies — 维护团队
{
"devDependencies": {
"jest": "^29.0.0",
"eslint": "^8.0.0",
"typescript": "^5.0.0"
}
}
这些就是安全帽、扳手和蓝图。 游客看不到它们,但没有它们就无法建造或维修过山车。
- 何时使用: 用于开发、测试、构建或代码检查的工具(例如 Jest、ESLint、Vite)。
- 规则: 当有人将你的包作为库安装时 不会 被安装。它们只存在于施工现场(你的本地机器)。
PeerDependencies — “自带装备”要求
{
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
}
这是入口处的“要求”标志。 想象一个水滑梯,上面写着:“我们提供滑梯,但 你 必须自带泳衣。”
“啊哈!”时刻
我们的组件库就是那条水滑梯。如果我们把泳衣和每个滑梯一起打包(即把 React 放在 dependencies 中),公园里就会充斥着冗余、湿漉漉的衣服,根本不适合游客(包体膨胀 & 版本冲突)。
相反,我们在入口处检查:“你有泳衣(React 19)吗?”我们的库假设宿主环境已经安装了该包。
链式谜团 — 传递式 Peer Dependencies
如果我们的滑梯使用了特定的漂浮装置(另一个库,例如 awesome-form-components),而该漂浮装置需要一个泵(react-hook-form),那么访客就必须同时拥有两者——即使我们的库并未直接使用 react-hook-form。
Main App (The Guest)
└── My Library (The Slide)
└── awesome-form-components (The Floatie)
└── [peer] react-hook-form (The Pump) 🚩
结果: 主应用 必须 安装 react-hook-form。如果没有,npm 会抛出 Peer Dependency Resolution Error。
- 何时使用:
- 插件和 UI 套件,“插入”更大的框架(React、Vue、Tailwind)。
- 单例强制(同一包只能有一个实例)。
- 版本灵活性(让开发者使用在你指定范围内的任意兼容版本)。
OptionalDependencies — VIP 快速通行证
{
"optionalDependencies": {
"fsevents": "^2.3.0"
}
}
这些是“锦上添花”的额外功能。 也许它像是木筏漂流中的加热座位。如果加热器缺货,游乐设施仍然可以运行——只是会稍微凉一点。
- 使用时机: 平台特定的优化(例如,仅 macOS 的功能)。
- 规则: 如果安装失败,npm 只会耸耸肩并继续。你的代码应当优雅地处理“空座位”。
传递依赖 – 供应链
传递依赖是“我们依赖的依赖”。
想象一下,你买了一座过山车(直接依赖),但这座过山车是使用特定品牌的螺栓和润滑脂(传递依赖)建造的。你没有订购这些螺栓,但它们现在已经在你的乐园里了!
Your Park (Project)
├── Roller‑coaster (Direct)
│ ├── Specialized Bolts (Transitive)
│ └── Industrial Grease (Transitive)
风险: 如果这些螺栓出现安全召回(安全漏洞),整个设施都将受到威胁——即使你从未与螺栓制造商直接接触。
快速参考表
| 类型 | 何时安装? | 类比 |
|---|---|---|
dependencies | 始终 | 乘坐设施(必需) |
devDependencies | 本地仅 | 工具(施工) |
peerDependencies | 由用户决定 | 装备(泳衣/头盔) |
optionalDependencies | 如果可能 | VIP特权(额外) |
实用技巧
- 构建库吗? 使用
peerDependencies来声明框架包,这样就不会强制用户使用特定的 React 版本。这为他们提供了灵活性,并且保持 bundle 大小较小。 - 看到重复吗? 运行
npm dedupe或yarn dedupe,确保你的项目没有存储五份相同的“螺栓”。 - 锁定依赖: 始终提交
package-lock.json(或yarn.lock)。它是“已构建”蓝图,保证每个开发者(以及每次 CI 运行)使用相同的版本。
版本范围 – 我们如何告诉供应商发送什么
| Symbol | Name | Meaning | Example | Accepts | Rejects |
|---|---|---|---|---|---|
^ | 插入符号 (Caret) | 次要 + 修补更新 | ^1.2.3 | 1.2.3, 1.3.0, 1.9.9 | 2.0.0 |
~ | 波浪号 (Tilde) | 仅修补更新 | ~1.2.3 | 1.2.3, 1.2.4, 1.2.9 | 1.3.0 |
| (none) | 精确 (Exact) | 必须完全匹配的版本 | 1.2.3 | 仅 1.2.3 | 1.2.4 |
>= | 范围 (Range) | 大于或等于 | >=1.2.3 | 1.2.3, 1.3.0, 2.0.0 | — |
了解这些符号可以帮助您控制依赖版本的宽松(或严格)程度,确保您的公园安全且保持最新。
版本兼容性
1.2.0“我们的项目触发电路并抛出:SyntaxError: Cannot use import statement outside a module
在 第 2 部分 中,我将逐步讲解导致此问题的技术“线路”:
- 15 A 对比 5 A – 为什么 ESM 和 CommonJS 不能直接“即插即用”。
- 短路 – 为什么即使代码逻辑完美,构建仍会失败。
- 重型适配器 – 解决
transpilePackages的谜团。
敬请期待!⚡ 我们很快会修复这些线路。
在此之前,Pranipat 🙏