内部包(Internal Packages)

内部包是源代码位于你的工作空间内的库。你可以快速创建内部包以在你的单体仓库中共享代码,如果以后需要,你可以选择 将它们发布到 npm 注册表

内部包在你的仓库中的使用方式是在 package.json 中安装它们,类似于来自 npm 注册表的外部包。但是,不是标记要安装的版本,你可以使用包管理器的工作空间安装语法引用包:

./apps/web/package.json
{
  "dependencies": {
    "@repo/ui": "*"
  }
}

创建内部包指南中,你可以使用编译包策略从头开始构建内部包。在本页面中,我们将描述创建内部包的其他策略及其权衡,包括将包发布到 npm 注册表以创建外部包。

然后你可以像使用外部包一样将包导入到你的代码中:

./apps/web/app/page.tsx
import { Button } from '@repo/ui'; 
 
export default function Page() {
  return <Button>Submit</Button>;
}

编译策略

根据你对库的需求,你可以选择以下三种编译策略之一:

  • 即时包:通过允许应用程序打包器在使用包时编译包来为你的包创建最小配置。
  • 编译包:通过适度的配置,使用像 tsc 或打包器这样的构建工具编译你的包。
  • 可发布包:编译并准备发布到 npm 注册表的包。这种方法需要最多的配置。

即时包

即时包由使用它的应用程序编译。这意味着你可以直接使用你的 TypeScript(或未编译的 JavaScript)文件,比本页面上的其他策略需要少得多的配置。

这种策略在以下情况下最有用:

  • 你的应用程序使用现代打包器如 Turbopack、webpack 或 Vite 构建。
  • 你想避免配置和设置步骤。
  • 你对应用程序的构建时间感到满意,即使在无法命中包的缓存时也是如此。

即时包的 package.json 可能看起来像这样:

./packages/ui/package.json
{
  "name": "@repo/ui",
  "exports": {
    "./button": "./src/button.tsx",
    "./card": "./src/card.tsx"
  },
  "scripts": {
    "lint": "eslint . --max-warnings 0",
    "check-types": "tsc --noEmit"
  }
}

这个 package.json 中有几个重要的注意事项:

  • 直接导出 TypeScriptexports 字段标记了包的入口点,在这种情况下,你直接引用 TypeScript 文件。这是可能的,因为应用程序的打包器会在其构建过程中编译代码。
  • 没有 build 脚本:因为这个包正在导出 TypeScript,所以它不需要转译包的构建步骤。这意味着你不需要在这个包中配置构建工具就可以让它在你的工作空间中工作。

限制和权衡

  • 仅适用于消费者进行转译的情况:这种策略只能在包将被使用在使用打包器或原生理解 TypeScript 的工具中时使用。消费者的打包器负责将 TypeScript 包转译为 JavaScript。如果你的构建或包的其他用途无法使用 TypeScript,你需要转向编译包策略。
  • 不支持 TypeScript paths:被其消费者转译的库不能使用 compilerOptions.paths 配置,因为 TypeScript 假设源代码是在编写它的包中被转译的。如果你使用的是 TypeScript 5.4 或更高版本,我们建议使用 Node.js 子路径导入。要了解如何操作,请访问我们的 TypeScript 页面
  • Turborepo 无法缓存即时包的构建:因为包没有自己的 build 步骤,它不能被 Turborepo 缓存。如果你想将配置保持在最小限度并且对应用程序的构建时间感到满意,这种权衡可能对你来说是合理的。
  • 内部依赖中的错误将被报告:当直接导出 TypeScript 时,如果内部依赖中的代码有 TypeScript 错误,依赖包中的类型检查将失败。在某些情况下,你可能会发现这令人困惑或有问题。

编译包

编译包是使用构建工具(如 tsc(TypeScript 编译器))处理自己编译的包。

./packages/ui/package.json
{
  "name": "@repo/ui",
  "exports": {
    "./button": {
      "types": "./src/button.tsx",
      "default": "./dist/button.js"
    },
    "./card": {
      "types": "./src/card.tsx",
      "default": "./dist/card.js"
    }
  },
  "scripts": {
    "build": "tsc"
  }
}

编译你的库会将编译后的 JavaScript 输出到一个目录(distbuild 等),你将使用这个目录作为包的入口点。一旦构建输出被添加到任务的 outputs中,它们就会被 Turborepo 缓存,让你获得更快的构建时间。

限制和权衡

  • 使用 TypeScript 编译器:大多数编译包应该使用 tsc。由于包很可能被使用打包器的应用程序消费,应用程序的打包器将准备库包以在应用程序的最终包中分发,处理 polyfill、降级和其他问题。只有在你有特定的用例需要它时才应该使用打包器,比如将静态资产打包到包的输出中。
  • 更多配置:编译包需要更深入的知识和配置来创建构建输出。TypeScript 编译器有许多配置,这些配置可能难以管理和理解,还需要进一步配置以优化打包器,比如 package.json 中的 sideEffects。你可以在我们专门的 TypeScript 指南中找到一些建议。

可发布包

将包发布到 npm 注册表需要满足本页面上打包策略中最严格的要求。因为你不知道从注册表下载包的消费者将如何使用你的包,你可能会发现由于健壮包所需的众多配置而感到困难。

此外,将包发布到 npm 注册表的过程需要专门的知识和工具。我们推荐使用 changesets 来管理版本控制、变更日志和发布过程。

有关详细指南,请访问我们的发布包指南