outDir

Sets the directory emitted JavaScript is written to, mirroring the source tree.

Signature
"outDir": string  // a directory path, relative to the tsconfig.json
  • Added in TypeScript 1.0

Parameters

ParameterTypeDescription
$outDirstringDestination directory, resolved relative to the tsconfig.json. If unset, each output file is written beside the source file it came from.
$rootDiroptionalstringCompanion option. Names the directory the output tree is mirrored from. Unset, it is inferred as the longest common path of all input files, which is what makes the shape of outDir change when you add or remove a file.

Return value

Redirects emitted .js, .d.ts, .js.map and .d.ts.map files into the named directory, preserving the source directory structure beneath it.

Errors

Error TS6059 when an input file is not under rootDir. Error TS5055 when an emitted file would overwrite an input file — typically because outDir is also part of include.

Changelog

  • PHP 6.0rootDir now defaults to the directory containing the tsconfig.json rather than being inferred from the input files, so the output tree no longer changes shape when files are added or removed.
  • PHP 6.0outFile was removed; producing a single bundled file is a bundler's job.

outDir sends emitted files — .js plus any .d.ts and source maps — into a directory of their own instead of scattering them beside your sources. The directory structure of the inputs is preserved underneath it.

How the tree inside is shaped

The layout is outDir plus each file's path relative to rootDir. That second part is where the surprises come from. Historically rootDir was inferred as the longest common directory of all input files, so a project compiling only src/** emitted dist/index.js, but the moment a single file outside src joined the program — a root-level config.ts, a test — the common root moved up and everything shifted to dist/src/index.js. Every import path in your published package broke, from an edit that touched no import.

Setting rootDir explicitly pins the shape and turns the failure into a clear compile error, TS6059, naming the file that fell outside. TypeScript 6.0 made the default the config's own directory for the same reason.

What trips people up

If outDir falls inside include, the emitted .js becomes input on the next run. Add it to exclude.

Example

Example · typescript
// tsconfig.json
// { "compilerOptions": { "rootDir": "src", "outDir": "dist" },
//   "include": ["src"], "exclude": ["dist"] }
//
// src/index.ts       ->  dist/index.js
// src/lib/money.ts   ->  dist/lib/money.js

export function formatCents(cents: number): string {
  return `$${(cents / 100).toFixed(2)}`;
}

console.log(formatCents(1999));
// -> $19.99

When to use it

  • Keeping compiled output in dist/ so it can be gitignored and published separately.
  • Pinning a package's entry point at dist/index.js by setting rootDir to src.
  • Emitting declarations and source maps alongside JavaScript in one build folder.
  • Diagnosing an unexpected dist/src/ nesting level after adding a config file.

Frequently asked questions

Why does tsc output dist/src/index.js instead of dist/index.js?

Because rootDir was inferred as the common parent of all input files, and some file outside src joined the program. Set "rootDir": "src" explicitly to pin the output shape; TypeScript 6.0 also changed the default to the tsconfig.json's own directory.

What is the difference between outDir and rootDir?

outDir is where output goes, and rootDir is the directory the output tree is mirrored from. The path of each emitted file is outDir plus the source file's path relative to rootDir.

How do I stop compiled files appearing next to my source files?

Set outDir to a folder such as dist, and add that folder to exclude so its emitted .js files are not read back as inputs. Without outDir, each .js is written beside the .ts it came from.

More examples

How rootDir decides the nesting

Case B is the silent breakage: package.json still points at dist/index.js, which no longer exists, and nothing warned you. Case C converts the same situation into an immediate, named compile error — which is why pinning rootDir is worth the extra line.

Example · typescript
// Files in the program:
//   src/index.ts
//   src/lib/money.ts

// A) "outDir": "dist"                    (rootDir inferred as src)
//    dist/index.js
//    dist/lib/money.js

// B) same config, then someone adds scripts/build.ts to the program.
//    The inferred root moves up to the project folder:
//    dist/src/index.js        <- every published path just changed
//    dist/src/lib/money.js
//    dist/scripts/build.js

// C) "rootDir": "src" pinned, plus scripts/build.ts:
//    error TS6059: File 'scripts/build.ts' is not under 'rootDir' 'src'.

console.log(["dist/index.js", "dist/lib/money.js"].join(" "));
// -> dist/index.js dist/lib/money.js

A publishable library layout

Every emitted artifact type follows outDir, not just JavaScript, so declarations and maps stay together in one publishable folder. Excluding dist matters: without it the emitted .js files are picked up as inputs on the next run, which is error TS5055.

Example · typescript
// tsconfig.json
// { "compilerOptions": {
//     "rootDir": "src",
//     "outDir": "dist",
//     "declaration": true,
//     "declarationMap": true,
//     "sourceMap": true
//   },
//   "include": ["src"],
//   "exclude": ["dist", "node_modules"]
// }
//
// src/index.ts emits four files into dist/:
//   index.js  index.d.ts  index.d.ts.map  index.js.map

export const version = "1.4.0";
export function formatCents(cents: number): string {
  return `$${(cents / 100).toFixed(2)}`;
}

console.log(`${version} ${formatCents(1999)}`);
// -> 1.4.0 $19.99

Discussion

  • Be the first to comment on this lesson.