Repository navigation
Configuration Inheritance #9876
Copy link
Copy link
Closed
Labels
CommittedThe team has roadmapped this issueThe team has roadmapped this issueSuggestionAn idea for TypeScriptAn idea for TypeScript
Milestone
Description
Our friends from Angular (@angular) asked for this feature while they were visiting last week, since they use a lot of configuration files internally.
Proposal
Add a new top-level (alongside compilerOptions, files, includes, and excludes) tsconfig parameter - extends (to steal the term from eslint).
- Its value is a string or an array of strings which are interpreted as rooted or relative paths (so starting with either a drive root or
.or..) from the current tsconfig to other tsconfigs to inherit from - with or without a.jsonextension, if no extension is provided, we first look for an extensionless file by the name, and failing that, add a .json extension and look for that. - If we add recommended or built-in configs to inherit from in the future, those will look like a module identifier, rather than a path (ie,
"typescript/strict"). For now, non-relative, non-rooted paths will be an error. At present, built-in or shareable configs are not part of this proposal. - All configs your config inherits from are first parsed and resolved (thereby filling in anything they inherit), and then merged. Configs later in the list override the configuration provided by those earlier in the list. Configuration in the active config can override those provided by an inheriting config. If a circularity is encountered, we report an error.
- If any inherited configs we load have errors, we will report them.
- Top-level members other than
compilerOptionsare completely overridden by following definitions.compilerOptionsat the top level is merged (though its individual keys will still be overridden as though they were top-level keys). - Relative paths found in the configuration file will be resolved relative to the configuration file they originated in.
Examples
configs/base.json:
{
"compilerOptions": {
"allowJs": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}configs/tests.json:
{
"compilerOptions": {
"preserveConstEnums": true,
"stripComments": false,
"sourceMaps": true
},
"exclude": [
"../tests/baselines",
"../tests/scenarios"
],
"include": [
"../tests/**/*.ts"
]
}tsconfig.json:
{
"extends": "./configs/base",
"files": [
"main.ts",
"supplemental.ts"
]
}tsconfig.nostrictnull.json:
{
"extends": "./tsconfig"
"compilerOptions": {
"strictNullChecks": false
}
}tsconfig.tests.json:
{
"extends": ["./configs/tests", "./tsconfig"],
"compilerOptions": {
"module": "commonjs"
}
}tsconfig.tests.browser.json:
{
"extends": ["./configs/tests", "./tsconfig"],
"compilerOptions": {
"module": "amd"
}
}Alex Eagle (@alexeagle) Daniel Rosenwasser (@DanielRosenwasser) Does this cover your usecases?
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
CommittedThe team has roadmapped this issueThe team has roadmapped this issueSuggestionAn idea for TypeScriptAn idea for TypeScript
Activity
kitsonk commentedon Jul 21, 2016
Not to take anything away from the Angular (@angular) team 😄, but other projects would benefit from this too. Currently Dojo (@dojo) is managing this via the tooling tasks which overwrite configuration "magically" when doing different targets, but it would be great to just provide this as a
.jsonfile thattscunderstood inherently.weswigham commentedon Jul 21, 2016
Kitson Kelly (@kitsonk) We actually started doing the same thing with our debug/release build duality inside the compiler, if you look at our gulpfile. 😆
alexeagle commentedon Jul 21, 2016
SGTM cc Igor Minar (@IgorMinar)
I understand about file references being rooted relative to the file requested by
-poption, though that is clearly the thorny issue here, as knowledge of resolution is non-local in the tsconfig containing the reference. Iftsconfig.jsonextends./a.jsonandsubdir/tsconfig.jsonextends../a.jsonthere are no correct options for relative paths ina.json?weswigham commentedon Jul 21, 2016
Alex Eagle (@alexeagle) Correct. Though, you could still have useful things such as
"exclude": ["node_modules"], which is probably still what you wantedregardless of where its resolved from. My general recommendation would be
to avoid using paths in inheritable configurations unless you're really
sure you want it. Generally, the config extends member will behave mostly
as
Object.assign({}, ...extendedConfigs). Or at least that's the intent.Rewriting all the paths complicates that a lot... And requires knowledge of
what is a path and what isn't.
mhegazy commentedon Jul 21, 2016
You could still do this if you say you are merging the "resolved" version of the config files. so you first look at the config file, and make all the file path properties absolute using the containing file location, something we already do today, then merge the result.
alexeagle commentedon Jul 21, 2016
+1 for resolving the config before merging. Tools will also have an
incredibly hard time with paths in an included file that point to the wrong
place, even if it looks canonical like "node_modules" I don't think the IDE
should be expected to look in parent directories for that.
On Thu, Jul 21, 2016 at 3:53 PM Mohamed Hegazy notifications@github.com
wrote:
weswigham commentedon Jul 21, 2016
Mohamed Hegazy (@mhegazy) We could do that for the top-level options easily, but for things within the
compilerOptionshash, it seems like a really dicey proposition. I originally wanted to not resolve anything just because I don't think we can identify every potential path in thecompilerOptionshash (specifically, paths which appear in 'object' or 'list' argument kinds). So I thought differing behavior between options would be surprising, making not resolving paths at all the least surprising option.mhegazy commentedon Jul 21, 2016
it is very hard to reason about relative paths when consumed in the context of a different file.
we already mark some compiler options as
isFilePathand make these absolute relative to the tsconfig.json path. so this would not be too much different here. we just have to resolve them before we do the merge.weswigham commentedon Jul 21, 2016
Mohamed Hegazy (@mhegazy) But we don't currently mark members of object compiler options as paths (like the
pathsoption). Also, in the extensibility model pr, I have no way of knowing if an extension's arguments are a path.mhegazy commentedon Jul 21, 2016
"paths" is not. it is always relative to
baseURL,baseURLhowever is a file path.mhegazy commentedon Jul 21, 2016
for the extensions, this is a hard one. no idea what we can do there. these are opaque objects and we have no visibility in them.
weswigham commentedon Jul 21, 2016
Mohamed Hegazy (@mhegazy) since
pathsisn't an issue, I guess we can just resolve all filepaths we see in the config and say that the extensions hash is treated as an opaque object, so you shouldn't rely on inheriting paths in it anyway?Or maybe I should just not think about extensions, solve this for this, then rebuild the extensions argument structure on top of it? That sounds better, I think.
In any case, I'll update the OP to reflect that we will resolve paths in all path-based options.
clavecoder commentedon Jul 22, 2016
This is certainly more versatile than my suggestion (##9835), yet, perhaps you would like to check with the angular team if the environment based solution (which avoids the relative paths issue) is good enough. I know they source multiple packages from one repo that each have their own
tsconfig.jsonfiles, so maybe that is their driver. It would be good to add a user story to understand what problems are addressed by this solution.I'm trying to follow the Angular 2 pattern my repo and figured I was doomed to copy settings everywhere. Multiplackage development it's so common that there is a popular NodeJS tool to support it: https://lernajs.io/
17 remaining items
mhegazy commentedon Sep 13, 2016
This should be available in the nightly builds starting 9/14/2016
tomitrescak commentedon Oct 4, 2016
Shall this work with @next? Running
2.1.0-dev.20161004.In my
src/tsconfig.jsonI keep the global project configuration with es6 modulesIn my
src/server/tsconfig.jsonI have following configuration{ "extends": "../tsconfig", "module": "commonjs" }Yet, when I compile my project I still have es6 modules everywhere :/
kitsonk commentedon Oct 4, 2016
It isn't going to magically know what files are included and excluded, just based on its location and you would need to invoke the compiler twice.
So in
src/tsconfig.jsonyou need to excludesrc/server/and insrc/server/tsconfig.jsonyou need to limit to onlysrc/server/and invoketsctwice.tomitrescak commentedon Oct 4, 2016
Hmm, I'm not sure if I follow correctly. These are my two typings.jsons
/tsconfig.json
{ "compileOnSave": true, "compilerOptions": { "target": "es6", "module": "es6", }, "include": [ "./server/**/*.ts", "./client/**/*.ts", "./client/**/*.tsx", "./typings/**/*.ts", "./shared/**/*.ts" ], "exclude": [ "node_modules", "./server/**/*.ts" ] }and the /server/tsconfig.json
{ "extends": "../tsconfig.json", "module": "commonjs", "include": ["./**/*.ts"] }Now the server files are not emitted at all.
kitsonk commentedon Oct 4, 2016
Where did your extends go in
/server/tsconfig.json? How are you invoking the compiler?tomitrescak commentedon Oct 4, 2016
Hi, I updated the above example. I simply executed tsc twice in the same destination.
mhegazy commentedon Oct 4, 2016
The file that extends a configuration should be a valid configuration file itself, i.e.:
{ "extends": "../tsconfig.json", "compilerOptions": { "module": "commonjs" } }weswigham commentedon Oct 15, 2016
Aleksey-Bykov Alright. Done; but I don't think closed issues are normally considered a canonical source of documentation. 😛
aluanhaddad commentedon Oct 16, 2016
Mohamed Hegazy (@mhegazy) Sorry for resurrecting this thread. In spite of your advice, which I still think is correct advice, I heedlessly went ahead with an experiment. I found that analyzing the
jspm.confg.jscould be used to install correct types and that multiple versions could be supported, but my solution only works with Aurelia, and will fail if they restructure their git repository. However generating paths was surprisingly pleasant, especially since I got to write a node.js app using TypeScript'sasync/awaitfor the first time. From the experience, I would say that analyzing where to get the declaration files is the difficult part.Thanks for your time.
evil-shrike commentedon Mar 24, 2017
In the original proposal it was suggested to allow using arrays as
extendsvalues.But it seems it wasn't implemented (only strings are allowed).
Here's our use case where it's needed to have multiple base config (array in
extends).Given an app written in TS. I have several dependencies installed via bower into vendor_components folder. The app have its own
tsconfig.jsonwhich is very simple as it's based on other configs:The config "./vendor_components/ourFramework/tsconfig" is major, it describes all compilerOptions. But ourModule's config is also needed as it contains
paths,like:
So it's ok if a lib/fw in vendor_components contains its own tsconfig. If the app want include that lib/fw sources it should use that tsconfig as well. But currently it can use only one. Other's config have to replicated in app's tsconfig.