Skip to main content

TypeScript Tooling

This chapter is a tour of the tools that surround TypeScript: linting, formatting, build tools, editor setup and testing.


TypeScript Development Ecosystem

TypeScript's tooling is one of its biggest strengths. There are strong tools for every stage of development:

  • Code quality: ESLint with TypeScript support, type checking and linting, code style enforcement.
  • Development: VS Code integration, debugging tools, Hot Module Replacement (HMR).
  • Build & deploy: bundlers (Vite, Webpack, Parcel), module bundling, production optimisation.

Linting with ESLint

Installation. Install ESLint together with the official TypeScript parser and plugin, so ESLint understands TypeScript syntax and rules: npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin.

Configuration. In .eslintrc.json, turn on the recommended TypeScript rules, point ESLint at your tsconfig for type-aware linting, and adjust a few common rules:

  • "root": true
  • "parser": "@typescript-eslint/parser"
  • "plugins": ["@typescript-eslint"]
  • "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended-requiring-type-checking"]
  • "parserOptions": { "project": "./tsconfig.json", "ecmaVersion": 2020, "sourceType": "module" }
  • "rules": { "@typescript-eslint/explicit-function-return-type": "warn", "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }] }

NPM scripts. Add scripts to package.json that run the linter and a type-only check. Use lint:fix to auto-fix simple problems.

  • "lint": "eslint . --ext .ts,.tsx"
  • "lint:fix": "eslint . --ext .ts,.tsx --fix"
  • "type-check": "tsc --noEmit"

Code Formatting with Prettier

Prettier keeps the code style the same across your whole team.

Combine it with ESLint so formatting problems do not show up as lint errors.

Installation. Install Prettier plus the ESLint packages that turn off conflicting rules and report formatting issues through ESLint: npm install --save-dev prettier eslint-config-prettier eslint-plugin-prettier.

Configuration. Put your preferences in .prettierrc, for example:

  • "semi": true
  • "singleQuote": true
  • "tabWidth": 2
  • "printWidth": 100
  • "trailingComma": "es5"
  • "bracketSpacing": true
  • "arrowParens": "avoid"

And list generated folders in .prettierignore, one per line: node_modules, build, dist, .next, .vscode.

Integrate with ESLint. Extend plugin:prettier/recommended so formatting problems are reported as ESLint issues. In .eslintrc.json add it to the "extends" array, after all other configs: "extends": ["plugin:prettier/recommended"]. It must be the last entry in the array.


Modern Build Tools

Vite is the recommended choice for a fast dev server and modern builds.

Webpack is still common and very configurable.

Vite (recommended). Create a new React + TypeScript project with Vite. It starts a dev server with HMR for quick feedback:

  • npm create vite@latest my-app -- --template react-ts — create the project
  • cd my-app — go into the project folder
  • npm install — install dependencies
  • npm run dev — start the development server

Webpack configuration. If you pick Webpack, this minimal setup compiles TypeScript, handles CSS and serves the app with webpack-dev-server.

TypeScript configuration. A strict tsconfig.json for modern browsers. The optional baseUrl and paths allow absolute imports such as @/components/Button. Its compilerOptions:

  • "target": "es2020"
  • "module": "esnext"
  • "lib": ["dom", "dom.iterable", "esnext"]
  • "allowJs": true
  • "skipLibCheck": true
  • "esModuleInterop": true
  • "allowSyntheticDefaultImports": true
  • "strict": true
  • "forceConsistentCasingInFileNames": true
  • "moduleResolution": "node"
  • "resolveJsonModule": true
  • "isolatedModules": true
  • "noEmit": true
  • "jsx": "react-jsx"
  • "baseUrl": "."
  • "paths": { "@/*": ["src/*"] }

with "include": ["src"] and "exclude": ["node_modules"].

Minimal Webpack setup

ts-loader handles .ts/.tsx files, CSS goes through the style and css loaders, and the dev server runs on port 3000 with hot reloading.

script.ts
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.tsx',
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
  devServer: {
    static: path.join(__dirname, 'dist'),
    compress: true,
    port: 3000,
    hot: true,
  },
};

Development Environment Setup

Set up your editor so it shows lint errors, formats on save and debugs easily.

VS Code extensions. Recommended extensions:

  • TypeScript + Webpack Problem Matchers — better error reporting
  • ESLint — runs ESLint inside VS Code
  • Prettier - Code formatter — consistent formatting
  • Path IntelliSense — autocompletes file names
  • Error Lens — shows errors inline

VS Code settings. In .vscode/settings.json, make Prettier the default formatter, fix ESLint issues on save and prefer non-relative import paths:

  • "editor.defaultFormatter": "esbenp.prettier-vscode"
  • "editor.formatOnSave": true
  • "editor.codeActionsOnSave": { "source.fixAll.eslint": true, "source.organizeImports": true }
  • "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"]
  • "typescript.tsdk": "node_modules/typescript/lib"
  • "typescript.preferences.importModuleSpecifier": "non-relative"

Debugging configuration. In .vscode/launch.json ("version": "0.2.0"), add two entries to "configurations":

  • A Chrome launch: "type": "chrome", "request": "launch", "name": "Launch Chrome against localhost", "url": "http://localhost:3000", "webRoot": "${workspaceFolder}", "sourceMaps": true, and "sourceMapPathOverrides" mapping webpack:///./~/* to ${workspaceFolder}/node_modules/* and webpack:///./* to ${workspaceFolder}/src/*.
  • A Node test debugger: "type": "node", "request": "launch", "name": "Debug Tests", "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/jest", "args": ["--runInBand", "--watchAll=false"], "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", "sourceMaps": true.

Testing Setup

Jest with Testing Library is a popular way to test React + TypeScript apps.

Jest + Testing Library. Install Jest, its TypeScript support and the React Testing Library helpers for DOM assertions and user interaction: npm install --save-dev jest @types/jest ts-jest @testing-library/react @testing-library/jest-dom @testing-library/user-event.

Jest configuration. Configure Jest to use ts-jest, map CSS modules, and resolve alias paths such as @/.

Example test. A simple component test that checks rendering and click behaviour with Testing Library.

Jest configuration

The config uses the ts-jest preset with a jsdom environment and maps the @/ alias and style imports.

script.ts
// jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['@testing-library/jest-dom'],
  moduleNameMapper: {
    '^@/(.*)$': '/src/$1',
    '\\\.(css|less|scss|sass)$': 'identity-obj-proxy',
  },
  transform: {
    '^.+\\\.tsx?$': 'ts-jest',
  },
  testMatch: ['**/__tests__/**/*.test.(ts|tsx)'],
};

Component test with Testing Library

Two tests: one checks the button text is rendered, the other checks that the click handler runs once.

script.ts
// src/__tests__/Button.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import Button from '../components/Button';

describe('Button', () => {
  it('renders button with correct text', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument();
  });

  it('calls onClick when clicked', () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click me</Button>);

    fireEvent.click(screen.getByRole('button'));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });
});

Best Practices

Development workflow:

  • Use npm run dev for development with hot reloading
  • Run npm run type-check to verify TypeScript types
  • Use npm run lint to check for linting errors
  • Run npm run build to create a production build

Performance optimisation:

  • Use code splitting with dynamic imports
  • Enable tree-shaking in production builds
  • Use React.memo and useMemo for expensive computations
  • Lazy load non-critical components

Common Pitfalls

  • TypeScript configuration: make sure strict mode is enabled
  • ESLint + Prettier conflicts: use eslint-config-prettier to disable the conflicting rules
  • Slow builds: consider Vite or esbuild for faster development
  • Missing type definitions: install @types packages for all dependencies
  • Debugging issues: make sure source maps are configured correctly

Recommended Tools

  • Bundlers: Vite, Webpack, Parcel
  • Testing: Jest, React Testing Library, Cypress
  • Linting/Formatting: ESLint, Prettier, Stylelint
  • Documentation: TypeDoc, Storybook
  • Performance: Web Vitals, Lighthouse