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:
rootDirwill be set tosrc/mainorsrc/testrespectivelysrcfolder does not appear as part of theliboutputsrc/maincannot importsrc/test(only vice versa should be possible)
| 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
Videos
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.
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.
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.