Files
website-enchun-mgr/.agent/skills/turborepo/references/best-practices/packages.md
pkupuk ad8e2e313e chore(agent): configure AI agents and tools
Add configuration for BMad, Claude, OpenCode, and other AI agent tools and workflows.
2026-02-11 11:51:23 +08:00

336 lines
6.2 KiB
Markdown

# Creating Internal Packages
How to create and structure internal packages in your monorepo.
## Package Creation Checklist
1. Create directory in `packages/`
2. Add `package.json` with name and exports
3. Add source code in `src/`
4. Add `tsconfig.json` if using TypeScript
5. Install as dependency in consuming packages
6. Run package manager install to update lockfile
## Package Compilation Strategies
### Just-in-Time (JIT)
Export TypeScript directly. The consuming app's bundler compiles it.
```json
// packages/ui/package.json
{
"name": "@repo/ui",
"exports": {
"./button": "./src/button.tsx",
"./card": "./src/card.tsx"
},
"scripts": {
"lint": "eslint .",
"check-types": "tsc --noEmit"
}
}
```
**When to use:**
- Apps use modern bundlers (Turbopack, webpack, Vite)
- You want minimal configuration
- Build times are acceptable without caching
**Limitations:**
- No Turborepo cache for the package itself
- Consumer must support TypeScript compilation
- Can't use TypeScript `paths` (use Node.js subpath imports instead)
### Compiled
Package handles its own compilation.
```json
// packages/ui/package.json
{
"name": "@repo/ui",
"exports": {
"./button": {
"types": "./src/button.tsx",
"default": "./dist/button.js"
}
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
}
}
```
```json
// packages/ui/tsconfig.json
{
"extends": "@repo/typescript-config/library.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
```
**When to use:**
- You want Turborepo to cache builds
- Package will be used by non-bundler tools
- You need maximum compatibility
**Remember:** Add `dist/**` to turbo.json outputs!
## Defining Exports
### Multiple Entrypoints
```json
{
"exports": {
".": "./src/index.ts", // @repo/ui
"./button": "./src/button.tsx", // @repo/ui/button
"./card": "./src/card.tsx", // @repo/ui/card
"./hooks": "./src/hooks/index.ts" // @repo/ui/hooks
}
}
```
### Conditional Exports (Compiled)
```json
{
"exports": {
"./button": {
"types": "./src/button.tsx",
"import": "./dist/button.mjs",
"require": "./dist/button.cjs",
"default": "./dist/button.js"
}
}
}
```
## Installing Internal Packages
### Add to Consuming Package
```json
// apps/web/package.json
{
"dependencies": {
"@repo/ui": "workspace:*" // pnpm/bun
// "@repo/ui": "*" // npm/yarn
}
}
```
### Run Install
```bash
pnpm install # Updates lockfile with new dependency
```
### Import and Use
```typescript
// apps/web/src/page.tsx
import { Button } from '@repo/ui/button';
export default function Page() {
return <Button>Click me</Button>;
}
```
## One Purpose Per Package
### Good Examples
```
packages/
├── ui/ # Shared UI components
├── utils/ # General utilities
├── auth/ # Authentication logic
├── database/ # Database client/schemas
├── eslint-config/ # ESLint configuration
├── typescript-config/ # TypeScript configuration
└── api-client/ # Generated API client
```
### Avoid Mega-Packages
```
// BAD: One package for everything
packages/
└── shared/
├── components/
├── utils/
├── hooks/
├── types/
└── api/
// GOOD: Separate by purpose
packages/
├── ui/ # Components
├── utils/ # Utilities
├── hooks/ # React hooks
├── types/ # Shared TypeScript types
└── api-client/ # API utilities
```
## Config Packages
### TypeScript Config
```json
// packages/typescript-config/package.json
{
"name": "@repo/typescript-config",
"exports": {
"./base.json": "./base.json",
"./nextjs.json": "./nextjs.json",
"./library.json": "./library.json"
}
}
```
### ESLint Config
```json
// packages/eslint-config/package.json
{
"name": "@repo/eslint-config",
"exports": {
"./base": "./base.js",
"./next": "./next.js"
},
"dependencies": {
"eslint": "^8.0.0",
"eslint-config-next": "latest"
}
}
```
## Common Mistakes
### Forgetting to Export
```json
// BAD: No exports defined
{
"name": "@repo/ui"
}
// GOOD: Clear exports
{
"name": "@repo/ui",
"exports": {
"./button": "./src/button.tsx"
}
}
```
### Wrong Workspace Syntax
```json
// pnpm/bun
{ "@repo/ui": "workspace:*" } // Correct
// npm/yarn
{ "@repo/ui": "*" } // Correct
{ "@repo/ui": "workspace:*" } // Wrong for npm/yarn!
```
### Missing from turbo.json Outputs
```json
// Package builds to dist/, but turbo.json doesn't know
{
"tasks": {
"build": {
"outputs": [".next/**"] // Missing dist/**!
}
}
}
// Correct
{
"tasks": {
"build": {
"outputs": [".next/**", "dist/**"]
}
}
}
```
## TypeScript Best Practices
### Use Node.js Subpath Imports (Not `paths`)
TypeScript `compilerOptions.paths` breaks with JIT packages. Use Node.js subpath imports instead (TypeScript 5.4+).
**JIT Package:**
```json
// packages/ui/package.json
{
"imports": {
"#*": "./src/*"
}
}
```
```typescript
// packages/ui/button.tsx
import { MY_STRING } from "#utils.ts"; // Uses .ts extension
```
**Compiled Package:**
```json
// packages/ui/package.json
{
"imports": {
"#*": "./dist/*"
}
}
```
```typescript
// packages/ui/button.tsx
import { MY_STRING } from "#utils.js"; // Uses .js extension
```
### Use `tsc` for Internal Packages
For internal packages, prefer `tsc` over bundlers. Bundlers can mangle code before it reaches your app's bundler, causing hard-to-debug issues.
### Enable Go-to-Definition
For Compiled Packages, enable declaration maps:
```json
// tsconfig.json
{
"compilerOptions": {
"declaration": true,
"declarationMap": true
}
}
```
This creates `.d.ts` and `.d.ts.map` files for IDE navigation.
### No Root tsconfig.json Needed
Each package should have its own `tsconfig.json`. A root one causes all tasks to miss cache when changed. Only use root `tsconfig.json` for non-package scripts.
### Avoid TypeScript Project References
They add complexity and another caching layer. Turborepo handles dependencies better.