Error: 'rootDir' is expected to contain all source files.

rootDir is expected to be the root of all source/input files. TS compiler proceeds like this:

collect all input files ("files" / "include" / "exclude" / imports) 
  --> for each included input file
    --> chop off the "rootDir" from the input 
    --> prepend the "outDir"

The src/test files import from src/main, so currently rootDir can only be set to src or above.

Solution: Project References (TS 3.0+)

Project references can solve all of the issues you described:

  • rootDir will be set to src/main or src/test respectively
  • src folder does not appear as part of the lib output
  • src/main cannot import src/test (only vice versa should be possible)
Resulting project structure:
|   tsconfig-base.json            // other config options go here
|   tsconfig.json                 // solution config containing references
|   
+---lib                           // output folder
|   +---main
|   |       mod.d.ts
|   |       mod.js
|   |       tsconfig.tsbuildinfo
|   |       
|   \---test
|           mod-test.js     
\---src
    +---main                      // referenced sub-project main 
    |       mod.ts
    |       tsconfig.json
    |       
    \---test                      // referenced sub-project test 
            mod-test.ts
            tsconfig.json
./tsconfig-base.json:
{
  "compilerOptions": {
    // just some sample values, set your own as needed
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "node",
    "strict": true,
    "esModuleInterop": true,
    // other compiler options
  }
}
./tsconfig.json:
{
  "files": [],
  "references": [
    {
      "path": "./src/main"
    },
    {
      "path": "./src/test"
    }
  ]
}
./src/main/mod.ts:
export const foo = "foo";
./src/main/tsconfig.json:
{
  "extends": "../../tsconfig-base.json",
  "compilerOptions": {
    "outDir": "../../lib/main",
    "composite": true // flag to signal referenced project      
  }
}
./src/test/mod-test.ts:
import { foo } from "../main/mod";

// use your test framework here
export function testMod() {
  if (foo !== "foo") throw new Error("foo expected.");
}

testMod()
./src/test/tsconfig.json:
{
  "extends": "../../tsconfig-base.json",
  "compilerOptions": {
    "outDir": "../../lib/test"
  },
  "references": [
    {
      "path": "../main" // test project depends on main
    }
  ]
}
You can build and clean the project with following scripts:
"scripts": {
    "build": "tsc -b -v",
    "clean": "tsc -b --clean"
},

Further links

  • TypeScript Project References Demo
Answer from ford04 on Stack Overflow
🌐
TypeScript
typescriptlang.org › tsconfig
TypeScript: TSConfig Reference - Docs on every TSConfig option
This option defaults to true under the node16, nodenext, and bundler options for --moduleResolution. ... Rewrite .ts, .tsx, .mts, and .cts file extensions in relative import paths to their JavaScript equivalent in output files. For more information, see the TypeScript 5.7 release notes. ... Default: The longest common path of all non-declaration input files. If composite is set, the default is instead the directory containing the tsconfig.json file.
🌐
Medium
medium.com › @seanbridger › typescript-basics-1-10-typescript-compiler-tsc-and-tsconfig-f9bf0134c5eb
TypeScript Basics (1/10): TypeScript Compiler(tsc) and tsconfig | by Likely Coder | Medium
November 24, 2023 - tsconfig.json is a configuration file for TypeScript projects. It allows you to specify compiler options, include/exclude files, and configure other settings for your TypeScript project.
🌐
Medium
medium.com › @leroyleowdev › understanding-the-typescript-compiler-and-its-options-63bbc3c494ab
Understanding the TypeScript Compiler and Its Options | by Leroy Leow | Medium
January 18, 2025 - When working with the TypeScript compiler, consider these best practices: Use a tsconfig.json file to manage compiler options consistently across your project.
Top answer
1 of 1
4

Error: 'rootDir' is expected to contain all source files.

rootDir is expected to be the root of all source/input files. TS compiler proceeds like this:

collect all input files ("files" / "include" / "exclude" / imports) 
  --> for each included input file
    --> chop off the "rootDir" from the input 
    --> prepend the "outDir"

The src/test files import from src/main, so currently rootDir can only be set to src or above.

Solution: Project References (TS 3.0+)

Project references can solve all of the issues you described:

  • rootDir will be set to src/main or src/test respectively
  • src folder does not appear as part of the lib output
  • src/main cannot import src/test (only vice versa should be possible)
Resulting project structure:
|   tsconfig-base.json            // other config options go here
|   tsconfig.json                 // solution config containing references
|   
+---lib                           // output folder
|   +---main
|   |       mod.d.ts
|   |       mod.js
|   |       tsconfig.tsbuildinfo
|   |       
|   \---test
|           mod-test.js     
\---src
    +---main                      // referenced sub-project main 
    |       mod.ts
    |       tsconfig.json
    |       
    \---test                      // referenced sub-project test 
            mod-test.ts
            tsconfig.json
./tsconfig-base.json:
{
  "compilerOptions": {
    // just some sample values, set your own as needed
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "node",
    "strict": true,
    "esModuleInterop": true,
    // other compiler options
  }
}
./tsconfig.json:
{
  "files": [],
  "references": [
    {
      "path": "./src/main"
    },
    {
      "path": "./src/test"
    }
  ]
}
./src/main/mod.ts:
export const foo = "foo";
./src/main/tsconfig.json:
{
  "extends": "../../tsconfig-base.json",
  "compilerOptions": {
    "outDir": "../../lib/main",
    "composite": true // flag to signal referenced project      
  }
}
./src/test/mod-test.ts:
import { foo } from "../main/mod";

// use your test framework here
export function testMod() {
  if (foo !== "foo") throw new Error("foo expected.");
}

testMod()
./src/test/tsconfig.json:
{
  "extends": "../../tsconfig-base.json",
  "compilerOptions": {
    "outDir": "../../lib/test"
  },
  "references": [
    {
      "path": "../main" // test project depends on main
    }
  ]
}
You can build and clean the project with following scripts:
"scripts": {
    "build": "tsc -b -v",
    "clean": "tsc -b --clean"
},

Further links

  • TypeScript Project References Demo
🌐
Fig
fig.io › manual › tsc
tsc <tsc script>
tsc · CLI tool for TypeScript compiler · On this page · Arguments · Options ·
🌐
E-zest
blog.e-zest.com › what-are-compiler-options-in-typescript
What are Compiler Options in TypeScript?
They are extremely useful for any settings used in our projects, such as for removing comments, or a base url to resolve non-absolute module names, language plugins such as the typescript0styled plugin (to provide CSS linting inside template strings) etc. The compiler options needed to compile the project along with the root files are defined by the tsconfig.json file.
Find elsewhere
🌐
GitHub
github.com › microsoft › TypeScript › wiki › Compiler-Options
Compiler Options · microsoft/TypeScript Wiki · GitHub
TypeScript is a superset of JavaScript that compiles to clean JavaScript output. - Compiler Options · microsoft/TypeScript Wiki
Author   microsoft
🌐
Typescriptdocs
typescriptdocs.com › project-config › compiler-options.html
tsc CLI Options | Typescript Docs
# Run a compile based on a backwards look through the fs for a tsconfig.json tsc # Emit JS for just the index.ts with the compiler defaults tsc index.ts # Emit JS for any .ts files in the folder src, with the default settings tsc src/*.ts # Emit files referenced in with the compiler settings from tsconfig.production.json tsc --project tsconfig.production.json # Emit d.ts files for a js file with showing compiler options which are booleans tsc index.js --declaration --emitDeclarationOnly # Emit a single .js file from two files via compiler options which take string arguments tsc app.ts util.ts --target esnext --outfile index.js
Top answer
1 of 2
21

According to tsc --help:

  --project, -p  Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'.

  --build, -b  Build one or more projects and their dependencies, if out of date

The --project option compile a single project.

The --build option can be seen as a build orchestrator that find referenced projects, check if they are up-to-date, and build out-of-date projects in the correct order. See the documentation for details.

To answer your second question, the --build option is slower because it also compiles dependencies, but it should be faster at a second run, because it compiles only out-of-date projects.

2 of 2
0

For a single project without many dependencies or project references, it seems to be at least experimentally that --build is far faster overall (after caching).

 hyperfine "npx tsc" "npx tsc --project tsconfig.json" "npx tsc --build tsconfig.json" "npx tsc --build" -s "rm -rf dist" --warmup 3
Benchmark 1: npx tsc
  Time (mean ± σ):      1.440 s ±  0.118 s    [User: 2.794 s, System: 0.226 s]
  Range (min … max):    1.293 s …  1.630 s    10 runs

Benchmark 2: npx tsc --project tsconfig.json
  Time (mean ± σ):      1.687 s ±  0.305 s    [User: 3.114 s, System: 0.244 s]
  Range (min … max):    1.336 s …  2.186 s    10 runs

Benchmark 3: npx tsc --build tsconfig.json
  Time (mean ± σ):     408.9 ms ±   4.7 ms    [User: 433.8 ms, System: 110.9 ms]
  Range (min … max):   402.5 ms … 417.3 ms    10 runs

Benchmark 4: npx tsc --build
  Time (mean ± σ):     407.3 ms ±   3.6 ms    [User: 431.9 ms, System: 112.4 ms]
  Range (min … max):   402.8 ms … 413.1 ms    10 runs

Summary
  npx tsc --build ran
    1.00 ± 0.01 times faster than npx tsc --build tsconfig.json
    3.54 ± 0.29 times faster than npx tsc
    4.14 ± 0.75 times faster than npx tsc --project tsconfig.json

Without any warmups and worst case scenario of having no incremental compilation cache:

 hyperfine "npx tsc" "npx tsc --project tsconfig.json" "npx tsc --build tsconfig.json" "npx tsc --build" -p "rm -rf dist"
Benchmark 1: npx tsc
  Time (mean ± σ):      2.642 s ±  0.447 s    [User: 5.445 s, System: 0.287 s]
  Range (min … max):    2.184 s …  3.413 s    10 runs

Benchmark 2: npx tsc --project tsconfig.json
  Time (mean ± σ):      2.156 s ±  0.070 s    [User: 4.780 s, System: 0.289 s]
  Range (min … max):    2.056 s …  2.275 s    10 runs

Benchmark 3: npx tsc --build tsconfig.json
  Time (mean ± σ):      2.226 s ±  0.181 s    [User: 4.889 s, System: 0.269 s]
  Range (min … max):    2.055 s …  2.569 s    10 runs

Benchmark 4: npx tsc --build
  Time (mean ± σ):      2.153 s ±  0.125 s    [User: 4.740 s, System: 0.263 s]
  Range (min … max):    2.065 s …  2.475 s    10 runs

Summary
  npx tsc --build ran
    1.00 ± 0.07 times faster than npx tsc --project tsconfig.json
    1.03 ± 0.10 times faster than npx tsc --build tsconfig.json
    1.23 ± 0.22 times faster than npx tsc

So it seems like npx tsc --build is best in all situations for a small project, and it only improves if you have incremental caching.

🌐
Tutorial Teacher
tutorialsteacher.com › typescript › typescript-compiling-project-and-tsconfig
Compile TypeScript Project
In the above sample tsconfig.json file, the compilerOptions specifies the custom options for the TypeScript compiler to use when compiling a project. Learn about all the compilerOptions available. These are the tsc command options, which you may use while compiling a file.
🌐
TypeScript
typescriptlang.org › docs › handbook › 2 › basic-types.html
TypeScript: Documentation - The Basics
This is the default experience with TypeScript, where types are optional, inference takes the most lenient types, and there’s no checking for potentially null/undefined values. Much like how tsc emits in the face of errors, these defaults are put in place to stay out of your way.
🌐
TypeScript
typescriptlang.org › docs › handbook › tsconfig-json.html
TypeScript: Documentation - What is a tsconfig.json
By invoking tsc with no input files and a --project (or just -p) command line option that specifies the path of a directory containing a tsconfig.json file, or a path to a valid .json file containing the configurations.
🌐
Reddit
reddit.com › r/typescript › do you use tsc in a production environment? seems like everyone uses a third-party transpiler.
r/typescript on Reddit: Do you use tsc in a production environment? Seems like everyone uses a third-party transpiler.
May 10, 2024 -

I'm wondering what the use-cases are for tsc as a transpiler, since it seems like almost everyone uses a third-party one. I'd prefer to use tsc instead of relying on an additional dependency, but find it too restrictive. This has me wondering: who does use it?

To be clear: I understand the use-case for tsc as a type-checker, just not as a transpiler.

Edit: I should have specified: this question is about transpiling with tsc for front-end code, not back-end code.

🌐
Better Stack
betterstack.com › community › guides › scaling-nodejs › cli-tsc-compiler
Using the TypeScript Compiler (tsc): A Complete Guide | Better Stack Community
This guide covers compilation basics, tsconfig.json configuration, strict type checking, watch mode, source maps, incremental builds, and advanced compiler options for optimizing your TypeScript workflow.
🌐
Parcel
parceljs.org › languages › typescript
TypeScript
The TSC transformer also does not perform any type checking (see below). ... You can also choose to use Babel to compile TypeScript. If a Babel config containing @babel/preset-typescript is found, Parcel will use it to compile .ts and .tsx files. Note that this has the same caveats about isolated modules as above. See Babel in the JavaScript docs for more details. ... Parcel does not currently support the baseUrl or paths options in tsconfig.json, which are TypeScript specific resolution extensions.
🌐
Simon Willison
til.simonwillison.net › typescript › basic-tsc
Very basic tsc usage | Simon Willison’s TILs
VSCode has built-in TypeScript support. Hit Shift+Command+B and select the tsc: watch option and it runs that watch command in a embedded terminal pane inside the editor itself.