Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(docs) Fix Node.js subpath import guidance. #9066

Merged
merged 2 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/repo-docs/core-concepts/internal-packages.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ There are a few important things to notice in this `package.json`:
#### Limitations and tradeoffs

- **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](#compiled-packages) strategy.
- **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](https://devblogs.microsoft.com/typescript/announcing-typescript-5-4/#auto-import-support-for-subpath-imports).
- **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](https://devblogs.microsoft.com/typescript/announcing-typescript-5-4/#auto-import-support-for-subpath-imports). To learn how, visit [our TypeScript page](/repo/docs/guides/tools/typescript#use-nodejs-subpath-imports-instead-of-typescript-compiler-paths).
- **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.

### Compiled Packages
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,40 +315,7 @@ Using exports this way provides three major benefits:

#### `imports` (optional)

[The `imports` field](https://nodejs.org/api/packages.html#imports) gives you a way to create subpaths to other modules within your package. You can think of these like "shortcuts" to get to other modules within your package.

<Tabs items={["package.json", "add.ts", "multiply.ts"]}>
<Tab value="package.json">
```json title="./packages/ui/package.json"
{
"imports": {
"#*": "./*"
}
}
```
</Tab>
<Tab value="add.ts">
```ts title="./packages/math/src/add.ts"
export const add = (a: number, b: number) => a + b;
```
</Tab>

<Tab value="multiply.ts">
```ts title="./packages/math/src/multiply.ts"
import { add } from "#add";

export const multiply = (a: number, b: number) => {
let result = 0;
for (let i = 0; i < b; i++) {
result = add(result, a);
}
return result;
}
```
</Tab>
</Tabs>

This can be useful for concisely importing other modules within the package and can make refactors easier to manage.
[The `imports` field](https://nodejs.org/api/packages.html#imports) gives you a way to create subpaths to other modules within your package. You can think of these like "shortcuts" to write simpler import paths that are more resilient to refactors that move files. To learn how, visit [the TypeScript page](/repo/docs/guides/tools/typescript#use-nodejs-subpath-imports-instead-of-typescript-compiler-paths).

<Callout type="info">
You may be more familiar with TypeScript's `compilerOptions#paths` option, which accomplishes a similar goal. As of Typescript 5.4, TypeScript can infer subpaths from `imports`, making it a better option since you'll be working with Node.js conventions. For more information, visit [our Typescript guide](/repo/docs/guides/tools/typescript#use-nodejs-subpath-imports-instead-of-typescript-compiler-paths).
Expand Down
63 changes: 58 additions & 5 deletions docs/repo-docs/guides/tools/typescript.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Learn how to use TypeScript in a monorepo.

import { Callout } from '#/components/callout';
import { File, Folder, Files } from '#/components/files';
import { PackageManagerTabs, Tab } from '#/components/tabs';
import { PackageManagerTabs, Tabs, Tab } from '#/components/tabs';
import { LinkToDocumentation } from '#/components/link-to-documentation';

TypeScript is an excellent tool in monorepos, allowing teams to safely add types to their JavaScript code. While there is some complexity to getting set up, this guide will walk you through the important parts of a TypeScript setup for most use cases.
Expand Down Expand Up @@ -175,7 +175,7 @@ Setting up `exports` this way has several advantages:

- Using the `types` field allows `tsserver` to use the code in `src` as the source of truth for your code's types. Your editor will always be up-to-date with the latest interfaces from your code.
- You can quickly add new entrypoints to your package without creating [dangerous barrel files](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js#what's-the-problem-with-barrel-files).
- You'll receive auto-importing suggestions for your imports across package boundaries in your editor. For more information about why you may not want to wildcard the entrypoints, see the [limitations section](/repo/docs/guides/tools/typescript#package-entrypoint-wildcards).
- You'll receive auto-importing suggestions for your imports across package boundaries in your editor. For more information about why you may not want to wildcard the entrypoints, see the [limitations section](#package-entrypoint-wildcards).

<Callout type="warn">
If you're publishing the package, you cannot use references to source code in
Expand Down Expand Up @@ -221,11 +221,64 @@ For [Internal Packages](/repo/docs/core-concepts/internal-packages), we recommen

### Use Node.js subpath imports instead of TypeScript compiler `paths`

It's possible to create absolute imports in your packages using [the TypeScript compiler's `paths` option](https://www.typescriptlang.org/tsconfig#paths). However, [as of TypeScript 5.4](https://devblogs.microsoft.com/typescript/announcing-typescript-5-4/#auto-import-support-for-subpath-imports), you can use [Node.js subpath imports](https://nodejs.org/api/packages.html#imports) instead.
It's possible to create absolute imports in your packages using [the TypeScript compiler's `paths` option](https://www.typescriptlang.org/tsconfig#paths), but these paths can cause failed compilation when using [Just-in-Time Packages](https://turbo.build/repo/docs/core-concepts/internal-packages#just-in-time-packages). [As of TypeScript 5.4](https://devblogs.microsoft.com/typescript/announcing-typescript-5-4/#auto-import-support-for-subpath-imports), you can use [Node.js subpath imports](https://nodejs.org/api/packages.html#imports) instead for a more robust solution.

Additionally, Node.js subpath imports are usable in [Just-in-Time Packages](/repo/docs/core-concepts/internal-packages#just-in-time-packages) while TypeScript's `compilerOptions#paths` are not.
#### Just-in-Time packages

This strategy brings your Node.js and TypeScript configurations closer together, making your project's configuration simpler.
In [Just-in-Time packages](https://turbo.build/repo/docs/core-concepts/internal-packages#just-in-time-packages), `imports` must target the source code in the package, since build outputs like `dist` won't be created.

<Tabs storageKey="ts-imports-jit" items={["package.json", "Source code"]}>
<Tab value="package.json">
```json title="./packages/ui/package.json"
{
"imports": {
"#*": "./src/*"
}
}
```
</Tab>
<Tab value="Source code">
```tsx title="./packages/ui/button.tsx"
import { MY_STRING } from "#utils.ts" // Uses .ts extension // [!code highlight]

export const Button = () => {
return (
<button>{MY_STRING}</button>
)
}
```
</Tab>

</Tabs>

#### Compiled packages

In [Compiled packages](https://turbo.build/repo/docs/core-concepts/internal-packages#compiled-packages), `imports` target the built ouptuts for the package.

<Tabs storageKey="ts-imports-compiled" items={["package.json", "Source code"]}>
<Tab value="package.json">
```json title="./packages/ui/package.json"
{
"imports": {
"#*": "./dist/*"
}
}
```
</Tab>

<Tab value="Source code">
```tsx title="./packages/ui/button.tsx"
import { MY_STRING } from "#utils.js" // Uses .js extension // [!code highlight]

export const Button = () => {
return (
<button>{MY_STRING}</button>
)
}
```
</Tab>

</Tabs>

### You likely don't need a `tsconfig.json` file in the root of your project

Expand Down
Loading