依赖过山车:驾驭 NPM 主题公园

发布: (2026年1月12日 GMT+8 13:12)
8 min read
原文: Dev.to

Source: Dev.to

抱歉,我无法直接访问外部链接获取文章内容。请您把需要翻译的文本粘贴在这里,我会为您翻译成简体中文。

开始一切的 “啊哈!” 时刻

我在实现一个新功能,感觉自己像个代码巫师 🧙‍♂️。提交 PR 后,我的 TL 留下了一条评论,听起来像是外语:

You need to add these as peer dependencies so the consuming app can install them.

等等,什么? 🤔

我盯着屏幕发呆。自从接触 Node 以来,我一直在使用 dependenciesdevDependencies,但 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 dedupeyarn dedupe,确保你的项目没有存储五份相同的“螺栓”。
  • 锁定依赖: 始终提交 package-lock.json(或 yarn.lock)。它是“已构建”蓝图,保证每个开发者(以及每次 CI 运行)使用相同的版本。

版本范围 – 我们如何告诉供应商发送什么

SymbolNameMeaningExampleAcceptsRejects
^插入符号 (Caret)次要 + 修补更新^1.2.31.2.3, 1.3.0, 1.9.92.0.0
~波浪号 (Tilde)仅修补更新~1.2.31.2.3, 1.2.4, 1.2.91.3.0
(none)精确 (Exact)必须完全匹配的版本1.2.31.2.31.2.4
>=范围 (Range)大于或等于>=1.2.31.2.3, 1.3.0, 2.0.0

了解这些符号可以帮助您控制依赖版本的宽松(或严格)程度,确保您的公园安全且保持最新。

版本兼容性

  • 1.2.0 “我们的项目触发电路并抛出:

    SyntaxError: Cannot use import statement outside a module

第 2 部分 中,我将逐步讲解导致此问题的技术“线路”:

  1. 15 A 对比 5 A – 为什么 ESM 和 CommonJS 不能直接“即插即用”。
  2. 短路 – 为什么即使代码逻辑完美,构建仍会失败。
  3. 重型适配器 – 解决 transpilePackages 的谜团。

敬请期待!⚡ 我们很快会修复这些线路。

在此之前,Pranipat 🙏

Back to Blog

相关文章

阅读更多 »