@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.md和LICENSE),不含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 检查和外部冒烟测试存在的意义。
另请参阅
- 框架概览
- ADR 0007 — 端口/适配器契约
- 访问权限切换操作手册:monorepo 中的
docs/maintenance/npm-public-launch.md