Skip to content

The compilerOptions.outDir config is incorrectly resolved when in a shareable config #29172

Description

TypeScript Version: 3.3.0-dev.20181222

Search Terms: outDir, output directory, outDir extends

Expected behavior:

TypeScript 3.2 got support for configuration inheritance via node_modules packages. I have created a package with a shareable config. In this shareable config, I have defined the outDir option: https://github.com/sindresorhus/tsconfig/blob/50b0ba611480ed45b97a59b910f5bb5e8cbc25ef/tsconfig.json#L2-L3 as I always use the same outDir and don't want to have to specify it in each project.

I expected the outDir path to be resolved against the project root, even when it's defined in an extended config.

Actual behavior:

It turns out the outDir relative path is actually resolved against the shareable config path instead of the project's root (tsconfig.json) path. So when I have a project foo, and compile TypeScript, the output ends up in foo/@sindresorhus/tsconfig/dist instead of foo/dist.

You can reproduce it by cloning https://github.com/sindresorhus/ow/tree/8ae048c4931dfd51b496cefb40b24c78d3722be6, then removing this line https://github.com/sindresorhus/ow/blob/8ae048c4931dfd51b496cefb40b24c78d3722be6/tsconfig.json#L4 (which is a workaround to the problem), and then run $ npm test. The compiled TS code will end up in node_modules/@sindresorhus/tsconfig/dist instead of dist.

Activity

  1. weswigham commented on Jan 2, 2019

    @weswigham
    Member

    Path-based compiler options (outDir, outFile, rootDir, include, files) are resolved from the config file they're found in - we thought this'd be more consistent when combining config files, especially when you have multiple configs within the same project, as paths always get resolved relative to the file they were written in (so you can safely write references to any path you want in a config file without worrying about if that config gets extend'd later on - its paths will continue to work).

    It would be horribly breaking to change this behavior now~

  2. added
    SuggestionAn idea for TypeScript
    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.
    on Jan 2, 2019
  3. chyzwar commented on Feb 3, 2019

    @chyzwar

    But this makes "extends" pretty useless. Without outDir you cannot use project references with extends. A The use case for extends is that I specify baseline options for the compiler but paths and references should be based on project settings. It should be possible overwrite paths or even individual options.

    @myscope/tsc-config/tsconfig.base.json

    {
      "compilerOptions": {
        "moduleResolution": "node",
        "target": "ES2018",
        "newLine": "lf",
        "jsx": "react",
        "strict": true,
        "allowSyntheticDefaultImports": false
    }

    @myscope/my-project/tsconfig.json

    {
      "extends": "@myscope/tsc-config/tsconfig.base.json",
      "compilerOptions": {
        "moduleResolution": "node",
        "target": "ES2018",
        "newLine": "lf",
        "jsx": "react",
         "outDir": "lib",
        "strict": true,
        "allowSyntheticDefaultImports": true
    }

    Resolved config:

    {
      "compilerOptions": {
        "moduleResolution": "node",
        "target": "ES2018",
        "newLine": "lf",
        "jsx": "react",
         "outDir": "@myscope/my-project/lib",
        "strict": true,
        "allowSyntheticDefaultImports": true
    }

    This should be a non-breaking change.

  4. mmmeff commented on Feb 22, 2019

    @mmmeff

    Just want to chime in, I'm really surprised these paths are resolving relatively. This really makes config extensions much less useful.

    I really just want to set my rootDir and outDir across all of my packages uniformly by extending a singular base configuration - there's no way to do that right now without defining rootDir and outDir in every single one of my packages.

    Took me a hot minute to find my build files inside node_modules/@myorg/shared-tsconfigs/dist... 😭

  5. mmmeff commented on Feb 22, 2019

    @mmmeff

    You're correct that this is a breaking change though. Everyone is setting their paths in shared configs with ../../ prefixing them. You can imagine what would happen if you made these paths start resolving from their downstream consumer's roots.

  6. ravenscar commented on Mar 4, 2019

    @ravenscar

    I think this is very confusing, I read the docs about extends and when I read:

    All relative paths found in the configuration file will be resolved relative to the configuration file they originated in.

    I took this at it's word that if I used a relative path such as

    {
      ...
      outDir: './src',
      ...
    }
    

    in the base configuration path would be resolved relative to the base configuration's path. The implication here is that non-relative paths are not relative to where they appear but rather the project wherever tsc is run, so I expect:

    {
      ...
      outDir: 'src',
      ...
    }
    

    to be relevant to the project.

    It seems however that src and ./src are both identical wrt extends which seems like a very unintuitive decision.

  7. mmmeff commented on Mar 7, 2019

    @mmmeff

    Since changing this behavior would be a breaking change and is impossible to make backwards compatible, could we maybe create new keys that resolve relatively?

    I vote srcDir (similar to nuxt) and distDir. From there deprecation notices can be added to users still using rootDir and outDir.

    I'm opposed to the idea of making them something explicit like relativeOutDir and relativeRootDir because I don't think the current behavior should be the default behavior - it's very confusing to anyone attempting to use extended configurations

  8. NoelAbrahams commented on Mar 25, 2019

    @NoelAbrahams

    The current implementation is rather unfortunate as it results in surprising behaviour. In addition to outDir, outFile, rootDir, include, exclude, files we now also have tsBuildInfoFile.

    Incidentally, in the MSBuild config inheritance implementation (Directory.build.props), the setting for <OutputPath>mypath</OutputPath> is absolute. Hence can be conveniently defined at the solution level.

    There is a proposal for a non-breaking implementation in #30163

  9. kirillgroshkov commented on Apr 20, 2019

    @kirillgroshkov

    I agree, got confused about it as well. Any plans to change this behaviour to support relative paths in shareable configs?

  10. MartinDoyleUK commented on Apr 25, 2019

    @MartinDoyleUK

    Rather than a breaking fix, couldn't one just handle placeholder variables, such as $PROJECT_DIR or $ROOT_DIR? So the outDir in my common config file could be "$PROJECT_DIR/lib"?

  11. alvis commented on Apr 27, 2019

    @alvis

    It's such a surprise that extends resolves path relatively to the source config rather than the inhering config.

    Consistency? YES definitely.
    Use case? NO Don't think so.

    As a workaround, here is my hack:

    1. link the source tsconfig.json to the same directory of target project e.g.
    $ ln -s node_modules/<tsconfig_config_pkg>/tsconfig.json tsconfig.base.json
    1. then make a tsconfig.json which extendstsconfig.base.json e.g.
    { "extends": "./tsconfig.base.json"}

    Would be great if suggestion such as #30163 can be accepted!!!

  12. ExE-Boss commented on May 12, 2019

    @ExE-Boss
    Contributor

    I ran into this while making pnpm use a shared config.

  13. 11 remaining items

  14. uglow commented on Jun 29, 2022

    @uglow

    As per Martin Doyle (@MartinDoyleUK) 's suggestion - which is similar to what Jest has done with <rootDir>, please add a $ROOT_DIR option to support this common use case.

  15. added a commit that references this issue on Nov 5, 2022
  16. revero-doug commented on Dec 29, 2022

    @revero-doug

    4 years and we still can't get one of the non-breaking solutions merged?

  17. mlewand commented on Aug 28, 2023

    @mlewand

    I just was making a new project in TS (pretty much the first time I'm doing a monorepo TS project from the scratch) and I also faced this issue. I was expecting paths to be resolved from the "final" tsconfig.json file.

    None of workarounds menitoned during the discussion works. I also have a feeling that adding root directory variable would be a viable solution to this - though discovery of it wouldn't be perfect.

    The only solution for me is to use:

    {
        "extends": "../tsconfig-package.json",
        "compilerOptions": {
            "outDir": "./dist", // Needed due to https://github.com/microsoft/TypeScript/issues/29172.
        }
    }

    Which is not ideal but still makes few lines to be reused.

  18. fabiospampinato commented on Aug 28, 2023

    @fabiospampinato

    None of workarounds menitoned during the discussion works.

    My workaround works and I use it daily. You need to install a dependency that exports a tsconfig.json, extend that in your tsconfig.json, and have the dependency automatically rewrite its tsconfig.json to point to the right paths absolutely.

    It's weird, but that works, unless you are using a package manger that isn't really installing things but just symlinking them or something.

  19. zanminkian commented on Aug 28, 2023

    @zanminkian

    I faced this issue. Assuming I have a monorepo.

    .
    ├── packages
    │   ├── my-pkg1
    │   │   ├── src
    │   │   ├── package.json
    │   │   └── tsconfig.build.json
    │   └── my-pkg2
    │       ├── src
    │       ├── package.json
    │       └── tsconfig.build.json
    ├── package.json
    └── tsconfig.json
    

    Every tsconfig.build.json extends the tsconfig.json in the project root.
    I have to write redundant configs in each tsconfig.build.json file:

    {
      "extends": "../../tsconfig",
      "include": ["src"],
      "exclude": ["**/*.spec.ts"],
      "compilerOptions": {
        "outDir": "dist"
      }
    }

    It's redundant and ugly.

  20. kyle-belle commented on Oct 14, 2023

    @kyle-belle

    This is honestly crazy. about five years later and we still don't have anything.

    even just an extra config property we could put in the base/shared config like outDirBaseUrlOverride or allowExtendOutDir would be a good simple fix which i cant imagine being a breaking change as its something that needs to be manually enabled

  21. maksnester commented on Feb 16, 2024

    @maksnester
  22. mmmeff commented on Feb 16, 2024

    @mmmeff
  23. RyanCavanaugh commented on Feb 16, 2024

    @RyanCavanaugh
    Member

    We're discussing options about this at #56436

  24. slorber commented on Apr 19, 2024

    @slorber

    Wow, great to know a solution has been implemented and merged!

    #58042

    As far as I understand it, soon we should be able to write a base config such as:

    // @fileName: tsconfig.base.json
    {
      "compilerOptions": {
        "rootDir": "${configDir}/src",
        "outDir": "${configDir}/lib",
        "tsBuildInfoFile": "${configDir}/lib/.tsbuildinfo",
      } 
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.SuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions