内部软件包

内部包是源代码位于你的工作区内的库。你可以快速创建内部包,以便在你的 monorepo 中共享代码,并在以后需要时选择 将它们发布到 npm 注册表

¥Internal Packages are libraries whose source code is inside your Workspace. You can quickly make Internal Packages to share code within your monorepo and choose to publish them to the npm registry if you need to later.

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

¥Internal Packages are used in your repository by installing them in package.json similar to an external package coming from the npm registry. However, instead of marking a version to install, you can reference the package using your package manager's workspace installation syntax:

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

创建内部包指南 中,你可以使用 编译包策略 从头构建内部包。在本页面中,我们将介绍创建内部包的其他策略及其利弊,包括使用 将软件包发布到 npm 注册表 创建外部包。

¥In the Creating an Internal Package guide, you can build an Internal Package from the beginning using the Compiled Package strategy. On this page, we'll describe other strategies for creating Internal Packages and their tradeoffs, including publishing the package to the npm registry to create an External Package.

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

¥You can then import the package into your code like you're used to doing with an external package:

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

编译策略

¥Compilation Strategies

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

¥Depending on what you need from your library, you can choose one of three compilation strategies:

  • 即时软件包:通过允许应用打包器在使用包时对其进行编译,为你的包创建最小配置。

    ¥Just-in-Time Packages: Create minimal configuration for your package by allowing application bundlers to compile the package as it uses it.

  • 已编译的软件包:使用适量的配置,使用构建工具(例如 tsc)或打包器编译你的软件包。

    ¥Compiled Packages: With a moderate amount of configuration, compile your package using a build tool like tsc or a bundler.

  • 可发布的软件包:编译并准备一个包以发布到 npm 仓库。此方法需要最多的配置。

    ¥Publishable Packages: Compile and prepare a package to publish to the npm registry. This approach requires the most configuration.

即时软件包

¥Just-in-Time Packages

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

¥A Just-in-Time package is compiled by the application that uses it. This means you can use your TypeScript (or uncompiled JavaScript) files directly, requiring much less configuration than the other strategies on this page.

此策略在以下情况下最有用:

¥This strategy is most useful when:

  • 你的应用是使用 Turbopack、webpack 或 Vite 等现代打包工具构建的。

    ¥Your applications are built using a modern bundler like Turbopack, webpack, or Vite.

  • 你希望避免配置和设置步骤。

    ¥You want to avoid configuration and setup steps.

  • 即使软件包无法命中缓存,你对应用的构建时间也感到满意。

    ¥You're satisfied with your applications' build times, even when you can't hit cache for the package.

即时包的 package.json 可能如下所示:

¥A package.json for a Just-in-Time package may look like this one:

./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 中,有几点需要注意:

¥There are a few important things to notice in this package.json:

  • 直接导出 TypeScript:exports 字段标记包的入口点,在本例中,你直接引用的是 TypeScript 文件。这是因为应用的打包器会在构建过程中使用代码时对其进行编译。

    ¥Directly exporting TypeScript: The exports field marks the entrypoints for the package and, in this case, you're referencing TypeScript files directly. This is possible because the bundler for the application will compile the code as it uses it in its build process.

  • 不支持 build 脚本:由于此包导出的是 TypeScript,因此无需构建步骤来转译包。这意味着你无需在此包中配置构建工具即可使其在你的工作区中工作。

    ¥No build script: Because this package is exporting TypeScript, it doesn't need a build step for transpiling the package. This means you don't have to configure a build tool in this package to make it work in your Workspace.

限制和权衡

¥Limitations and tradeoffs

  • 仅在使用者进行转译时适用:此策略仅当包将用于使用打包器或原生支持 TypeScript 的工具时才适用。消费者的打包器负责将 TypeScript 包转换为 JavaScript。如果你的构建或包的其他用法无法使用 TypeScript,则需要迁移到 已编译的软件包 策略。

    ¥Only applicable when consumers do transpiling: This strategy can only be used when the package is going to be used in tooling that uses a bundler or natively understands TypeScript. The consumer's bundler is responsible for transpiling the TypeScript packages to JavaScript. If your builds or other usages of the package are not able to consume TypeScript, you will need to move to the Compiled Packages strategy.

  • 不支持 TypeScript paths:正在被使用者转译的库无法使用 compilerOptions.paths 配置,因为 TypeScript 假定源代码是在编写它的包中进行转译的。如果你使用的是 TypeScript 5.4 或更高版本,我们推荐使用 使用 Node.js 子路径导入。要了解如何操作,请访问 我们的 TypeScript 页面

    ¥No TypeScript paths: A library that is being transpiled by its consumer cannot use the compilerOptions.paths configuration because TypeScript assumes that source code is being transpiled in the package where it is written. If you're using TypeScript 5.4 or later, we recommend using Node.js subpath imports. To learn how, visit our TypeScript page.

  • Turborepo 无法缓存即时软件包的构建:由于该包没有自己的 build 步骤,因此无法被 Turborepo 缓存。如果你希望将配置保持在最低限度,并且对应用的构建时间没有异议,那么这种权衡可能对你来说很有意义。

    ¥Turborepo cannot cache a build for a Just-in-Time Package: Because the package doesn't have its own build step, it can't be cached by Turborepo. This tradeoff may make sense for you if you want to keep configuration to a minimum and are okay with the build times for your applications.

  • 将报告内部依赖中的错误:直接导出 TypeScript 时,如果内部依赖中的代码存在 TypeScript 错误,则依赖包中的类型检查将失败。在某些情况下,你可能会发现这令人困惑或有问题。

    ¥Errors in internal dependencies will be reported: When directly exporting TypeScript, type-checking in a dependent package will fail if code in an internal dependency has TypeScript errors. You may find this confusing or problematic in some situations.

已编译的软件包

¥Compiled Packages

已编译包是指使用构建工具(例如 tsc(TypeScript 编译器))自行编译的包。

¥A Compiled Package is a package that handles its own compilation using a build tool, like tsc (the TypeScript compiler).

./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 缓存,从而加快构建速度。

¥Compiling your library produces compiled JavaScript outputs into a directory (dist, build, etc.) that you will use for the entrypoints for your package. The build outputs will be cached by Turborepo once they're added to the outputs key of the task, allowing you to have faster build times.

限制和权衡

¥Limitations and tradeoffs

  • 使用 TypeScript 编译器:大多数已编译的包都应该使用 tsc。由于该软件包很可能被使用打包器的应用使用,因此应用的打包器将准备库软件包,以便在应用的最终软件包中分发,处理 polyfill、降级和其他问题。仅当你有特定用例需要它时才应使用打包器,例如将静态资源打包到包的输出中。

    ¥Using the TypeScript compiler: The majority of Compiled Packages should use tsc. Since the package is highly likely to be consumed by an application that is using a bundler, the application's bundler will prepare the library package for distribution in the application's final bundles, handling polyfilling, downleveling, and other concerns. A bundler should only be used if you have a specific use case that requires it, like bundling static assets into your package's outputs.

  • 更多配置:编译包需要更深入的知识和配置才能创建构建输出。有些 TypeScript 编译器的许多配置 可能难以管理和理解,并且需要进一步配置以针对打包器(例如 package.json 中的 sideEffects)进行优化。你可以在 我们的专用 TypeScript 指南 中找到我们的一些建议。

    ¥More configuration: Compiled Packages require deeper knowledge and configuration to create build outputs. There are many configurations for the TypeScript compiler that can be difficult to manage and understand, and further configuration to optimize for bundlers, like the sideEffects key in package.json. You can find some of our recommendations in our dedicated TypeScript guide.

可发布的包

¥Publishable packages

将包发布到 npm 仓库符合本页面打包策略中最严格的要求。由于你不了解从注册表下载包的用户将如何使用你的包,因此你可能会发现这很困难,因为要构建一个健壮的包需要进行大量配置。

¥Publishing a package to the npm registry comes with the most strict requirements of the packaging strategies on this page. Because you don't know anything about how your package will be used by consumers who download the package from the registry, you may find it difficult due to the numerous configurations required for a robust package.

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

¥Additionally, the process of publishing a package to the npm registry requires specialized knowledge and tooling. We recommend changesets for managing versioning, changelogs, and the publishing process.

详细指南:请访问 我们的软件包发布指南

¥For a detailed guide, visit our Publishing packages guide.