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 vianpm 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 (DOMfor browser,WebWorkerfor workers, etc.).jsx— JSX compilation (react-jsx,preserve,react-native).
Strictness
-
strict: true— enables the full strict suite:strictNullChecks—null/undefinednot assignable to other types.noImplicitAny— implicitanyis an error.strictFunctionTypes— strict function-parameter variance.strictBindCallApply—bind/call/applyare strictly typed.strictPropertyInitialization— class properties must be initialised.noImplicitThis— implicitanyforthisis an error.alwaysStrict— emits"use strict".useUnknownInCatchVariables— catch variables areunknown.
-
noUncheckedIndexedAccess— array/object indexing returnsT | undefined. -
noImplicitOverride—overridekeyword required. -
noImplicitReturns— all paths must return. -
noFallthroughCasesInSwitch—casemust 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 (replacesimportsNotUsedAsValues).allowSyntheticDefaultImports— admits default imports from CommonJS.resolveJsonModule— admitsimport data from "./data.json".
Output
outDir— output directory.rootDir— source root.declaration— emit.d.tsfiles.declarationMap— emit declaration source maps.sourceMap— emit.js.mapfiles.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 (withbaseUrl).
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"
}
}
Strict settings (recommended)
{
"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: trueintsconfig.json. - Add
noUncheckedIndexedAccess: truefor substantial safety. - Use ES modules (
"type": "module"inpackage.json). - Use a bundler for browser code (Vite, esbuild).
- Use
tsc --noEmitfor type checking; the bundler handles emission. - Use
@types/...for typing pre-existing JavaScript libraries. - Use
pnpmorbun installfor substantially faster installs. - Use ESLint and Prettier (or Biome).
- Use project references for monorepo builds.
- Enable
verbatimModuleSyntaxfor clean import handling. - Generate
.d.tsfor libraries;outDir/rootDirfor 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.