Skip to content

/flags/typescript/tsc-outdir-rootdir

tsc --outDir and --rootDir: control where compiled .js files go

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash) · v24.20.0updated
tool
TypeScript
command
tsc
flag
--outDir <dir>, --rootDir <dir>
default
outDir: none (each .js is written next to its .ts); rootDir: the folder of tsconfig.json in TypeScript 6+

--outDir dist makes tsc write the compiled .js (and .d.ts, .map) files into dist instead of next to each .ts file. --rootDir src says which source folder that output tree starts from, so src/index.ts becomes dist/index.js rather than dist/src/index.js. Every compiled file must live under rootDir; a file outside it fails with TS6059 "'rootDir' is expected to contain all source files".

The three common situations

bash
npm init -y >/dev/null
npm install --silent --no-audit --no-fund typescript@7.0.2 >/dev/null 2>&1
mkdir -p src scripts
echo 'export const a: number = 1;' > src/index.ts
echo 'console.log("build helper");' > scripts/build.ts
opts='"module": "nodenext", "target": "es2022", "strict": true, "types": []'

echo "== 1. outDir only, include src"
echo "{ \"compilerOptions\": { \"outDir\": \"dist\", $opts }, \"include\": [\"src\"] }" > tsconfig.json
npx tsc; echo "exit: $?"; find dist -type f; rm -rf dist

echo "== 2. outDir + rootDir src, include src"
echo "{ \"compilerOptions\": { \"outDir\": \"dist\", \"rootDir\": \"src\", $opts }, \"include\": [\"src\"] }" > tsconfig.json
npx tsc; echo "exit: $?"; find dist -type f; rm -rf dist

echo "== 3. rootDir src, but scripts/ is included too"
echo "{ \"compilerOptions\": { \"outDir\": \"dist\", \"rootDir\": \"src\", $opts } }" > tsconfig.json
npx tsc; echo "exit: $?"

Output

== 1. outDir only, include src
tsconfig.json(1,24): error TS5011: The common source directory of 'tsconfig.json' is './src'. The 'rootDir' setting must be explicitly set to this or another path to adjust your output's file layout.
  Visit https://aka.ms/ts6 for migration information.
exit: 2
dist/src/index.js
== 2. outDir + rootDir src, include src
exit: 0
dist/index.js
== 3. rootDir src, but scripts/ is included too
error TS6059: File '/tmp/work/scripts/build.ts' is not under 'rootDir' '/tmp/work/src'. 'rootDir' is expected to contain all source files.
  The file is in the program because:
    Matched by default include pattern '**/*'
  File is CommonJS module because '/tmp/work/package.json' has field "type" whose value is not "module"
exit: 2
  1. Without rootDir, TypeScript 7 uses the folder of tsconfig.json as the root, writes dist/src/index.js and exits with status 2, reporting TS5011 because the common source folder (./src) differs from it. Older versions silently used the common source folder and wrote dist/index.js, so this is a frequent upgrade surprise.
  2. With rootDir: "src" the layout is flat and there is no error.
  3. Without include, every .ts in the project is compiled, scripts/build.ts is outside src, and tsc stops with TS6059, explaining that the file came from the default include pattern **/*.

How the options interact

OptionEffectDefault
outDirFolder for emitted .js, .d.ts and .map filesnone: files are emitted next to the sources
rootDirBase folder whose structure is reproduced under outDirTypeScript 6+: folder of the tsconfig; before: longest common folder of the inputs
include / exclude / filesWhich files are compiledinclude: **/* in the tsconfig folder
declarationDirSeparate folder for .d.ts filessame as outDir
noEmitType check only, write nothingfalse

Fixing TS6059 and TS5011

  • TS6059 (file is not under rootDir): either narrow include to the source folder ("include": ["src"]), move the file into src, or widen rootDir (for example to .) and accept a nested output layout. Test files and config scripts such as vite.config.ts are the usual intruders; give them their own tsconfig with noEmit.
  • TS5011 (common source directory differs): set rootDir explicitly to the folder the message names, usually ./src. The message links to the TypeScript 6 migration notes.
  • Imports from outside rootDir: a relative import of ../shared/util.ts pulls that file into the program, and it then has to be inside rootDir too. Use project references or a package for shared code.

Pitfalls

  • outDir does not clean old output. Renamed or deleted sources leave stale .js files in dist; delete the folder in your build script before compiling.
  • Paths in an extended config are resolved from that config's folder: a base config in configs/ with "outDir": "dist" writes to configs/dist. See tsc -p and check the result with tsc --showConfig.
  • Without outDir, .js files appear next to the .ts sources and can be picked up by the next compile or by your test runner.

FAQ

Why does tsc put my files in dist/src instead of dist?

The output mirrors the source tree relative to rootDir. If rootDir is the project folder (the default in TypeScript 6 and 7), src/index.ts is written to dist/src/index.js. Set rootDir to src to get dist/index.js.

Does rootDir decide which files are compiled?

No. include, files and imports decide that. rootDir only says which folder the output layout starts from, and every compiled file must be inside it, otherwise tsc reports TS6059.

Where do .d.ts files go?

With declaration enabled they go next to the .js files in outDir, unless declarationDir sets another folder. Both follow the same rootDir layout.

Sources