setup-typescript-project
skillComprehensive configuration templates and guidance for setting up TypeScript projects with modern tooling, testing frameworks, and code quality enforcement.
apm::install
apm install @nickromney/setup-typescript-projectapm::skill.md
---
name: setup-typescript-project
description: >
Comprehensive configuration templates and guidance for setting up TypeScript projects
with modern tooling, testing frameworks, and code quality enforcement.
---
# TypeScript Project Setup
Comprehensive configuration templates and guidance for setting up TypeScript projects with modern tooling, testing frameworks, and code quality enforcement.
## Purpose
This skill provides standardized configurations and best practices for TypeScript projects, including:
- TypeScript configuration with ES modules or CommonJS
- Choice of **Biome** (recommended) or **ESLint+Prettier** for linting/formatting
- Vitest for unit and integration testing (optional)
- Playwright for end-to-end testing (optional)
- Husky for pre-commit hooks
- Makefile for common operations
- GitHub Actions for CI/CD
- Package.json scripts for common workflows
## When to Use
Use this skill when:
- Starting a new TypeScript project
- Adding TypeScript to an existing JavaScript project
- Setting up testing infrastructure (unit + e2e)
- Implementing code quality gates and pre-commit hooks
- Standardizing configurations across multiple TypeScript projects
- Need guidance on recommended dependencies and versions
## Initial Setup Questions
When using this skill, Claude will ask:
1. **Module System:**
- **ES Modules** (recommended for new projects) - Modern, bundler-friendly
- **CommonJS** - Traditional Node.js, better for legacy compatibility
1. **Linting/Formatting Tool:**
- **Biome** (recommended) - Modern, fast, all-in-one tool
- **ESLint + Prettier** - Traditional, wider ecosystem support
- **None** - Rely on TypeScript strict mode only (minimal approach)
1. **Testing Strategy:**
- **Vitest only** - Unit and integration testing
- **Playwright only** - E2E and component testing
- **Vitest + Playwright** - Comprehensive testing (unit + E2E)
- **None** - No testing setup
1. **Pre-commit Hooks:**
- **Yes** - Set up Husky with quality checks
- **No** - Manual quality checks only
1. **Project Type:**
- **Vite Frontend** - Browser app with bundler mode
- **Node.js Backend** - Server-side application
- **CLI Tool** - Command-line tool
- **Library** - Reusable package with type definitions
- **General** - Standard TypeScript project
## Project Structure
Recommended directory structure:
```
project-root/
├── src/ # TypeScript source files
│ ├── **/*.ts
│ └── **/*.tsx
├── tests/ # Test files
│ ├── **/*.test.ts # Vitest unit tests (if using Vitest)
│ └── **/*.spec.ts # Test files (Vitest or Playwright)
├── dist/ # Compiled output (if not using bundler)
├── .husky/ # Git hooks (if enabled)
│ └── pre-commit
├── .github/ # GitHub Actions workflows
│ └── workflows/
│ └── checks.yml
├── tsconfig.json # TypeScript config
├── biome.json # Biome config (if using Biome)
├── eslint.config.js # ESLint config (if using ESLint)
├── .prettierrc.json # Prettier config (if using Prettier)
├── vitest.config.js # Vitest config (if using Vitest)
├── playwright.config.ts # Playwright config (if using Playwright)
├── Makefile # Common operations
└── package.json
```
## Configuration Templates
### 1. TypeScript Configuration (tsconfig.json)
#### For Vite/Bundler Projects (Frontend)
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src"]
}
```
**Key settings for Vite:**
- `"moduleResolution": "bundler"` - Modern bundler-aware resolution
- `"noEmit": true` - Vite handles compilation, not tsc
- `"allowImportingTsExtensions": true` - Import `.ts` files directly
- `"isolatedModules": true` - Required for bundlers
#### For Node.js/Backend Projects (ES Modules)
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"lib": ["ES2022"],
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "dist",
"rootDir": "src",
"declaration": true,
"resolveJsonModule": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}
```
#### For Node.js/Backend Projects (CommonJS)
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
**When to use CommonJS:**
- Legacy Node.js projects
- Enterprise environments with older Node versions
- Projects that depend on CommonJS-only packages
- When ES modules cause compatibility issues
### 2. Linting and Formatting
## Option A: Biome (Recommended)
**Configuration (biome.json):**
```json
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"organizeImports": {
"enabled": true
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 120,
"lineEnding": "lf"
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"suspicious": {
"noExplicitAny": "warn"
},
"style": {
"useConst": "error",
"noNonNullAssertion": "warn"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "asNeeded",
"trailingCommas": "es5"
}
},
"files": {
"ignore": [
"dist",
"node_modules",
"test-results",
"playwright-report",
"*.config.js",
"*.config.ts"
]
}
}
```
**Package.json scripts:**
```json
{
"scripts": {
"lint": "biome check .",
"lint:fix": "biome check --write .",
"format": "biome format --write .",
"check": "biome check . && tsc --noEmit"
}
}
```
**Dependencies:**
```bash
npm install --save-dev @biomejs/biome
```
**Biome advantages:**
- 10-100x faster than ESLint (Rust-based)
- Single tool for linting + formatting + import sorting
- Zero config needed (sensible defaults)
- Simple configuration file
- Modern, actively developed
**Code style:**
- Single quotes
- Semicolons only when needed (ASI-friendly)
- Trailing commas in ES5 contexts (arrays, objects)
- 120 character line width
- 2-space indentation
## Option B: ESLint + Prettier (Traditional)
**ESLint Configuration (eslint.config.js):**
```javascript
import eslint from '@eslint/js'
import tseslint from '@typescript-eslint/eslint-plugin'
import tsParser from '@typescript-eslint/parser'
export default [
eslint.configs.recommended,
{
files: ['**/*.ts', '**/*.js'],
languageOptions: {
parser: tsParser,
ecmaVersion: 2020,
sourceType: 'module',
globals: {
// Browser globals
window: 'readonly',
document: 'readonly',
console: 'readonly',
// Node globals
process: 'readonly',
module: 'readonly',
require: 'readonly',
__dirname: 'readonly'
}
},
plugins: {
'@typescript-eslint': tseslint
},
rules: {
...tseslint.configs.recommended.rules,
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_'
}],
'@typescript-eslint/no-explicit-any': 'warn',
'no-console': 'warn',
'semi': ['error', 'never'],
'quotes': ['error', 'single', { avoidEscape: true }],
'comma-dangle': ['error', 'never']
}
},
{
files: ['**/*.test.js', '**/*.test.ts'],
rules: {
'no-console': 'off'
}
}
]
```
**Prettier Configuration (.prettierrc.json):**
```json
{
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"tabWidth": 2,
"arrowParens": "avoid"
}
```
**Package.json scripts:**
```json
{
"scripts": {
"lint": "eslint src/**/*.{js,ts}",
"lint:fix": "eslint src/**/*.{js,ts} --fix",
"lint:errors-only": "eslint src/**/*.{js,ts} --quiet",
"format": "prettier --write src/**/*.{js,ts}",
"format:check": "prettier --check src/**/*.{js,ts}",
"check": "npm run typecheck && npm run lint && npm run format:check"
}
}
```
**Dependencies:**
```bash
npm install --save-dev \
eslint @eslint/js \
@typescript-eslint/eslint-plugin \
@typescript-eslint/parser \
prettier
```
**Code style:**
- Single quotes
- No semicolons
- No trailing commas
- 100 character line width
- 2-space indentation
## Option C: No Linting (Minimal)
**When to use:**
- Small, simple projects
- Relying on TypeScript strict mode for type safety
- Team has strong IDE setup and conventions
- CI handles quality checks
**Package.json scripts:**
```json
{
"scripts": {
"check": "tsc --noEmit"
}
}
```
**Note:** TypeScript's strict mode catches many issues that linters would, but you lose style consistency enforcement.
### 3. Testing Configurations
## Option A: Vitest Only
**When to use:** Unit testing, integration testing, API testing (no browser needed).
**Configuration (vitest.config.js):**
```javascript
import {defineConfig} from 'vitest/config'
export default defineConfig({
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./tests/setup.js'],
include: ['./tests/**/*.test.js', './tests/**/*.test.ts', './tests/**/*.spec.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html']
}
}
})
```
**Note:** Vitest works without a config file - it will auto-discover tests. Config is optional for customization.
**Package.json scripts:**
```json
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"test:ui": "vitest --ui"
}
}
```
**Dependencies:**
```bash
npm install --save-dev vitest @vitest/coverage-v8
```
## Option B: Playwright Only
**When to use:** E2E testing, browser testing, visual testing.
**Configuration (playwright.config.ts):**
```typescript
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: process.env.BASE_URL ? undefined : {
command: 'npm run preview',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
})
```
**Package.json scripts:**
```json
{
"scripts": {
"test": "playwright test",
"test:headed": "playwright test --headed",
"test:ui": "playwright test --ui"
}
}
```
**Dependencies:**
```bash
npm install --save-dev @playwright/test
```
## Option C: Vitest + Playwright
**When to use:** Comprehensive testing strategy (fast unit tests + thorough E2E tests).
**Testing strategy:**
- `.test.ts` files → Vitest (unit tests)
- `.spec.ts` files → Playwright (E2E tests)
**Package.json scripts:**
```json
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"test:ui": "vitest --ui",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:report": "playwright show-report"
}
}
```
### 4. Pre-commit Hooks with Husky
**Setup Husky:**
```bash
npm install --save-dev husky
npx husky init
```
**Pre-commit hook (.husky/pre-commit):**
```bash
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run quality:commit
```
**Package.json scripts:**
For Biome:
```json
{
"scripts": {
"quality": "npm run check && npm run test",
"quality:commit": "biome check . && tsc --noEmit && npm run test",
"prepare": "husky"
}
}
```
For ESLint+Prettier:
```json
{
"scripts": {
"quality": "npm run typecheck && npm run lint && npm run format:check && npm run test",
"quality:commit": "npm run typecheck && npm run lint:errors-only && npm run test",
"prepare": "husky"
}
}
```
For minimal (no linting):
```json
{
"scripts": {
"quality": "npm run typecheck && npm run test",
"quality:commit": "tsc --noEmit && npm run test",
"prepare": "husky"
}
}
```
**What runs in pre-commit:**
1. Type checking (fast)
1. Linting errors only (warnings ignored)
1. Tests (unit tests only, not E2E)
**What NOT to run in pre-commit:**
- Formatting (too slow, should be editor responsibility)
- Full linting with warnings (too noisy)
- E2E tests (too slow)
### 5. Makefile
**Common Makefile targets:**
```makefile
.PHONY: help install build test clean lint format check
help:
@echo "Available targets:"
@echo " install - Install dependencies"
@echo " build - Build the project"
@echo " test - Run tests"
@echo " clean - Clean build artifacts"
@echo " lint - Run linting"
@echo " format - Format code"
@echo " check - Run all quality checks"
install:
npm ci
build:
npm run build
test:
npm run test
clean:
rm -rf dist node_modules
lint:
npm run lint
format:
npm run format
check:
npm run quality
```
### 6. GitHub Actions
**Basic CI workflow (.github/workflows/checks.yml):**
```yaml
name: Checks
on:
push:
branches-ignore:
- main
pull_request:
branches:
- main
permissions: read-all
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Type check
run: npm run typecheck
- name: Lint
run: npm run lint
if: hashFiles('biome.json', 'eslint.config.js') != ''
- name: Test
run: npm run test
if: hashFiles('vitest.config.js', 'playwright.config.ts') != ''
```
### 7. Complete Package.json Examples
**For ES Modules + Biome + Vitest:**
```json
{
"name": "my-typescript-project",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "your-dev-command",
"build": "tsc",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"lint": "biome check .",
"lint:fix": "biome check --write .",
"format": "biome format --write .",
"check": "biome check . && tsc --noEmit",
"quality": "npm run check && npm run test",
"quality:commit": "biome check . && tsc --noEmit && npm run test",
"prepare": "husky"
},
"devDependencies": {
"@biomejs/biome": "^1.9.0",
"@types/node": "^22.0.0",
"@vitest/coverage-v8": "^2.1.0",
"husky": "^9.0.0",
"typescript": "^5.6.0",
"vitest": "^2.1.0"
}
}
```
**For CommonJS + No Linting + Vitest:**
```json
{
"name": "my-typescript-project",
"version": "1.0.0",
"scripts": {
"build": "tsc",
"test": "vitest",
"coverage": "vitest run --coverage",
"typecheck": "tsc --noEmit",
"quality": "npm run typecheck && npm run test",
"quality:commit": "tsc --noEmit && npm run test",
"prepare": "husky"
},
"devDependencies": {
"@types/node": "^22.0.0",
"@vitest/coverage-v8": "^2.1.0",
"husky": "^9.0.0",
"typescript": "^5.6.0",
"vitest": "^2.1.0"
}
}
```
## Quick Setup Guide
### 1. Initialize Project
```bash
npm init -y
npm install --save-dev typescript
```
### 2. Choose Module System
**For ES Modules (recommended):**
```bash
npm pkg set type=module
```
**For CommonJS:**
No action needed (default)
### 3. Initialize TypeScript
```bash
npx tsc --init
```
Then replace with the appropriate config from this skill.
### 4. Choose Linting/Formatting
**Option A: Biome (recommended)**
```bash
npm install --save-dev @biomejs/biome
npx @biomejs/biome init
```
**Option B: ESLint + Prettier**
```bash
npm install --save-dev \
eslint @eslint/js \
@typescript-eslint/eslint-plugin \
@typescript-eslint/parser \
prettier
```
**Option C: None**
Skip this step
### 5. Choose Testing
**Option A: Vitest**
```bash
npm install --save-dev vitest @vitest/coverage-v8
```
**Option B: Playwright**
```bash
npm init playwright@latest
```
**Option C: Both**
```bash
npm install --save-dev vitest @vitest/coverage-v8 @playwright/test
npm init playwright@latest
```
### 6. Set up Pre-commit Hooks
```bash
npm install --save-dev husky
npx husky init
echo "npm run quality:commit" > .husky/pre-commit
chmod +x .husky/pre-commit
```
### 7. Create Makefile
Create a `Makefile` with the targets shown above.
### 8. Set up GitHub Actions
Create `.github/workflows/checks.yml` with the workflow shown above.
## Best Practices
### 1. Module System Choice
**ES Modules (recommended for new projects):**
- Modern, future-proof
- Better tree-shaking in bundlers
- Native browser support
- Required for Vite and modern tools
**CommonJS (for legacy compatibility):**
- Better compatibility with older Node.js
- Some npm packages only support CommonJS
- Traditional Node.js pattern
### 2. Type Safety
- Enable all strict flags in tsconfig.json
- Use `noUnusedLocals` and `noUnusedParameters`
- Avoid `any` types (linters warn about them)
- Prefix unused variables with `_` if needed
### 3. Code Style
- **Biome default:** Single quotes, semicolons as needed, trailing commas in ES5 contexts
- **ESLint+Prettier default:** Single quotes, no semicolons, no trailing commas
- Consistent 2-space indentation
- 120 char line width (modern)
### 4. Testing Strategy
- **Vitest only:** Fast, good for libraries and backend
- **Playwright only:** Essential for frontend apps
- **Both:** Comprehensive coverage for full-stack apps
- Keep E2E tests out of pre-commit hooks (too slow)
### 5. Pre-commit Hooks
- Run fast checks only (typecheck, lint errors, unit tests)
- Skip slow checks (formatting, warnings, E2E tests)
- Let CI handle comprehensive quality checks
- Developers should format code via editor
### 6. CI/CD
- Run comprehensive checks in CI (what you skip in pre-commit)
- Use caching for faster builds
- Separate workflows for PRs vs main branch
- Consider coverage thresholds
### 7. Makefile Usage
- Provides consistent interface across projects
- Easier for new contributors (single command reference)
- Works regardless of language/tooling
- Good for complex multi-step operations
## Decision Matrix
| Criteria | Biome | ESLint+Prettier | None |
|----------|-------|-----------------|------|
| Speed | Much faster | Slower | Fastest |
| Simplicity | Single tool | Two tools | No tools |
| Ecosystem | Growing | Mature, extensive | N/A |
| Style enforcement | Excellent | Excellent | Manual |
| Type safety | Via rules | Via rules | TypeScript only |
| Criteria | ES Modules | CommonJS |
|----------|------------|----------|
| Modern | Yes | No |
| Bundler support | Excellent | Good |
| Node.js compat | Node 12+ | All versions |
| Future-proof | Yes | Legacy |
| Criteria | Vitest | Playwright | Both |
|----------|--------|------------|------|
| Unit testing | Excellent | N/A | Excellent |
| E2E testing | N/A | Excellent | Excellent |
| Speed | Fast | Slower | Mixed |
| Setup | Simple | Moderate | Complex |
## Common Issues and Solutions
### Issue: "Cannot find module" errors with ES modules
**Solution:** Ensure `"type": "module"` is in package.json and use `.js` extension for config files.
### Issue: Biome not recognizing TypeScript
**Solution:** Biome works out of the box with TypeScript. Ensure `biome.json` exists and `@biomejs/biome` is installed.
### Issue: ESLint not recognizing TypeScript
**Solution:** Verify `@typescript-eslint/parser` is specified in `languageOptions.parser`.
### Issue: Pre-commit hook is too slow
**Solution:** Use `quality:commit` (fast) instead of `quality` (comprehensive). Remove E2E tests from pre-commit.
### Issue: Vitest not finding tests
**Solution:** Vitest auto-discovers tests. Ensure test files match patterns: `**/*.{test,spec}.{js,ts}`.
### Issue: Husky hooks not running
**Solution:** Ensure `.husky/pre-commit` is executable: `chmod +x .husky/pre-commit`.
### Issue: Make commands not working
**Solution:** Ensure Makefile uses tabs (not spaces) for indentation.
## Project Type Recommendations
### Vite Frontend Projects
- ES Modules
- Biome (faster, modern)
- TypeScript with bundler mode
- Playwright for E2E testing
- Pre-commit hooks
- GitHub Actions CI
### Node.js Backend Projects
- ES Modules (or CommonJS for legacy)
- Biome or ESLint+Prettier
- TypeScript with node resolution
- Vitest for unit tests
- Pre-commit hooks
- GitHub Actions CI
### CLI Tools
- CommonJS (better compatibility)
- Minimal linting (or none)
- TypeScript with CommonJS module
- Vitest for unit tests
- Pre-commit hooks
- GitHub Actions CI
### Library Projects
- ES Modules with CommonJS output (dual package)
- ESLint+Prettier (wider compatibility)
- TypeScript with `declaration: true`
- Vitest for comprehensive unit testing
- Pre-commit hooks with strict checks
- GitHub Actions CI with publishing
### Monorepo Projects
- ES Modules
- Biome (single config, fast)
- TypeScript project references
- Both Vitest + Playwright
- Workspace-level pre-commit hooks
- GitHub Actions CI with matrix builds