我的文档库

Monorepo 内部私有包设计:让内部能力保持灵活,又不污染公共依赖体系

介绍 monorepo 中`private:true`内部私有包,依托 workspace:* 实现源码内部复用、不对外发布。划分工程脚本、测试辅助等六类包,区分开发态与运行时风险,明确禁止公开包依赖私有包,配套命名、质量分级与完整生命周期管理。

内部私有包用于承载只服务于当前 Monorepo 的能力,例如工程脚本、Mock、测试工具、内部适配层和实验性实现。

1. 什么是内部私有包

内部私有包的核心特征是:

{
  "private": true
}

它不会参与公开 npm 发布,主要通过 Workspace 在仓库内部直接共享源码。

原方案明确将内部私有包定义为“仅仓库内部共享、不发布到任何 npm 源”的包,并强调它与公开共享包是互补关系。fileciteturn1file3L163-L179

可以简单理解为:

公开共享包:稳定、标准、可复用
内部私有包:灵活、快速、内部化

2. 为什么需要私有包

并不是所有代码都值得进入公共共享体系。

例如:

  • CI 脚本;
  • 测试 Mock;
  • 临时迁移脚本;
  • 实验性技术;
  • 内部 BFF 适配;
  • 特定仓库资产;
  • 多端内部桥接层。

如果这些能力都直接复制到多个 App 中,会产生重复维护;如果强行做成公开共享包,又会增加 API 稳定性、版本管理和发布成本。

私有包提供了一个中间层:源码级复用,但不承担公共发布契约。

3. 六类常见私有包

类型示例常见依赖位置
工程工具internal-scriptsinternal-clidevDependencies
测试辅助internal-mocktest-utilsdevDependencies
业务中间层internal-adapterdomain-bridgedependencies
环境配置env-configfeature-flagsdependencies
实验与临时experimental-*temp-*按场景
内部资产internal-iconsinternal-themedependencies

原方案采用相同的六类划分,并特别区分开发态私有包和运行时私有包。fileciteturn1file3L151-L162

4. 开发态私有包:最常见的用法

绝大多数私有包应该属于开发态能力:

{
  "devDependencies": {
    "@org/internal-mock": "workspace:*",
    "@org/test-utils": "workspace:*"
  }
}

典型内容:

  • Mock 服务;
  • Fixtures;
  • Testing Library 封装;
  • CI 脚本;
  • 构建工具;
  • 代码生成器。

因为这些包位于 devDependencies,不会成为发布包的运行时依赖。

原方案将开发态私有包作为标准用法,并指出其不会影响上层公开包的发布。fileciteturn1file3L222-L230

5. 运行时私有包:谨慎使用

运行时私有包可能被放入:

{
  "dependencies": {
    "@org/internal-adapter": "workspace:*"
  }
}

此时必须特别注意:如果上层包要发布到 npm,而运行时又依赖一个不发布的私有包,下游安装后就无法解析这个依赖。

原方案明确指出运行时私有包用于业务桥接、内部适配和聚合层时需要谨慎;如果发布包依赖它,就需要在构建阶段内联相关代码,或者重新评估包边界。fileciteturn1file3L231-L259

6. 私有包的标准 package.json

一个简单的内部工具可以非常轻量:

{
  "name": "@org/internal-mock",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "main": "./src/index.ts",
  "types": "./src/index.ts"
}

对于只在仓库内使用的源码包,不一定需要完整的公开发布字段。

原方案也采用 private: true + workspace:* 的最小配置,并允许直接引用源码。fileciteturn0file4L151-L169

7. 为什么 workspace:* 适合私有包

私有包的核心目标之一是降低版本管理成本。

{
  "devDependencies": {
    "@org/internal-mock": "workspace:*"
  }
}

这样开发时可以直接关联 Workspace 源码:

Application

@org/internal-mock

packages/internal-mock/src

修改代码后,使用方可以立即获得变化,不需要执行“构建 → 发布 → 升级版本”的完整流程。

原方案将源码级共享、无版本成本作为私有包的重要价值。fileciteturn1file3L165-L179

8. 工程工具类

这是最常见的私有包类型。

例如:

packages/internal-scripts/
├── src/
│   ├── bump-deps.ts
│   ├── gen-api.ts
│   ├── archive-pkg.ts
│   └── check-deps.ts
├── bin/
└── package.json

可以承载:

  • 批量依赖升级;
  • 包元数据检查;
  • API 类型生成;
  • 废弃包归档;
  • 发布前检查;
  • 自研 CLI。

原方案给出了类似的内部脚本目录结构和用途示例。fileciteturn1file3L43-L67

9. 测试辅助类

测试私有包可以集中管理:

  • Mock 数据;
  • Fixtures;
  • 测试工具;
  • 自定义断言;
  • Testing Library 封装;
  • 通用测试环境初始化。

例如:

@org/internal-mock
@org/test-utils
@org/fixtures
@org/test-helpers

这些包应该默认只进入 devDependencies,避免测试代码进入生产产物。原方案也明确要求测试辅助类统一用于开发和测试阶段。fileciteturn1file3L68-L86

10. 业务中间层

有些能力确实需要多个应用共享,但又不适合作为稳定的业务域包,例如:

  • 多端适配层;
  • 跨域桥接;
  • 内部页面片段;
  • 内部 BFF 聚合;
  • 过渡期兼容层。

这类能力可以暂时放入私有包,以避免将临时实现直接固化到公共业务 API。

原方案将 internal-adapterdomain-bridge、内部页面片段和内部 BFF 作为典型场景。fileciteturn1file3L87-L114

11. 环境与配置类

可以建立:

@org/env-config
@org/feature-flags
@org/deploy-config
@org/runtime-env

用于:

  • 环境地址;
  • 功能开关;
  • 部署参数;
  • 运行时环境变量。

但是敏感信息必须和普通配置分离:

Repository
  └── 非敏感配置

Secret Manager / CI Secret
  └── 密钥、密码、Token

原方案明确要求 API Key、数据库密码等敏感信息不要直接进入仓库。fileciteturn1file3L115-L127

12. 实验与临时包

实验包是私有包体系中非常有价值的一类:

@org/experimental-new-store
@org/experimental-chart
@org/temp-migration

它允许团队先验证技术方案,再决定是否沉淀为正式共享包。

但必须记录:

  • 用途;
  • 负责人;
  • 创建时间;
  • 预计生命周期;
  • 转正条件;
  • 删除条件。

原方案建议使用 experimental-temp- 前缀,并要求 README 标注生命周期和负责人。fileciteturn1file3L128-L138

13. 私有包与公开包的边界

最重要的一条规则是:

Private Package

Public Package

不应该出现反方向依赖:

Public Package

Private Package   ❌

原因很简单:公开包的用户无法安装一个不存在于发布源中的私有依赖。

原方案将“私有包可以依赖公开共享包,公开共享包禁止依赖私有包”作为核心边界。fileciteturn1file5L240-L250

14. 命名规范

建议从包名就能看出其生命周期属性:

@org/internal-xxx
@org/experimental-xxx
@org/temp-xxx

业务中间层也可以显式使用 internal-domain-bridge 等命名。

原方案要求通过命名前缀避免私有包和正式共享包混淆。fileciteturn1file5L229-L244

15. 质量分级

私有包不需要全部采用和核心基础包相同的质量标准,可以根据生命周期和重要程度分级:

等级典型包最低要求
核心工程配置、核心适配测试、评审、负责人
普通Mock、业务中间层README、核心逻辑测试
临时实验、迁移脚本生命周期、负责人、清理计划

原方案也采用核心、普通、临时三级质量管控。fileciteturn1file5L245-L250

16. 生命周期管理

私有包最容易出现的问题不是创建困难,而是只创建、不删除

推荐生命周期:

Create

Internal Validation

Stable / Keep Private

Promote to Public Package
        or
      Archive

创建时:

  • 明确用途;
  • 指定负责人;
  • 记录生命周期。

定期巡检:

  • 删除过期实验;
  • 删除完成迁移的临时包;
  • 合并重复能力;
  • 评估是否应该转为正式共享包。

原方案建议按季度巡检,并在能力稳定且需要跨仓库复用时升级为公开共享包。fileciteturn1file5L251-L255

17. 标准目录结构

简单私有包:

packages/internal-mock/
├── src/
│   ├── index.ts
│   ├── user.mock.ts
│   └── order.mock.ts
├── package.json
└── README.md

复杂工具:

packages/internal-scripts/
├── src/
│   ├── index.ts
│   ├── commands/
│   └── utils/
├── bin/
├── package.json
├── tsconfig.json
└── README.md

原方案同样提供了简单 Mock 包和多模块内部脚本包两种结构。fileciteturn0file4L170-L211

18. 私有包什么时候应该“转正”

可以使用以下判断标准:

转为正式共享包

当它:

  • 被多个独立仓库需要;
  • API 已经稳定;
  • 生命周期长期存在;
  • 有明确维护负责人;
  • 需要独立版本管理。

继续保持私有

当它:

  • 只服务当前仓库;
  • 仍在快速变化;
  • 与内部 CI 或环境强绑定;
  • 不希望承担公共兼容性成本。

直接删除或归档

当它:

  • 只是一次迁移脚本;
  • 实验已经结束;
  • 被新能力完全替代;
  • 已经没有任何使用方。

19. 总结

私有包并不是“低质量代码区”,而是一种不同生命周期的工程资产。

公开包解决长期稳定复用,私有包解决内部快速复用。

真正重要的是让两者之间存在明确的晋升和退出机制,而不是让临时能力无限期留在仓库中。

本页目录