25

I have the following directory structure:

.
├── tsconfig.json ("module": "CommonJS")
└── foo/
    ├── node-file.ts
    └── bar/
        ├── browser-file.ts
        └── tsconfig.json ("module": "esnext")

The root tsconfig.json has module set to CommonJS because I want most of my files to compile for Node. But inside bar, I want the files to compile to JavaScript Modules, so I've set module to esnext.

Now when I run tsc from the root, I expect node-file.ts to compile to CommonJS module and browser-file.ts to compile to a JavaScript Module. But that's not what I'm getting. It seems that tsc is completely ignoring foo/bar/tsconfig.json and is only picking up the root tsconfig.json.

(I also use tsc --watch while developing, so I'm trying to avoid having to run two different tsc processes to compile the two different targets. It feels to me that running a single tsc with nested tsconfig.json files should give me the desired results.)

Anyone know what it is that I'm doing wrong?

3
  • TypeScript only uses one tsconfig.json and doesn't automatically use subdirectories' tsconfig.json for the files there. You may want to look into project references Commented Nov 1, 2020 at 4:05
  • I couldn’t tell if project references will allow me to set a different module type per directory, I’ll look more into it. But thanks for correcting my wrong assumption about how the compiler works. Feel free to post this as an answer and I’ll accept it. Commented Nov 1, 2020 at 7:59
  • The behaviour you describe is implemented by IDEs like VS Code. The fact that tsc doesn't support this beahviour means that we can't easily type-check our codebases in a way that reports the exact same errors as our IDEs by default. This is sooo annoying and makes setting up monorepo projects unnecessarily challenging. The "simplest" workaround is to create additional tsconfig.<something>.json files that include only the files that are not included by nested tsconfig files and then run tsc -p for each of them... Commented May 24, 2023 at 7:15

1 Answer 1

32

TypeScript only uses one tsconfig.json and doesn’t automatically use subdirectories’ tsconfig.json for the files there. However, you can use project references for this.

Create a directory structure like this:

.
├── tsconfig.json
├── tsconfig.settings.json (optional)
└── foo/
    ├── node-file.ts
    ├── tsconfig.json ("module": "commonjs")
    └── bar/
        ├── browser-file.ts
        └── tsconfig.json ("module": "esnext")

tsconfig.json

{
  "files": [],
  "references": [
    {"path": "./foo"},
    {"path": "./foo/bar"}
  ]
}

This is the root tsconfig.json. When you run tsc --build (see below) in the root directory, TypeScript will build the referenced projects ./foo/tsconfig.json and ./foo/bar/tsconfig.json.

The "files": [] is to stop accidental tscs without --build from attempting to compile everything in the root directory, which will error but create multiple .js files in possibly the incorrect places.

tsconfig.settings.json (optional)

{
  "compilerOptions": {
    "strict": true,
    "noImplicitReturns": true
  }
}

You can put configuration common to foo and foo/bar and extend this configuration with extends to reduce duplication. Note that all relative paths in here will be resolved relative to tsconfig.settings.json when extended, so something like "outDir": "dist" may not work as expected.

foo/tsconfig.json

{
  "extends": "../tsconfig.settings.json",
  "exclude": ["bar/**/*.ts"],
  "compilerOptions": {
    "module": "commonjs"
  }
}

This is the configuration for the CommonJS files. It also extends the common config and excludes the files in foo/bar.

foo/bar/tsconfig.json

{
  "extends": "../../tsconfig.settings.json",
  "compilerOptions": {
    "module": "esnext"
  }
}

This is pretty similar to foo’s configuration.


Building

To compile foo and foo/bar at the same time, use build mode from the root directory:

tsc --build         # or tsc -b
# Watch mode:
tsc --build --watch # or tsc -bw

From the handbook:

A long-awaited feature is smart incremental builds for TypeScript projects. In 3.0 you can use the --build flag with tsc. This is effectively a new entry point for tsc that behaves more like a build orchestrator than a simple compiler.

Running tsc --build (tsc -b for short) will do the following:

  • Find all referenced projects
  • Detect if they are up-to-date
  • Build out-of-date projects in the correct order

You can provide tsc -b with multiple config file paths (e.g. tsc -b src test). Just like tsc -p, specifying the config file name itself is unnecessary if it’s named tsconfig.json.

You can also compile individual projects:

tsc -b foo     # or cd foo     && tsc -b
tsc -b foo/bar # or cd foo/bar && tsc -b

Note that are some build-only flags and you cannot override compiler options with command-line arguments.

Sign up to request clarification or add additional context in comments.

2 Comments

What if something inside the subfolder (bar) access a function exposed by the upper folder foo?
@igol You should be able to add a reference to the foo project from bar with "references": [{"path": "../foo"}] in bar’s tsconfig.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.