Polyglot
Languages TypeScript modules
TypeScript § modules

Tooling and packaging

The TypeScript ecosystem revolves around npm (the JavaScript package registry), package.json (the project manifest), and tsconfig.json (the TypeScript compiler configuration). The conventional contemporary build pipeline uses tsc for type-checking and a bundler (Vite, esbuild, Rollup, webpack) or runtime (Node.js, Deno, Bun) for emission and execution. Declaration files (.d.ts) describe the types of JavaScript code; the DefinitelyTyped repository (the @types/... packages) provides types for thousands of npm packages. The combination — npm as registry, package.json as manifest, tsconfig.json as compiler config, declaration files as type interface — is the substance of TypeScript’s tooling surface.

package.json

The npm project manifest:

{
    "name": "my-project",
    "version": "1.0.0",
    "description": "An example project",
    "type": "module",
    "main": "./dist/index.js",
    "types": "./dist/index.d.ts",
    "exports": {
        ".": {
            "types": "./dist/index.d.ts",
            "import": "./dist/index.js"
        }
    },
    "scripts": {
        "build": "tsc",
        "test": "vitest",
        "lint": "eslint src/"
    },
    "dependencies": {
        "react": "^19.0.0",
        "@tanstack/react-query": "^5.50.0"
    },
    "devDependencies": {
        "typescript": "^5.7.0",
        "@types/node": "^22.0.0",
        "vite": "^6.0.0",
        "vitest": "^2.0.0",
        "eslint": "^9.0.0"
    },
    "peerDependencies": {
        "react": "^19.0.0"
    },
    "engines": {
        "node": ">=20.0.0"
    }
}

The principal fields:

  • type: "module" — declares ES modules; without it, files are CommonJS.
  • main — the entry point.
  • types — the type declaration file.
  • exports — modern entry-point map (admits subpath exports).
  • scripts — npm scripts; run via npm run <name>.
  • dependencies — runtime dependencies.
  • devDependencies — build, test, lint tools.
  • peerDependencies — depend on the host’s version.

tsconfig.json

The TypeScript compiler configuration:

{
    "compilerOptions": {
        "target": "ES2022",
        "module": "ESNext",
        "moduleResolution": "Bundler",
        "lib": ["ES2022", "DOM", "DOM.Iterable"],
        "jsx": "react-jsx",

        "strict": true,
        "noUncheckedIndexedAccess": true,
        "noImplicitOverride": true,

        "esModuleInterop": true,
        "isolatedModules": true,
        "verbatimModuleSyntax": true,

        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true,

        "outDir": "./dist",
        "rootDir": "./src",
        "declaration": true,
        "sourceMap": true,

        "paths": {
            "@/*": ["./src/*"]
        }
    },
    "include": ["src/**/*"],
    "exclude": ["node_modules", "dist"]
}

The principal compiler options:

Target and module

  • target — the JavaScript version to emit (ES2022, ES2024, etc.).
  • module — the module system (ESNext, CommonJS, Node16, etc.).
  • moduleResolution — how imports are resolved (Bundler, NodeNext, Node10).
  • lib — built-in type definitions (DOM for browser, WebWorker for workers, etc.).
  • jsx — JSX compilation (react-jsx, preserve, react-native).

Strictness

  • strict: true — enables the full strict suite:

    • strictNullChecksnull/undefined not assignable to other types.
    • noImplicitAny — implicit any is an error.
    • strictFunctionTypes — strict function-parameter variance.
    • strictBindCallApplybind/call/apply are strictly typed.
    • strictPropertyInitialization — class properties must be initialised.
    • noImplicitThis — implicit any for this is an error.
    • alwaysStrict — emits "use strict".
    • useUnknownInCatchVariables — catch variables are unknown.
  • noUncheckedIndexedAccess — array/object indexing returns T | undefined.

  • noImplicitOverrideoverride keyword required.

  • noImplicitReturns — all paths must return.

  • noFallthroughCasesInSwitchcase must end with break/return/throw.

Module behaviour

  • esModuleInterop — admits cleaner CommonJS imports.
  • isolatedModules — verifies each file is independently transpilable (required for esbuild, swc, etc.).
  • verbatimModuleSyntax — preserves type-only imports verbatim (replaces importsNotUsedAsValues).
  • allowSyntheticDefaultImports — admits default imports from CommonJS.
  • resolveJsonModule — admits import data from "./data.json".

Output

  • outDir — output directory.
  • rootDir — source root.
  • declaration — emit .d.ts files.
  • declarationMap — emit declaration source maps.
  • sourceMap — emit .js.map files.
  • removeComments — strip comments from output.

Project structure

  • include — files to compile.
  • exclude — files to skip.
  • files — explicit file list (rare).
  • references — project references for monorepo builds.
  • paths — path aliases (with baseUrl).

Common tsconfig.json recipes

Modern application with bundler

{
    "compilerOptions": {
        "target": "ES2022",
        "module": "ESNext",
        "moduleResolution": "Bundler",
        "lib": ["ES2022", "DOM"],
        "strict": true,
        "noUncheckedIndexedAccess": true,
        "skipLibCheck": true,
        "esModuleInterop": true,
        "isolatedModules": true,
        "noEmit": true,                          // bundler emits, not tsc
        "jsx": "react-jsx",
        "verbatimModuleSyntax": true
    }
}

The bundler (Vite, esbuild) handles transpilation; tsc runs only for type-checking.

Library with declaration emit

{
    "compilerOptions": {
        "target": "ES2020",
        "module": "ESNext",
        "moduleResolution": "Bundler",
        "lib": ["ES2020", "DOM"],
        "strict": true,
        "skipLibCheck": true,
        "declaration": true,
        "declarationMap": true,
        "sourceMap": true,
        "outDir": "./dist",
        "rootDir": "./src"
    },
    "include": ["src/**/*"]
}

Node.js application

{
    "compilerOptions": {
        "target": "ES2022",
        "module": "NodeNext",
        "moduleResolution": "NodeNext",
        "lib": ["ES2022"],
        "types": ["node"],
        "strict": true,
        "skipLibCheck": true,
        "outDir": "./dist",
        "rootDir": "./src"
    }
}
{
    "compilerOptions": {
        "strict": true,
        "noUncheckedIndexedAccess": true,
        "noImplicitOverride": true,
        "noImplicitReturns": true,
        "noFallthroughCasesInSwitch": true,
        "exactOptionalPropertyTypes": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true
    }
}

The exactOptionalPropertyTypes distinguishes { x?: number } (may be missing) from { x?: number | undefined } (may be missing OR undefined).

Declaration files

.d.ts files contain type-only declarations:

// my-lib.d.ts:
declare module "my-lib" {
    export function process(data: string): string;
    export interface Config {
        verbose: boolean;
    }
}

The conventional uses:

  • Typing JavaScript libraries@types/... packages from DefinitelyTyped.
  • Ambient declarations — globals provided by the runtime or build.
  • Module declarations — typing modules without TypeScript source.

Generating declarations

The tsc --declaration flag (or "declaration": true in tsconfig) emits .d.ts alongside .js:

tsc --declaration
# Produces: dist/index.js, dist/index.d.ts

The package.json’s types field points to the entry declaration file:

{
    "main": "./dist/index.js",
    "types": "./dist/index.d.ts"
}

Triple-slash directives

Legacy /// reference comments admit ambient typing:

/// <reference types="node" />
/// <reference path="./globals.d.ts" />

The conventional contemporary form uses the types field in tsconfig.json instead.

DefinitelyTyped and @types/...

The @types/... packages provide type declarations for thousands of npm packages:

npm install --save-dev @types/node
npm install --save-dev @types/react
npm install --save-dev @types/react-dom
npm install --save-dev @types/lodash

The packages contain only .d.ts files; the runtime implementation comes from the corresponding npm package.

For libraries that ship their own types (most modern TypeScript libraries), no @types/... is needed — the package’s own package.json types field points to its declaration file.

The node_modules layout

node_modules/
├── react/
│   ├── package.json
│   ├── index.js
│   └── ...
├── @types/
│   └── node/
│       ├── package.json
│       └── index.d.ts
└── @tanstack/
    └── react-query/
        ├── package.json
        └── ...

The @types/... namespace contains type packages; other scoped namespaces (@tanstack/, @anthropic-ai/) contain regular packages.

Bundlers

For browser-targeted code, a bundler combines modules into deployable artifacts:

  • Vite — modern, ESM-first, fast HMR.
  • esbuild — substantial speed; conventional as a transformer in larger setups.
  • Rollup — library-oriented; small output.
  • webpack — substantial ecosystem; complex configuration.
  • Parcel — zero-config; less mainstream.

The conventional contemporary choice for new projects is Vite or esbuild; webpack remains in legacy projects.

Runtimes

For server-side code, the principal runtimes:

  • Node.js — the conventional choice; native CommonJS and ES module support.
  • Deno — TypeScript-first, secure by default; native types from imports.
  • Bun — substantially fast; bundler and runtime combined.

Deno and Bun execute TypeScript directly without an explicit transpilation step; Node.js requires tsx, ts-node, or pre-compilation.

Common patterns

Project layout

my-project/
├── package.json
├── tsconfig.json
├── README.md
├── .gitignore
├── src/
│   ├── index.ts
│   ├── api/
│   ├── components/
│   └── utils/
├── tests/
│   └── ...
└── dist/                                         (build output, gitignored)

Monorepo with workspaces

{
    "name": "my-monorepo",
    "private": true,
    "workspaces": [
        "packages/*",
        "apps/*"
    ]
}
my-monorepo/
├── package.json
├── packages/
│   ├── core/
│   │   ├── package.json
│   │   └── src/
│   └── ui/
│       ├── package.json
│       └── src/
└── apps/
    └── web/
        ├── package.json
        └── src/

The conventional tooling for substantial monorepos: pnpm workspaces, npm workspaces, yarn workspaces, plus Turborepo or Nx for orchestration.

Project references

For substantial monorepos, project references admit incremental builds:

// tsconfig.json (root):
{
    "files": [],
    "references": [
        { "path": "./packages/core" },
        { "path": "./packages/ui" }
    ]
}
// packages/core/tsconfig.json:
{
    "extends": "../../tsconfig.base.json",
    "compilerOptions": {
        "composite": true,
        "outDir": "./dist",
        "rootDir": "./src"
    }
}

Build with tsc -b (build mode):

tsc -b                                            # builds all referenced projects
tsc -b --watch                                    # watch mode

Path aliases

{
    "compilerOptions": {
        "baseUrl": "./src",
        "paths": {
            "@/*": ["*"],
            "@components/*": ["components/*"],
            "@utils/*": ["utils/*"]
        }
    }
}
import { Button } from "@components/Button";
import { format } from "@utils/strings";

Bundler config must align — Vite, webpack, esbuild each have their own alias config.

Linting and formatting

The conventional contemporary tools:

  • ESLint (with typescript-eslint) — linting.
  • Prettier — formatting.
  • Biome — emerging combined alternative (Rust-based, faster).
// package.json:
{
    "scripts": {
        "lint": "eslint src/",
        "format": "prettier --write src/",
        "typecheck": "tsc --noEmit"
    }
}

Pre-commit hooks

{
    "scripts": {
        "prepare": "husky"
    },
    "lint-staged": {
        "*.{ts,tsx}": ["eslint --fix", "prettier --write"]
    }
}

The husky and lint-staged packages admit running checks on changed files before commit.

Type-only imports

import type { User } from "./types.js";
import { type Config, parse } from "./parser.js";

The type modifier admits clean tree-shaking; under verbatimModuleSyntax: true, the modifier is required for type-only imports.

Scripts via npm

npm install                                       # install dependencies
npm install <pkg>                                 # add dependency
npm install --save-dev <pkg>                      # add dev dependency
npm uninstall <pkg>
npm update                                        # update dependencies
npm run <script>                                  # run package.json script
npx <command>                                     # run a one-off command
npm publish                                       # publish to npm

The conventional alternatives: pnpm (faster, content-addressed), yarn (improved workflows), bun install (fastest as of 2026).

A note on the conventional discipline

The contemporary TypeScript tooling advice:

  • Use strict: true in tsconfig.json.
  • Add noUncheckedIndexedAccess: true for substantial safety.
  • Use ES modules ("type": "module" in package.json).
  • Use a bundler for browser code (Vite, esbuild).
  • Use tsc --noEmit for type checking; the bundler handles emission.
  • Use @types/... for typing pre-existing JavaScript libraries.
  • Use pnpm or bun install for substantially faster installs.
  • Use ESLint and Prettier (or Biome).
  • Use project references for monorepo builds.
  • Enable verbatimModuleSyntax for clean import handling.
  • Generate .d.ts for libraries; outDir/rootDir for clean output.

The combination — package.json for the project manifest, tsconfig.json for the compiler, declaration files for type interfaces, the npm registry, the @types/... ecosystem, bundlers for browser code, runtimes for server code — is the substance of TypeScript’s tooling surface. The discipline produces maintainable, type-safe, well-bundled projects with substantial automation.