MyBotBoxMyBotBox

@yarlisai 包的使用方式

双模式架构:内部通过 bun workspace 消费源码,外部通过 npm dist 压缩包。

每个 @yarlisai/* 包同时以两种完全不同的方式被消费。理解这两种模式,就能明白为什么部署时从不涉及 npm、为什么本地开发不需要构建步骤,以及为什么发布是一条并行轨道而非部署的前置依赖。

exports 映射

两种模式都依赖同一个条件式 exports 映射,每个包中都有:

{
  "exports": {
    ".": {
      "bun": "./src/index.ts",
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js",
      "default": "./dist/index.js"
    }
  }
}

在 Bun 运行时中,bun 条件会优先解析,直接指向 TypeScript 源码。其他所有消费者(Node、遵循 import/default 条件的打包工具)则使用编译后的 dist/ 输出。

内部模式 — workspace 源码

在 monorepo 内部,应用将包声明为 "@yarlisai/<pkg>": "workspace:*"。Bun workspaces 将 node_modules/@yarlisai/<pkg> 软链接到 packages/<pkg>/bun 导出条件将导入直接解析到 src/index.ts

  • 开发时无需构建步骤。 编辑 packages/email/src/ 会立即热重载应用——无需重新构建 dist/,也不会产生过期的构建产物。
  • Docker / CD 从源码构建。 生产镜像(Dockerfile.cloudrun)复制整个 workspace,使用 turbo 构建,Next.js standalone 输出追踪会将应用实际导入的包文件打包进去。
  • 部署时从不访问 npm。 镜像仓库故障、版本被撤回或发布损坏都不会影响部署——应用始终使用 git 仓库中的代码。

外部模式 — npm 压缩包

外部消费者从 npm 安装,获得已发布的压缩包:

  • 压缩包包含 dist/(以及 README.mdLICENSE),不含 src/。解析通过 import/default 条件指向 dist/index.js,类型来自 dist/index.d.ts
  • 构建时先运行 tsc,再运行 tsc-alias --resolve-full-paths,因此生成的导入语句带有显式的 .js 扩展名——Node ESM 对此有严格要求,即使打包工具并不要求。
  • 发布时,发布工作流会将 workspace:* 依赖范围改写为真实的 semver 范围(^x.y.z)。若不进行此改写,npm install 会因无效的 semver 而失败。
  • 目前各包以 restricted 方式发布至 Yarlis npm 组织;计划后续开放公开访问。

外部路径会在 CI 中通过外部消费者冒烟测试来验证:该测试会打包每个包,将压缩包安装到一个临时项目中(使用纯 npm),并在纯 Node 环境下导入每个导出子路径——不使用 Bun,不使用 workspace 软链接。否则,bun 条件会对所有内部消费者掩盖损坏的 dist/ 构建产物。

为什么需要两种模式

  • 部署不受 npm 影响。 发布问题(以及 npm 故障)永远不会阻碍产品上线——应用通过 workspace 消费源码。
  • 极速开发迭代。 源码级消费意味着跨包修改只需一次热重载,而非构建-链接-重启的循环。
  • 框架发布轨道解耦。 版本管理、变更日志和发布通过 changesets 及专用发布工作流按自身节奏运行,无需与应用发布协调。

双模式的代价在于,内部消费可能掩盖外部的损坏——这正是 publint 检查和外部冒烟测试存在的意义。

另请参阅