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

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

Closed
sindresorhus opened this issue Dec 27, 2018 · 30 comments
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript

Comments

@sindresorhus
Copy link

sindresorhus commented Dec 27, 2018

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.

@weswigham
Copy link
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~

@weswigham weswigham added Suggestion An idea for TypeScript Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Jan 2, 2019
@chyzwar
Copy link

chyzwar commented Feb 3, 2019

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.

@mmmeff
Copy link

mmmeff commented Feb 22, 2019

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... 😭

@mmmeff
Copy link

mmmeff commented Feb 22, 2019

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.

@ravenscar
Copy link

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.

@mmmeff
Copy link

mmmeff commented Mar 7, 2019

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

@NoelAbrahams
Copy link

NoelAbrahams commented Mar 25, 2019

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

@kirillgroshkov
Copy link

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

@MartinDoyleUK
Copy link

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"?

@alvis
Copy link

alvis commented Apr 27, 2019

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!!!

@ExE-Boss
Copy link
Contributor

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

realplatanopapi added a commit to realplatanopapi/tsconfig that referenced this issue May 21, 2019
mightyiam added a commit to mightyiam/tsconfigs that referenced this issue Jun 10, 2019
BREAKING CHANGE: Removed the following properties:
- `compilerOptions.rootDir`
- `compilerOptions.outDir`
- `include`
The reason for this removal is the following issue:
microsoft/TypeScript#29172

closes #53
@Bessonov
Copy link

Bessonov commented Sep 22, 2019

@weswigham what's about this comment from @MartinDoyleUK ?

Would love to see this solution.

@clehene
Copy link

clehene commented Oct 3, 2019

Not sure if related or different bug / feature, but I have (v 3.6.3):

    "baseUrl":"./src",
    "paths": {
      "common/*": ["../../common/src/*"]
    }

If I use the path in imports:

import logger from 'common/logger';

generated files will look like:

NOTE there's a copy of logger in server src

server/dist
├── common
│   └── src
│       ├── logger.js
│       └── logger.js.map
└── server
    └── src
        ├── csp.js
        ├── csp.js.map
        ├── index.js
        ├── index.js.map
        ├── logger.js
        ├── logger.js.map

without the import using the path config (but keeping the path config)

import logger from './logger';
server/dist
├── csp.js
├── csp.js.map
├── index.js
├── index.js.map
├── logger.js
├── logger.js.map

Full tsconfig

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": false,
    "noImplicitAny": false,
    "removeComments": true,
    "noLib": false,
    "allowSyntheticDefaultImports": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es6",
    "sourceMap": true,
    "allowJs": true,
    "outDir": "./dist",
    "skipLibCheck": true,
    "baseUrl":"./src",
    "paths": {
      "common/*": ["../../common/src/*"]
    }
  },
  "exclude": [
    "node_modules"
  ],
  "include": [
    "src/**/*"
  ]
}

build command

tsc -p tsconfig.json

@ilearnio
Copy link

ilearnio commented Dec 9, 2020

Just define "rootDir": "." together with outDir within your child module, this fixed the issue for me. Or remove rootDir from yoor root tsconfig.json

msalihaltun added a commit to iVis-at-Bilkent/pathway-mapper that referenced this issue Feb 1, 2021
@desmap
Copy link

desmap commented Mar 17, 2021

@weswigham any news on this?

I found @MartinDoyleUK's idea to intro $PROJECT_DIR or $ROOT_DIR vars quite smart and they aren't a breaking change.

@NaridaL
Copy link
Contributor

NaridaL commented Feb 26, 2022

Would it be possible to describe this behavior explicitly on https://www.typescriptlang.org/tsconfig#outDir ?

@fabiospampinato
Copy link

Just stumbled on this as well. This is so weird, the one thing I was sure extending another configuration would enable was using a single configuration for all the settings shared between projects. Like it's annoying to say to TS for every project to get the files from "src" and to put them in "dist".

@deanhiller
Copy link

I would love to see this closed/fixed so it just works. After trying quite a few monorepo setups in typescript(I come from java-land where it was a much easier task), this monorepo setup below seemed the best but you run into this github issue.

https://blog.ah.technology/a-guide-through-the-wild-wild-west-of-setting-up-a-mono-repo-with-typescript-lerna-and-yarn-ed6a1e5467a

agilgur5 added a commit to agilgur5/tsconfig that referenced this issue May 20, 2022
- base, library, and library-build, same as the package exports
  - initially started with library, but if I wanted to use this repo
    for apps as well, I wouldn't want some configurations like
    declarations or declaration maps as they're unused by apps
    - so split off library and base
  - library-build doesn't add much and is for a particular use-case, so
    I also may end up removing that at some point

- NOTE: `extends` resolves relative paths based on the location of the
  _extended_ config file, not the one doing the extension
  - https://www.typescriptlang.org/tsconfig#extends
  - This effectively means that all "path-based compiler options" like
    `outDir`, `outFile`, `rootDir`, `include`, `exclude`, and `files`
    have to be repeated in all tsconfigs
    - c.f. microsoft/TypeScript#29172,
      microsoft/TypeScript#25430
    - so the usage of them here is more of as an "example" of sorts of
      what should be repeated, as installing from NPM would result in
      the paths here being _inside_ `node_modules` (or elsewhere pending
      the NPM client you use)
alexiglesias93 added a commit to finsweet/tsconfig that referenced this issue Jun 6, 2022
agilgur5 added a commit to agilgur5/ts-library-base that referenced this issue Jun 14, 2022
Basically same as agilgur5/react-signature-canvas@cc6cce9

- this is my own tsconfig base (https://github.com/agilgur5/tsconfig) built on top of @tsconfig/strictest (https://github.com/tsconfig/bases)
  - as you can see, it just reduces all the non-type-checking related config that @tsconfig/strictest doesn't cover

- TS doesn't yet support package `exports` for tsconfigs yet (microsoft/TypeScript#48665), hence the longer path into the package

- Relative paths in tsconfig bases are also relative _within_ `node_modules` (microsoft/TypeScript#29172), so have to repeat any relative paths here
  - And due to this, I think it's better right now for `build` to extend the base here instead of the `build` tsconfig in `@agilgur5/tsconfig`
    - I didn't fully think that one through when I made it tbh, though it still serves as a good example tsconfig
    - Maybe that will be different in the future if TS changes or otherwise comes up with a solution to this (see the issue)
@uglow
Copy link

uglow commented Jun 29, 2022

As per @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.

opichon added a commit to dzangolab/vue that referenced this issue Nov 5, 2022
Paths in tsconfig are relative to the config fine in which they are declared.
So any path declared in `packages/tsconfig/base.json` is relative to that directory.
This applies in particular to `outDir` and `include`. These have to be redeclared
in each `tsconfig.json` file in each library.
See microsoft/TypeScript#29172
@revero-doug
Copy link

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

@mlewand
Copy link

mlewand commented Aug 28, 2023

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.

@fabiospampinato
Copy link

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.

@zanminkian
Copy link

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.

@kyle-belle
Copy link

kyle-belle commented Oct 14, 2023

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

@maksnester

This comment was marked as off-topic.

@mmmeff

This comment was marked as off-topic.

@RyanCavanaugh
Copy link
Member

We're discussing options about this at #56436

@slorber
Copy link

slorber commented Apr 19, 2024

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",
  } 
}

Gnuxie added a commit to Gnuxie/tsconfig that referenced this issue Apr 20, 2024
until microsoft/TypeScript#29172 makes its way into a release at least
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests