Typescript: Feature request: allow user to merge extended arrays in tsconfig files

Created on 17 Nov 2017  Β·  22Comments  Β·  Source: microsoft/TypeScript

Scenario: As a user, I would like to optionally merge extended arrays in tsconfig files. To do so, I would add a nested dot array ["..."] reminding spread operator to the property I want to merge. Here is an example:


tsconfig-base.json

{
  "exclude": ["**/__specs__/*"]
}


tsconfig-custom.json

{
  "extends":  "./tsconfig-base.json",
  "exclude": [["...tsconfig-base"], "lib"] // resolved to ["**/__specs__/*"; "lib"]
}



Alternative: using a config {} object


tsconfig-custom.json

{
  "extends":  "./tsconfig-base.json",
  "exclude": [{ "extends": "tsconfig-base" }, "lib"] // resolved to ["**/__specs__/*"; "lib"]
}

Awaiting More Feedback Suggestion

Most helpful comment

Personally, I'd sooner like to see a tsconfig.js or tsconfig.ts affordance (similar to a webpack config) to generally leverage JS syntax for complex configuration merging rather than introduce special-snowflake "syntax" into a json file with complex layering and execution semantics. There's already a thing that exists to describe these kinds of procedural transforms, and it's called "code".

All 22 comments

Personally, I'd sooner like to see a tsconfig.js or tsconfig.ts affordance (similar to a webpack config) to generally leverage JS syntax for complex configuration merging rather than introduce special-snowflake "syntax" into a json file with complex layering and execution semantics. There's already a thing that exists to describe these kinds of procedural transforms, and it's called "code".

Personally, I'd sooner like to see a tsconfig.js or tsconfig.ts affordance (similar to a webpack config) to generally leverage JS syntax for complex configuration merging rather than introduce special-snowflake "syntax" into a json file with complex layering and execution semantics. There's already a thing that exists to describe these kinds of procedural transforms, and it's called "code".

Noooooooooooooooo.

We did talk about the OP when we first put in the configuration inheritance in place, and had a proposal of extends and overwrites to be two different behaviors for cases like these.. but in the end we chose to avoid complexity and just go with extends meaning overwrite. i think in hindsight that was a good discussion, and most users did not have the need for additional complexity. i would say for this request as well, the additional complexity (both in supporting the feature, and for users tsconfig.json files) is not worth the value you get out of it.

"Do not over-complicate the implementation with abstractions where a little copying will suffice" - Unattributed programming koan

I understand the points you all made - especially the programming koan -, and I am only a novice. However you might be interested in the use-case : a project with many npm libraries acting as program modules, orchestrated with yarn workspaces and lerna. In such a situation, I find configuration reusability extended to the feature I proposed very convenient and clean.

As a side note, I find in some of your comments sarcasm I was absolutely not expecting from typescript repo maintainers. You don't need to scorn at people to make your point.

Sorry if I came off a bit sarcastic (I suppose quotes around things in text imply sarcastic airquotes). I probably should have used italics to imply emphasis and emphaticness rather than quotes in my first comment. I do understand the desire for reusable configuration, I just want the conversation and discussion on the issue to remain a bit light-hearted (and attempted to set such a tone) as once you start talking about code as configuration like I was, in my experience people start having _very_ strong opinions (both for and against). I mean no slight to either your suggestion or you personally.

@weswigham I greatly appreciate this clarification :-) But just to understand your viewpoint, would you really rather have a tsconfig.js ? Or, at the same time you consider code more appropriate and a javascript config file off-putting as too webpackish (the team reaction to your post is confusing) ?

I, personally, would prefer a code file. @mhegazy disagrees. :)
As I said, strong opinions.

@weswigham OK, then I'm totally with you on this. Either through extended syntax or "codable" config file, I would love the feature.

Code file is not statically alayzable. We have a whole set of tools that rely on this config file to drive user experiences. Code file is a non starter.

As I noted earlier, we have discussed such configuration inheritance use cases when we were implementing the feature; as a matter of fact @weswigham when he first propsed it had an extends and overrides, he also had multiple inheritance support. Back then we chose to not complicate the feature and ended up pulling the plug on these two proposals.

After having this in use for a few years now, I do not see that as a bad choice, and I do not think there are new use cases that are blocked by that decision.

And yes, it would be nice if you can configure every possible inheritance model you can think of; but features come with a cost, both for the compiler and toolset maintainers and for new users. There is always a trade of.

@mhegazy Thank you for both your posts which were very insightful. This is only speculation, but perhaps the shift towards monorepos (here is typescript workspaces plugin) will make the need for high config reusability more widespread.

We are always open to revisiting requests.

Code file is not statically alayzable.

Don't we have TypeScript for that? :D Thanks to allowJs I type check my Jest and Webpack configs like this https://twitter.com/PipoPeperoni/status/1016199550330195971 and to get features like code completion and so on. Are there more things left where .json is a strong requirement?

By the way I wonder what is the correct way to "read" an extended config? Is this the "shortest" way?:

import { parseJsonConfigFileContent, readConfigFile, sys } from 'typescript';
const path = './foo/tsconfig.json';
const { config } = readConfigFile(path, sys.readFile);
const host = {
  useCaseSensitiveFileNames: false,
  readDirectory: sys.readDirectory,
  fileExists: sys.fileExists,
  readFile: sys.readFile
};
const { options } = parseJsonConfigFileContent(
  config,
  host,
  basename(path)
);
console.log('Parsed (!) CompilerOptions:', options);

+1 for merge
I use nrwl/nx to manage my monorepo. Files structure looks like this:

β”œβ”€β”€ apps
β”‚   β”œβ”€β”€ simple-app
β”‚   β”‚   └── tsconfig.json
β”‚   └── complex-app
β”‚       └── tsconfig.json
β”œβ”€β”€ libs
β”‚   └── shared-lib
β”‚       └── tsconfig.json
β”œβ”€β”€ tsconfig.json
└── package.json

All apps/libs tsconfigs extend root tsconfig ("extends": "../../tsconfig.json").

In order for apps to import shared-lib NX adds a paths to root config:

    "baseUrl": ".",
    "paths": {
      "@product/shared-lib": ["libs/shared-lib/src/index.ts"]
    }

It works great! But a complex-app is really complex, so I want to add additional alias to the root of the app:

// in complex-app/tsconfig.json
    "baseUrl": "src",
    "paths": {
      "@@/*": ["./*"]
    }

And I've just lost an @product/shared-lib alias from the root config. I can add it manually. But it would be great if I could somehow merge paths.

@zaverden What about using the xplat architecture? Would not that be sufficient to tackle your use-case? After all, if you want to refactor modules, you simply have to rename your module and all the shared modules between your apps, so it's one additional file change per app, which IMO is a very scalable solution, but there might be a better approach. What does @weswigham think?

@diegovincent I don't think I understand how xplat can help to address the issue.

I have custom path aliases defined in complex-app/tsconfig.json. Now if I want to use some lib in complex-app I have to redefine a path alias to the lib, though the lib path alias is already defined in the root tsconfig.json.

So I don't understand how xplat can help me to get rid of custom path aliases on application level, when whole team use them a lot and think they are very convenient.

@zaverden I mean their architecture just makes yourself wonder if you truly need what you are asking for. A lot of the times we all stay too long wondering if we can do something, but not if we should do it.

I was just thinking, maybe all your features can probably be refactored outside of your app, or, in the case they are extremely app-specific, they can simply be refactored into Angular modules, hence you only import stuff once, then moving stuff around is just a matter of moving the module and its import. If the module is already inside your complex-app folder, then the nesting will not be something like ../../../..., but instead something like ./feature-a/feature-a.module or ./feature-a.module which is already really clean; that, in the worst case scenario. Best case scenario, you can refactor your functionality outside of the scope of your app, maybe you have some backend utilities, date parsing libraries, translate modules, and core logic of your company which is not precisely app-specific. So, after you refactor everything to libraries outside your complex-app folder, you can simply import it using:

import { MyModule } from '@my-org/libs/core/my-business-domain'

Which already solves your problem. My only concern is, can the experts at Microsoft or Angular or Nx or Xplat assure that it is indeed a best practice to create additional paths in nested tsconfig files, or this is just a sympton of a (mono)repo that needs refactoring?

Those are my 2 cents.

@diegovincent thank you for clarification. Indeed you are right, these custom aliases are signals that something went wrong. Unfortunately, my team did not understand these signals on earlier stages of a project.

Now we develop new app with xplat-like approach (we defined our own categories and templates). But there are dozens of old-style active apps. We cannot refactor them all in one day.

@zaverden

My recommendation is that you do not try to "over-complicate" the tsconfig file in your monorepo(s). Simply, refactor mercilessly but little by little. If you have a C-level rank at your company, or you are a Tech Lead, I highly recommend that you deeply read Clean Architecture book and the Extreme Programming websiteβ€”although you certainly do not have to ingrain all the rules in your company by any means, just "steal" their best practices for your team.

Good luck, I am going to be active in this kind of topics during this year, feel free to contact me!

D.

I just tried to add typeorm shim to my frontend to share entities using paths in the frontend tsconfig which extends my main monorepo tsconfig.

Is there a work around or I'll have to copy my main tsconfig path to the frontend tsconfig?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

siddjain picture siddjain  Β·  3Comments

wmaurer picture wmaurer  Β·  3Comments

fwanicka picture fwanicka  Β·  3Comments

seanzer picture seanzer  Β·  3Comments

uber5001 picture uber5001  Β·  3Comments