Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tsconfig.extends as array #29118

Closed
5 tasks done
mutech opened this issue Dec 21, 2018 · 22 comments · Fixed by #50403
Closed
5 tasks done

tsconfig.extends as array #29118

mutech opened this issue Dec 21, 2018 · 22 comments · Fixed by #50403
Assignees
Labels
Committed The team has roadmapped this issue Fix Available A PR has been opened for this issue Suggestion An idea for TypeScript

Comments

@mutech
Copy link

mutech commented Dec 21, 2018

Search Terms

tsconfig extends array

Suggestion

Support defining the "extends" option as array of filenames.

{ "extends": [ "a", "b", "c" ] }

would have the same result as extending c where c extends b and b extends a.

Use Cases

This would make it so much easier to compose modular configuration fragments, analogue to "presets" in babel.

Examples

Given a set of tsconfig files containing fragments:

  • tsconfig/check.json (strict: ...)
  • tsconfig/sourcemap.json (sourceMap: ...)
  • tsconfig/declarations.json (declarations: ...)
  • tsconfig/aliases.json (paths: common aliases)
  • tsconfig/aliases.react.json (extends: aliases, paths: for react)
  • tsconfig/aliases.preact.json (extends: aliases, paths: for preact)
  • tsconfig/webpack.json (settings supporting tree shaking)
  • tsconfig/babel.json (settings to let babel handle platform issues)
  • tsconfig/node.json (settings to target node)
  • tsconfig/browser.json (settings to target browsers)
  • tsconfig/base.json (general defaults)
  • ...

It would be possible to combine these fragments in a way that is much more expressive and easier to
understand than flat config files.

A/tsconfig.preact.json:

{
  "extends": ["base", "sourcemap", "declarations", "babel", "browser", "aliases.preact"],
  "compilerOptions": {
     "declarationDir": "build/A/typings",
     "outDir": "build/A-preact"
  },
  "files": [ "src/A/index.ts" ]
}

A/tsconfig.react.json:

{
  "extends": ["base", "sourcemap", "declarations", "babel", "browser", "aliases.react"],
  "compilerOptions": {
     "declarationDir": "build/A/typings",
     "outDir": "build/A-react"
  },
  "files": [ "src/A/index.ts" ]
}

tsconfig.webpack.json

{
  "extends": ["base", "webpack],
  "compilerOptions": {
  }
}

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.
@weswigham weswigham added Suggestion An idea for TypeScript In Discussion Not yet reached consensus labels Dec 25, 2018
@arogg
Copy link

arogg commented May 30, 2020

please do this i have 4 config. 2 of them have duplicate json in them because of missing mixin ability

@mesqueeb
Copy link

Was there any feedback from the developers of tsconfig files?

@arogg
Copy link

arogg commented Jul 21, 2020

Some more thoughts from me:

This would not be a big deal if tsconfig.js files were allowed.

Currently the only workaround is to regenerate tsconfig files via a custom tool everytime the source tsconfigs change, via file watcher.

I have the option to run typescript programmatically. But that is only for building stuff. The whole IDE experience however relies on actual real tsconfig files on the file system, and there is no way around that.

I understand the added complexity in languages like C# where multiple inheritance is not possible.
But this is not exactly inheritance like in programming languages as I see it. Are we not just talking about a (partially deep) Object.assign() here? Maybe I am just naive. I'll shut up if that is the case though :)

Of course specifying a string should still be possible for backwards compatibility and to hide "complexity".

Also lots of people also expect an array because they are used to eslint and other tools were that is possible.

I also think an array is extremely simple and intuitive to understand. It is the same as a single path, except applied multiple times. There are many options in tsconfig that are harder to understand than an extends array IMHO :)

@jordanbtucker
Copy link

Does anyone see any downsides to this, apart from the amount of work it may take to develop the feature? This seems like a no-brainer to me.

@mutech
Copy link
Author

mutech commented Aug 22, 2020

Looks like there is no point in opening feature tickets for this project.

@Zamiell
Copy link
Contributor

Zamiell commented Dec 11, 2021

The issue was reopened in January of this year, but it remains a little ambiguous whether or not a PR will be accepted.
What is the status of this issue?
Can someone from Microsoft confirm if TypeScript is accepting PRs that implement this functionality?

@mutech
Copy link
Author

mutech commented Dec 11, 2021

For some reason, I'm always the guy who sets up new projects. Everytime I start a node/web/electron project, I'm looking forward to coding because I really like developing with Typescript. And then my selective memory faces the reality of tsconfig, webpack.config, rollup.config, babelrc, createReactApp and later replacing it with a manual build and everytime I wished I had not said yes because I'm so tired of this configuration hell.

tsconfig is by far not the worst part of this trauma, but every time I suffer from the fact that in the end I need to make space in my brain for the full scope of tsconfig options, how they interact with every tool and the last little build pipeline screw, and I just get so much more tired every time.

I understand that this config issue is not exciting or important, but it could solve a persistent headache. Why can't I tell TSC that I want a baseline of language options, a config suitable for react and webpack and be done with it? Why do I have to try chaining up configs in the proper order just to yet again conclude that I have to copy & paste it together anyway. I never got around to use snippet managers. And I'm too old to change my mind on that.

Contemporary software development feels more like overlaying memory blocks in ForTRAN (I forgot the proper terminology, but I guess there are few people around who know enough to point it out) than something that deserves the attribute modern. And I'm saying this even though I deeply love Typescript. I just hate tsconfigs.

I would gladly volunteer to implement it, but considering the many other TS tickets I followed and the fate of related PR's, that would just amplify my frustration.

Sorry for ranting so much, I gladly accept the thumb-downs. Please note that despite the rant I really appreciate your work. Just not your horrible tsconfigs.

EDIT: And I forgot to mention the plethora of death threads that I have to issue in the dev team in case someone dares so much as to even look at any of these configs much less edit them. Extension arrays have the potential to save lifes!

@ritschwumm
Copy link

@mutech i agree in principle - i've seen too many pieces of partial tsconfigs being copied around for the reasons you describe. mixins might help solving this issue.

one caveat though: mixins can become quite interesting to debug once multiple ones are allowed to change the same values. to stay with OO terminology, with simple linear inheritance it's more often than not a good idea to rule out overwriting settings in a child that a parent already defines. with mixins, this is even more important to avoid difficulties in finding out where some setting value actually comes from.

@mutech
Copy link
Author

mutech commented Dec 23, 2021

one caveat though: mixins can become quite interesting to debug once multiple ones are allowed to change the same values.

That's a very valid point. I have this very discussion in another context right now. What if a developer changes the theme color and messes up the whole App because he wants to change the appearance of a button. Not surprisingly, we are now now not using any centralised information storage to be safe from such confusions.

But the thing is, no matter what precautions you take, developers are going to make mistakes, we are human and misunderstandings are not exceptions.

I think that generally the best approach is to use the same or closest level of abstraction to the problem or the real world item you are working with.

For TypeScript, tsconfig is reflecting reality perfectly. For a project, this is just wrong. tsconfig defines what language the project is using, well what language dialect. Which features are available, is JavaScript valid TypeScript, do we have attributes and which flavour, is the project TS strongly typed or forgiving, can we use namespaces, can we use enums and how, does every array has to have an extra comma, the list goes on and one. And if you naively choose one setting you get a bloody nose seeing weeks later that this has an impact on something you believe is utterly unrelated.

It doesn't stop there. We not only define the project language we use, we also have to define the babel language, the Webpack language, the language when extracting documentation, type declarations for JS folk, another endless list.

Technically, I believe it would be better to import typescript modules instead of compiling typescript down to javascript with dependencies on either external ts-runtimes or by copying js code and then using declarations to use the result of this process in a typescript client. This is however impossible, because every library uses their own variation of typescript that is very unlikely to even compile over there. If you want to specify what your typescript flavour is, you have to copy & paste your tsconfig, and in this tsconfig, you specify stuff about commas, module lookup, where to put source maps, which platform APIs are used, etc.

A modular structure has the potential to organise these configurations and separate concerns. We could standardise aspects of the language and create a vocabulary such that we don't always have to talk about commas.

No matter what we do, we cannot avoid difficulties when we work with complex systems, we have to face them instead and handle them in an intelligent way, something better than copy & paste.

I have seen the world of Cobol and Fortran, OO was news to me, I used Smalltalk and Eiffel, Perl and Assembler and I'm still hacking and witness the new ideology of simplicity, the search for the holy single source of truth and please don't inherit stuff. But in my experience, the thing is this, people write good software when they know what they're doing. And to understand stuff, you have to focus on important aspects and avoid unnecessary complications. Then you can inherit and it's not like every kind of engineering is necessarily over-engineered.

Sorry, I'm getting increasingly philosophical here, but I feel like my industry is getting too attached to simple rules being applied to complex problems. Being scared of multiple inheritance because someone once tried to write good C++ code and failed while we all have more than one parent and consider ourselves success stories is rather inconsistent.

The question should not be whether this is multiple inheritance (it's not) and thus bad, the question is whether this would overall make our lives easier, which I firmly believe it would.

@mutech
Copy link
Author

mutech commented Dec 23, 2021

it's more often than not a good idea to rule out overwriting settings in a child that a parent already defines. with mixins, this is even more important to avoid difficulties in finding out where some setting value actually comes from.

Imposing a complex semantic on how configs can or can't be combined or overridden is in my opinion not a good idea for these reasons:

  • It's much simpler to imply restrictions on who has the right to edit tsconfigs in a project and limit this group to people understanding the implications
  • To get such restrictions done right is complicated and once doing it gets complicated, using it typically gets worse.
  • In out world of build pipelines or at least commit hooks, chances that a catastrophic failure passes and hits sensitive body parts are rather slim. It's setting up these mechanisms that's getting harder and limit their usefulness because they fail due to misconfiguration or become so fragile that nobody dares touching anything.
  • I am a parent and I was a child. I am much more respectful towards my kids decisions because I remember how my parents opinions about what settings are best for my life where flawed or simply did not apply to my circumstances.

"extends" not supporting "mixins" is a perfect example for something being too simple to be useful, while a protectionist multiple inheritance mechanism would be too complex to be useful.

As soon as you have a modular configuration, it's easy to develop and define best practices (by creating fragments that you declare fit or a specific purpose), distill which features don't and won't work together and get this topic settled once and for all. And if someone dares touching these crown jewels you can still throw coffee mugs their way.

@arogg
Copy link

arogg commented Dec 23, 2021

User defined types can be more complex then multiple inheritance of tsconfig will ever be. We don't restrict the complexity on those types. And I have had a HARD time bugfixing some complex types of mine. So why are we allowing complexity in user defined types, but not allow a array of extends in tsconfig?

I think users should decide for themselves. I do not think developers should decide for users. We won't break tsconfig files, a string would still be allowed.

I understand that some programming languages have opted out of multiple inheritance of classes. But this is something completely different, since it is just overriding of properties in a certain order, so where is the magic?

I would like to ask for concrete examples of where multiple inheritance of tsconfig will go wrong, please. Since that's all that matters.

Note: tsc can/could print resulting tsconfig files on demand, so troubleshooting should be a breeze.

@frank-dspeed
Copy link

frank-dspeed commented Mar 26, 2022

@weswigham please review this and consider this as related to #46452 as it now gets more and more relevant with the new resolve modes and if the module do use allowSyntactic or not or esInterOp or not and so on also see: #48437 which is a duplicate of this but shows a more current typescript case with 4.7 and the need to have diffrent module resolve composition sets with diffrent options. for monorepos and so on.

@WillsterJohnson
Copy link

A somewhat niche (but decreasingly so) use case for multiple extends is for people using TypeScript in SvelteKit.

In SvelteKit, types are automatically generated for your components and pages, and have to be accessed by extending a generated tsconfig file (the generated config also gives a lot of other configurations which are necessary for development).

However if you're using a monorepo to handle related yet distinct packages, you can't have both a common tsconfig and the SvelteKit generated tsconfig.

This would not be an issue if instead of only permitting one extends, you were allowed several. A virtual chain of extensions could be made and analysed, where items in the array override items prior to them.

I don't think it's a particularly difficult thing to do, and passing a string can work exactly the same as it does currently after multiple extends is implemented.

@blujedis
Copy link

In addition to @willster277's explanation I would add that this issue is not going to go away. With the advent of Turborepo, Rushjs and others...I see this as a moderate but growing need. For those of us working in larger monos this would be truly helpful. Additionally with Vercel/Next investing in SvelteKit, which is gaining growing support, I suspect you will only see more and more of this very issue popup. While things are always more involved under the hood, most are going to see this as a simple Object.assign and wonder why it doesn't already exist. While I get it's not quite that simplistic, nonetheless that's the perception.

kodiakhq bot pushed a commit to vercel/turborepo that referenced this issue Jun 29, 2022
A basic svelte example. 

Replaces #1184 and #244

**NOTE**: There is no shared `ts-config` package here because of how svelte sets up it's ts-config inheritance. The base config is auto generated in a set location (`.svelte-kit`). There are a few open issues about adjusting this for better monorepo support (relevant issues: sveltejs/kit#4052, microsoft/TypeScript#29118 (comment))
@madeleineostoja
Copy link

madeleineostoja commented Aug 3, 2022

Is there any input from the typescript team on whether a PR would be accepted for this issue? Is it a technical resourcing issue in getting it done or still under consideration as to whether it's worth doing?

And just adding another datapoint to the pile, without this support in a monorepo context with shared tsconfig and any kind of generated project tsconfig (SvelteKit in my case), it becomes extremely painful to use Tyepscript without it.

@frank-dspeed
Copy link

frank-dspeed commented Aug 5, 2022

The most simple solution would also be to simply implement tsconfig.js call that from time to time and look if the result did change maybe expose a didChange value from time to time

thats the level we need to address everything.

i experimented with that via tsconfig.js => tsconfig.json

and i got good results but it would be more elegant if i could avoid the filesystem writes

@RyanCavanaugh RyanCavanaugh added Committed The team has roadmapped this issue and removed In Discussion Not yet reached consensus labels Aug 5, 2022
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 4.9.0 milestone Aug 5, 2022
@RyanCavanaugh RyanCavanaugh self-assigned this Aug 5, 2022
@RyanCavanaugh
Copy link
Member

The tipping-point scenario for us was the upcoming module resolution work, wherein it's likely that we'll be encouraging bundlers/etc to ship specific config subsets that describe their behavior.

@sheetalkamat
Copy link
Member

@navya9singh is making changes to make commandline options to support type that takes list or element type

@mutech
Copy link
Author

mutech commented Aug 9, 2022

@navya9singh is making changes to make commandline options to support type that takes list or element type

There are already command line options using lists (include/exclude), so there should be no reason to have different option types in the command line and config files.

@prometheas
Copy link

Looking forward to this enhancement hopefully shipping soon… 🤞

yeabu369 pushed a commit to yeabu369/afrovalleyio that referenced this issue Dec 31, 2022
A basic svelte example. 

Replaces vercel/turborepo#1184 and vercel/turborepo#244

**NOTE**: There is no shared `ts-config` package here because of how svelte sets up it's ts-config inheritance. The base config is auto generated in a set location (`.svelte-kit`). There are a few open issues about adjusting this for better monorepo support (relevant issues: sveltejs/kit#4052, microsoft/TypeScript#29118 (comment))
@akwodkiewicz
Copy link

Sorry if that was already explained above, but I want to be completely sure what this change brought to the table. Let's say that:

  • my base /tsconfig.json has compilerOptions with 10 properties

and

  • my /derived/tsconfig.json

    • extends ../tsconfig.json

    • has an explicit empty compilerOptions block

Then is it true that

  • pre-5.0: /derived/tsconfig.json has the default TS compilerOptions

  • post-5.0: /derived/tsconfig.json has the same compilerOptions as /tsconfig.json?

And if I'm right then does it mean that to maintain the same behaviour for the /derived project when migrating to 5.0 I now have to go through a list of tsconfig defaults and explicitly set each of the 10 properties mentioned in /tsconfig.json to the default values in /derived/tsconfig.json?

@jordanbtucker
Copy link

jordanbtucker commented Sep 6, 2023

Then is it true that

  • pre-5.0: /derived/tsconfig.json has the default TS compilerOptions
  • post-5.0: /derived/tsconfig.json has the same compilerOptions as /tsconfig.json?

No. TypeScript 4 does not set the default compiler options if you set compilerOptions to an empty object when extending from another tsconfig and neither does TypeScript 5. The compiler options from the base tsconfig(s) will be applied to the final tsconfig.

// tsconfig.base.json
{
    "compilerOptions": {
        "declaration": true,
    }
}
// tsconfig.json
{
    "extends": "./tsconfig.base.json",
    "compilerOptions": {}
}
// npx --package=typescript@4 -- tsc --showConfig
// npx --package=typescript@5 -- tsc --showConfig
// Both show same result:
{
    "compilerOptions": {
        "declaration": true
    },
    "files": [
        "./index.ts"
    ]
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Committed The team has roadmapped this issue Fix Available A PR has been opened for this issue Suggestion An idea for TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.