# Lang Typescript Library Dev

> TypeScript-specific library/package development patterns. Use when creating npm packages, configuring package.json exports, setting up tsconfig.json for libraries, generating declaration files, publishing to npm, or configuring ESM/CJS dual packages. Extends meta-library-dev with TypeScript tooling and ecosystem patterns.

- Skill: `arustydev/lang-typescript-library-dev` (Agent Skill)
- Install (CLI): `npx skillmds@latest add arustydev/lang-typescript-library-dev`
- Raw SKILL.md: https://api.skillmd.com/api/skills/arustydev/lang-typescript-library-dev/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: aRustyDev (https://skillmd.com/u/arustydev)
- Updated: 2026-09-08
- Page: https://skillmd.com/skills/arustydev/lang-typescript-library-dev

---


# TypeScript Library Development

TypeScript-specific patterns for library/package development. This skill extends `meta-library-dev` with TypeScript tooling, module system configuration, and npm ecosystem practices.

## This Skill Extends

- `meta-library-dev` - Foundational library patterns (API design, versioning, testing strategies)

For general concepts like semantic versioning, module organization principles, and testing pyramids, see the meta-skill first.

## This Skill Adds

- **TypeScript tooling**: tsconfig.json for libraries, declaration files, source maps
- **Package configuration**: package.json exports, ESM/CJS dual packages, bundling
- **npm ecosystem**: Publishing workflow, scoped packages, monorepos

## This Skill Does NOT Cover

- General library patterns - see `meta-library-dev`
- TypeScript syntax/patterns - see `lang-typescript-patterns-dev`
- React component libraries - see frontend skills
- Node.js application development

---

## Overview

Publishing a TypeScript library requires careful configuration of multiple interconnected systems:

```
┌─────────────────────────────────────────────────────────────────┐
│                    TypeScript Library Stack                     │
├─────────────────────────────────────────────────────────────────┤
│  Source Code (src/)                                             │
│       │                                                         │
│       ▼                                                         │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐         │
│  │ tsconfig    │───▶│ TypeScript  │───▶│ Declaration │         │
│  │   .json     │    │  Compiler   │    │ Files (.d.ts)│         │
│  └─────────────┘    └─────────────┘    └─────────────┘         │
│       │                   │                   │                 │
│       │                   ▼                   │                 │
│       │            ┌─────────────┐            │                 │
│       │            │  JavaScript │            │                 │
│       │            │   Output    │            │                 │
│       │            └─────────────┘            │                 │
│       │                   │                   │                 │
│       ▼                   ▼                   ▼                 │
│  ┌─────────────────────────────────────────────────────┐       │
│  │                   package.json                       │       │
│  │  ┌─────────┐  ┌─────────┐  ┌─────────┐             │       │
│  │  │ exports │  │  main   │  │  types  │             │       │
│  │  │  field  │  │ module  │  │  field  │             │       │
│  │  └─────────┘  └─────────┘  └─────────┘             │       │
│  └─────────────────────────────────────────────────────┘       │
│                          │                                      │
│                          ▼                                      │
│                    ┌───────────┐                                │
│                    │    npm    │                                │
│                    │  publish  │                                │
│                    └───────────┘                                │
└─────────────────────────────────────────────────────────────────┘
```

**Key Decision Points:**

| Decision | Options | Recommendation |
|----------|---------|----------------|
| Module format | ESM-only, CJS-only, Dual | ESM-only for new packages; Dual if supporting legacy |
| Build tool | tsc, tsup, unbuild, rollup | tsup for simplicity; tsc for control |
| Declaration files | Inline, Separate dir | Inline (same dir as JS) |
| Monorepo tool | pnpm workspaces, turborepo, nx | pnpm workspaces for simplicity |

---

## Quick Reference

| Task | Command |
|------|---------|
| New package | `npm init` or `pnpm init` |
| Build | `tsc` or bundler command |
| Test | `vitest` or `jest` |
| Lint | `eslint .` |
| Format | `prettier --write .` |
| Pack (dry run) | `npm pack --dry-run` |
| Publish | `npm publish` |
| Publish (scoped public) | `npm publish --access public` |

---

## Package.json Structure

### Required Fields for Publishing

```json
{
  "name": "my-library",
  "version": "1.0.0",
  "description": "A brief description of what this library does",
  "license": "MIT",
  "author": "Your Name <email@example.com>",
  "repository": {
    "type": "git",
    "url": "https://github.com/username/repo"
  },
  "keywords": ["keyword1", "keyword2", "keyword3"],
  "type": "module",
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  },
  "files": ["dist"],
  "engines": {
    "node": ">=18.0.0"
  }
}
```

### Exports Field (Modern)

The `exports` field controls what can be imported:

```json
{
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    },
    "./utils": {
      "types": "./dist/utils.d.ts",
      "import": "./dist/utils.js",
      "require": "./dist/utils.cjs"
    },
    "./package.json": "./package.json"
  }
}
```

**Order matters**: `types` must come first for TypeScript resolution.

### Files Field

Control what gets published:

```json
{
  "files": [
    "dist",
    "!dist/**/*.test.*",
    "!dist/**/*.spec.*"
  ]
}
```

Always verify with `npm pack --dry-run`.

---

## tsconfig.json for Libraries

### Base Configuration

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "lib": ["ES2022"],

    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,

    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,

    "outDir": "./dist",
    "rootDir": "./src",

    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}
```

### Declaration Files

| Option | Purpose |
|--------|---------|
| `declaration: true` | Generate `.d.ts` files |
| `declarationMap: true` | Enable "Go to Definition" in source |
| `emitDeclarationOnly: true` | Only emit declarations (use with bundler) |
| `declarationDir` | Separate output for declarations |

### Module Systems

| Config | Output | Use Case |
|--------|--------|----------|
| `"module": "NodeNext"` | ESM with `.js` | Modern Node.js packages |
| `"module": "CommonJS"` | CJS with `.js` | Legacy Node.js |
| `"module": "ESNext"` | ESM | For bundlers |

---

## ESM/CJS Dual Package

### Strategy 1: Dual Build (Recommended)

Build both formats from TypeScript:

```json
{
  "scripts": {
    "build": "npm run build:esm && npm run build:cjs",
    "build:esm": "tsc -p tsconfig.esm.json",
    "build:cjs": "tsc -p tsconfig.cjs.json"
  }
}
```

**tsconfig.esm.json:**
```json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "NodeNext",
    "outDir": "./dist/esm"
  }
}
```

**tsconfig.cjs.json:**
```json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "CommonJS",
    "outDir": "./dist/cjs"
  }
}
```

### Strategy 2: Use a Bundler

Use tsup, unbuild, or rollup for simpler dual builds:

**tsup.config.ts:**
```typescript
import { defineConfig } from 'tsup';

export default defineConfig({
  entry: ['src/index.ts'],
  format: ['esm', 'cjs'],
  dts: true,
  clean: true,
  sourcemap: true,
});
```

**package.json scripts:**
```json
{
  "scripts": {
    "build": "tsup"
  }
}
```

### Strategy 3: ESM-Only (Simplest)

For modern packages, consider ESM-only:

```json
{
  "type": "module",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "default": "./dist/index.js"
    }
  }
}
```

---

## Public API Design

### Export Patterns

**Explicit Named Exports (Preferred):**
```typescript
// src/index.ts
export { parse, serialize } from './parser.js';
export { validate } from './validator.js';
export type { Config, Options, Result } from './types.js';
```

**Avoid Default Exports:**
```typescript
// Avoid: Harder to tree-shake, inconsistent naming
export default class Parser { }

// Prefer: Named exports
export class Parser { }
```

### Type Exports

**Use `export type` for type-only exports:**
```typescript
// Enables proper tree-shaking and prevents runtime import
export type { User, Config } from './types.js';

// Re-export with types
export { parseUser, type ParseOptions } from './parser.js';
```

### Barrel Files

**src/index.ts (public API):**
```typescript
// Public API - explicit exports
export { createClient } from './client.js';
export { parse, serialize } from './parser.js';
export type { ClientOptions, ParseResult } from './types.js';

// Do NOT re-export internal modules
// import './internal.js';  // Wrong
```

---

## Type Declaration Best Practices

### Provide Good Types

```typescript
// Good: Specific, useful types
export interface ClientOptions {
  baseUrl: string;
  timeout?: number;
  headers?: Record<string, string>;
}

export function createClient(options: ClientOptions): Client;

// Avoid: Overly generic
export function createClient(options: object): unknown;
```

### Use Generics Appropriately

```typescript
// Good: Generic with constraints
export function parse<T extends Record<string, unknown>>(
  input: string,
  schema: Schema<T>
): T;

// Good: Infer return type
export function map<T, U>(
  items: T[],
  fn: (item: T) => U
): U[];
```

### Document with JSDoc

```typescript
/**
 * Parses a configuration string into a typed object.
 *
 * @param input - The configuration string to parse
 * @param options - Optional parsing options
 * @returns The parsed configuration object
 * @throws {ParseError} If the input is malformed
 *
 * @example
 * ```typescript
 * const config = parse('key=value', { strict: true });
 * console.log(config.key); // 'value'
 * ```
 */
export function parse<T>(input: string, options?: ParseOptions): T;
```

---

## Testing Libraries

### Vitest Configuration

**vitest.config.ts:**
```typescript
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    environment: 'node',
    include: ['src/**/*.test.ts'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      exclude: ['**/*.test.ts', '**/*.d.ts'],
    },
  },
});
```

### Test File Organization

```
src/
├── parser.ts
├── parser.test.ts      # Unit tests next to source
├── validator.ts
├── validator.test.ts
└── __tests__/          # Or separate test directory
    └── integration.test.ts
```

### Type Testing

**Test that types work correctly:**
```typescript
import { expectTypeOf } from 'vitest';
import { parse } from './parser.js';

test('parse returns correct type', () => {
  const result = parse('{"name": "test"}');
  expectTypeOf(result).toEqualTypeOf<ParsedResult>();
});
```

---

## Monorepo Patterns

### pnpm Workspace

**pnpm-workspace.yaml:**
```yaml
packages:
  - 'packages/*'
```

### Package Structure

```
my-monorepo/
├── package.json
├── pnpm-workspace.yaml
├── tsconfig.json          # Base config
└── packages/
    ├── core/
    │   ├── package.json
    │   ├── tsconfig.json  # Extends base
    │   └── src/
    └── utils/
        ├── package.json
        ├── tsconfig.json
        └── src/
```

### Internal Dependencies

```json
{
  "name": "@myorg/app",
  "dependencies": {
    "@myorg/core": "workspace:*",
    "@myorg/utils": "workspace:*"
  }
}
```

### Project References

**Root tsconfig.json:**
```json
{
  "references": [
    { "path": "./packages/core" },
    { "path": "./packages/utils" }
  ]
}
```

**Package tsconfig.json:**
```json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "composite": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "references": [
    { "path": "../utils" }
  ]
}
```

---

## Publishing to npm

### Pre-publish Checklist

- [ ] `npm run build` succeeds
- [ ] `npm run test` passes
- [ ] `npm run lint` passes
- [ ] Version bumped in package.json
- [ ] CHANGELOG.md updated
- [ ] README.md is current
- [ ] `npm pack --dry-run` shows correct files
- [ ] Types are correctly generated
- [ ] Exports work: `node -e "import('my-lib')"`

### Publishing Commands

```bash
# Verify package contents
npm pack --dry-run

# Publish to npm
npm publish

# Publish scoped package as public
npm publish --access public

# Publish with tag (for pre-releases)
npm publish --tag beta
```

### Scoped Packages

```json
{
  "name": "@myorg/my-library",
  "publishConfig": {
    "access": "public"
  }
}
```

### Automation with Changesets

```bash
# Initialize changesets
npx changeset init

# Add a changeset
npx changeset

# Version packages
npx changeset version

# Publish
npx changeset publish
```

---

## Common Dependencies

### Build Tools

```json
{
  "devDependencies": {
    "typescript": "^5.0.0",
    "tsup": "^8.0.0",
    "@types/node": "^20.0.0"
  }
}
```

### Testing

```json
{
  "devDependencies": {
    "vitest": "^1.0.0",
    "@vitest/coverage-v8": "^1.0.0"
  }
}
```

### Linting/Formatting

```json
{
  "devDependencies": {
    "eslint": "^8.0.0",
    "typescript-eslint": "^7.0.0",
    "prettier": "^3.0.0"
  }
}
```

---

## Anti-Patterns

### 1. Missing Types Field

```json
// Bad: Types not specified
{
  "main": "./dist/index.js"
}

// Good: Types explicitly declared
{
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts"
}
```

### 2. Wrong Export Order

```json
// Bad: types not first
{
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "types": "./dist/index.d.ts"
    }
  }
}

// Good: types first
{
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    }
  }
}
```

### 3. Publishing Source Files

```json
// Bad: Publishing everything
{
  "files": ["src", "dist"]
}

// Good: Only publish dist
{
  "files": ["dist"]
}
```

### 4. Missing Peer Dependencies

```json
// Bad: Bundling React in a React library
{
  "dependencies": {
    "react": "^18.0.0"
  }
}

// Good: Peer dependency
{
  "peerDependencies": {
    "react": "^18.0.0"
  }
}
```

---

## Troubleshooting

### Types Not Found by Consumers

**Symptom:** `Cannot find module 'my-lib' or its corresponding type declarations`

**Causes & Fixes:**

| Cause | Fix |
|-------|-----|
| Missing `types` field | Add `"types": "./dist/index.d.ts"` to package.json |
| Wrong export order | Put `types` first in exports conditions |
| Declaration files not generated | Set `"declaration": true` in tsconfig.json |
| Files not published | Check `files` field includes `dist` |

**Diagnostic:**
```bash
# Check what's actually published
npm pack --dry-run

# Validate types configuration
npx @arethetypeswrong/cli my-package
```

### ESM/CJS Import Errors

**Symptom:** `ERR_REQUIRE_ESM` or `Must use import to load ES Module`

**Common Fixes:**

```json
// Ensure package.json has correct type
{
  "type": "module"  // For ESM-first packages
}

// Or provide both formats in exports
{
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  }
}
```

### Declaration Files Missing Exports

**Symptom:** Types exist but some exports show as `any`

**Fixes:**
1. Ensure all exports use `export` keyword (not just `module.exports`)
2. Check `include` in tsconfig.json covers all source files
3. Verify no `// @ts-ignore` hiding type errors

### Monorepo Package Resolution

**Symptom:** `Cannot find module '@myorg/shared'` in monorepo

**Fixes:**

```json
// tsconfig.json - Add path mapping
{
  "compilerOptions": {
    "paths": {
      "@myorg/*": ["./packages/*/src"]
    }
  }
}

// Or use TypeScript project references
{
  "references": [
    { "path": "../shared" }
  ]
}
```

### Build Output Issues

| Problem | Solution |
|---------|----------|
| Output files have wrong extension | Check `module` setting matches desired output |
| Source maps not working | Enable `sourceMap` and `declarationMap` |
| Test files in dist | Add test patterns to `exclude` in tsconfig |
| node_modules in output | Ensure `rootDir` is set to `./src` |

### Publishing Failures

**Pre-publish checklist:**
```bash
# 1. Verify package contents
npm pack --dry-run

# 2. Test local install
npm pack && npm install ./my-package-1.0.0.tgz

# 3. Test imports work
node -e "import('my-package').then(console.log)"

# 4. Check for accidental secrets
grep -r "api_key\|password\|secret" dist/
```

---

## References

- `meta-library-dev` - Foundational library patterns
- `lang-typescript-patterns-dev` - TypeScript syntax and patterns
- [TypeScript Handbook: Publishing](https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html)
- [npm Docs: package.json](https://docs.npmjs.com/cli/v10/configuring-npm/package-json)
- [Are The Types Wrong?](https://arethetypeswrong.github.io/) - Validate package types

