Development Guide
This document provides technical details for developers working on this TypeScript package template, including build architecture, development workflows, and implementation patterns.
Build Architecture
TypeScript Configuration
Multiple TypeScript Configurations:
tsconfig.json
- Development configuration with all files includedtsconfig.build.json
- Production build configuration, excludes tests and dev files
Key Settings:
- Target:
ESNext
for modern JavaScript features - Module:
ESNext
for ES modules - Path aliases:
@/*
maps tosrc/*
for clean imports - Strict mode enabled for type safety
[!TIP] For detailed information about TypeScript setup, see the TypeScript Setup Guide.
Build System (tsup)
Why tsup over tsc:
- Fast builds with esbuild under the hood
- Dual format output (ESM + CJS) without complex configuration
- Automatic declaration file generation
- Built-in code splitting and tree-shaking
Configuration (tsup.config.ts
):
export default defineConfig({
target: "node20", // Target Node.js 20+
entry: ["src/index.ts"], // Single entry point
outDir: "dist", // Output directory
format: ["esm", "cjs"], // Dual module format
sourcemap: true, // Source maps for debugging
clean: true, // Clean output before build
dts: true, // Generate .d.ts files
splitting: true, // Code splitting for better tree-shaking
treeshake: true, // Remove unused code
esbuildOptions(options) {
options.alias = {
"@": "./src",
};
},
});
Module Format Strategy
Dual ESM/CJS Support:
- Modern bundlers use ESM (
module
field) - Legacy tools use CJS (
main
field) - Modern resolution via
exports
field
package.json Configuration:
{
"main": "./dist/index.cjs", // CommonJS entry
"module": "./dist/index.js", // ESM entry
"types": "./dist/index.d.ts", // TypeScript declarations
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"require": "./dist/index.cjs"
}
},
"./helpers": {
"import": {
"types": "./dist/helpers/index.d.ts",
"import": "./dist/helpers/index.js"
},
"require": {
"types": "./dist/helpers/index.d.cts",
"require": "./dist/helpers/index.cjs"
}
}
},
}
Testing Strategy
Vitest Configuration
Why Vitest:
- Fast execution with native ES modules support
- Hot module reloading for watch mode
- Built-in TypeScript support
- Rich assertion API and mocking capabilities
Key Features Configured:
- Path alias support (
@/
→src/
) - Coverage reporting with v8 provider
- UI mode for interactive testing
- Comprehensive coverage tracking
Test Patterns:
- Unit tests in
tests/
directory - Test files named
*.test.ts
- Type constraint testing for runtime validation
- Mock strategies for external dependencies
Coverage Configuration
Coverage Setup:
- Provider: v8 (faster than c8)
- Reporters: text, json, html
- Include:
src/**/*.ts
- Exclude: test files, coverage, node_modules
Development Workflow
Validation Pipeline
The npm run validate
Command:
- Type checking -
tsc --noEmit --project tsconfig.build.json
- Linting with auto-fix -
eslint --fix
- Formatting with auto-fix -
prettier --write .
- Testing -
vitest run
Why This Order:
- Type check first to catch structural issues
- Auto-fix linting and formatting to reduce noise
- Tests last to validate working code
Development Scripts
Core Development:
npm run dev
- tsx watch mode for rapid iterationnpm run debug
- tsx with inspect-brk for debuggingnpm run build
- Production build with tsup
Quality Assurance:
npm run lint
- ESLint check onlynpm run format
- Prettier check onlynpm run type-check
- TypeScript validation only
Testing:
npm run test
- Single test runnpm run test:watch
- Watch mode testingnpm run test:ui
- Interactive test interfacenpm run test:coverage
- Coverage report generation
Key Implementation Patterns
Runtime Validation Pattern
Dual Validation Strategy:
- TypeScript provides compile-time type safety
- Runtime validation provides JavaScript user safety
Implementation:
export const example = (person: Person) => {
// Runtime validation for JavaScript users
if (
!person ||
typeof person.name !== "string" ||
typeof person.age !== "number"
) {
throw new Error("Invalid person object...");
}
// Implementation continues...
};
Type Constraint Testing
Testing TypeScript Types:
// Test invalid types would fail compilation
const testCases = [
{ name: "John Doe" }, // missing age
{ age: 30 }, // missing name
];
testCases.forEach((invalidPerson) => {
// @ts-expect-error - Intentionally testing invalid types
expect(() => example(invalidPerson)).toThrow("Invalid person object");
});
Path Alias Usage
Clean Imports:
- Use
@/types
instead of./types
or../types
or@/types.ts
- Consistent across TypeScript, build tools, and tests
- Configured in tsconfig.json, tsup.config.ts, and vitest.config.ts
Code Quality Setup
ESLint Configuration
Rules Applied:
- TypeScript ESLint recommended rules
- Prettier integration to avoid conflicts
- Consistent casing enforcement
- Modern JavaScript/TypeScript patterns
Prettier Configuration
Formatting Strategy:
- Consistent code style across all files
- Automatic formatting on save (with editor setup)
- Integration with ESLint to avoid rule conflicts
File Organization
Source Structure
src/
├── index.ts # Main exports and public API
└── logger.ts # Logger utility for consistent logging
└── helpers/ # Helper functions and utilities
Test Structure
tests/
└── index.test.ts # Comprehensive tests including type constraints
Configuration Files
├── tsconfig.json # Development TypeScript config
├── tsconfig.build.json # Build-specific TypeScript config
├── tsup.config.ts # Build configuration
├── vitest.config.ts # Test configuration
├── eslint.config.js # Linting rules
└── prettier.config.json # Code formatting
Technical Decisions
Why These Tools?
tsup over tsc: Faster builds, easier dual-format output, better developer experience
vitest over jest: Native ES modules, faster execution, better TypeScript integration
ESLint + Prettier: Industry standard, extensive rule ecosystem, editor integration
tsx over ts-node: Faster TypeScript execution, better ES modules support
standard-version: Conventional commits, automated changelog, semantic versioning
Development Philosophy
- Fast Feedback Loops: Watch modes, hot reloading, instant type checking
- Comprehensive Validation: Multiple layers of checking before commits
- Modern Tooling: Embrace latest JavaScript/TypeScript capabilities
- Portability: Self-contained packages that work independently
- Developer Experience: Clear error messages, helpful tooling, good defaults