microsoft / microsoft/TypeScript
tsconfig.json should support multiple configurations
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 111k
- Forks
- 14.3k
- Avg merge
- 2d 4h
- Merged PRs (30d)
- 132
Description
Search Terms
multiple configurations, transitive closure
Suggestion
When using multiple packages in a monorepo, it is common to have a tsconfig.shared.json that all packages extend in their individual tsconfig.json. The current system makes it difficult to support various build configurations across packages (such as "dev" vs. "prod").
Because a property named "config" seems redundant in a file named tsconfig.json, I'll borrow the term select from Bazel and propose the following new option in a tsconfig.json file for defining configurations:
// tsconfig.shared.json
{
// Via "select", we define two configurations: "universal" and "modern".
"select": {
"universal": {
"compilerOptions": {
"target": "es3",
"downlevelIteration": true,
},
},
"modern": {
"compilerOptions": {
"target": "es2019",
},
},
},
// These compilerOptions are common to both configurations.
"compilerOptions": {
"sourceMap": true,
"module": "esnext",
"moduleResolution": "node",
"lib": ["dom", "es5", "es2019"],
"strict": true,
},
}
To complement this, tsc would have to support a new --select flag:
# Assume tsconfig.json extends tsconfig.json and defines "include" and "references".
# This would build the modern configuration.
$ tsc --build --select modern -p tsconfig.json
Ideally, it would also be possible to parameterize paths such as outDir so that you could also define this on the base compilerOptions in tsconfig.shared.json:
"outDir": "./dist/${select}/",
though I haven't been able to get "outDir" to do what I want in tsconfig.shared.json in my own project, so there might be more work to do on that front.
Use Cases
I want to be able to have a shared tsconfig.json that defines multiple configurations in one place that can be shared across multiple modules, as opposed to the current situation where we need O(M ⋅ C) tsconfig.json files where M is the number of modules and C is the number of configurations. A detailed example is below.
Examples
I have a Yarn workspace where each package is under the modules/ folder, so my root package.json contains the following:
{
"workspaces": [
"modules/*"
],
"scripts": {
"compile": "tsc --build modules/*"
}
}
I also have a shared.tsconfig.json in the root of my Yarn workspace where I define the "compilerOptions" that I want all packages in the workspace to use. (For example, I set "target": "es2019" in shared.tsconfig.json.) Each folder under modules/ contains its own tsconfig.json file that contains the line:
"extends": "../../tsconfig.shared.json",
and there is also a "references" property with the appropriate list of relative paths to other packages in the workspace.
Ideally, each such tsconfig.json would contain only "extends" and "references", but it seems I have to redeclare "compilerOptions.outDir" and "include" in each of these files even though the values are the same in each one (["src/**/*"] and "./dist", respectively).
OK, so far, so good, but now I want to be able to build my entire Yarn workspace with a different set of compilerOptions, specifically:
"downlevelIteration": true,
"target": "es3",
Ideally, I would be able to specify this in one place and leverage the "references" I already had to define so I could compile one .ts file or package and all of its transitive deps with these compiler options. (I happen to be using ts-loader in Webpack, so my ultimate output is a single .js file, which perhaps biases my expectations here.)
Unfortunately, there does not appear to be any clean way to do that. I could go through and define a tsconfig.es3.json in each of my packages that looks like this:
{
"extends": "./tsconfig.json",
"compilerOptions": {
"downlevelIteration": true,
"target": "es3",
},
}
though even if I did that, I'm not sure I could build all of my modules as ES3 with a one-liner as I did in my original package.json file. Now I'm faced with the additional incidental complexity of:
- Maintaining O(M)
tsconfig.es3.jsonfiles and ensuring all of them are in sync. - Introducing Lerna or some other tool to "walk my DAG" and run
tsc -b -p tsconfig.es3.jsonor something like that.
With the --select proposal, I could avoid the extra tsconfig.json files and still build via a one-liner:
tsc --build --select universal modules/*
I would expect if any of the tsconfig.json files under modules/* did not define a "universal" configuration, the build command would fail (or at least the strictness should be configurable).
Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the tsconfig.json inheritance and project references described in the issue, then trace the tsc --build entry point. Compare the proposed select configurations and --select flag with existing build behavior; done would mean selecting one shared configuration across referenced packages, with clear handling for missing selections and any path parameterization.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- build-system
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 25/100