Typescript: tsc should not care and fail with ts files outside of rootDir

Created on 21 Jul 2016  ·  87Comments  ·  Source: microsoft/TypeScript

TypeScript: 2.0.0

I have a fixtures folder which contains .ts files. In my tsconfig.json I set rootDir to src which does contain all my source code (including test).

The fixtures files are there for test cases. tsc shouldn't complain their existence and fail the compilation.

Working as Intended

Most helpful comment

Bleh, this is really working as intended? I just ran into this, I told typescript that my source was all in a particular folder (via rootDir) and then it proceeded to _completely ignore what I told it_ because I happened to have a single file with a .ts extension sitting outside of that folder.

This really feels like an error, if I tell the compiler where my source is via rootDir, it shouldn't completely disregard that setting and change my output folder structure because it thinks maybe I did something wrong. It should just ignore any files that are not in rootDir. Alternatively, it should hard fail, not continue to emit output that doesn't align with my desired output folder structure at all!


For anyone coming after me, the following will do what you _actually_ want:

{
    "compilerOptions": {
        "outDir": "./output",
        "rootDir": "./source",
    },
    "include": [
        "source"
    ]
}

All 87 comments

To get around, I add "exclude" back to my tsconfig.json

the rootDir is used to build the output folder structure. all files have to be under rootDir for the compiler to know where to write the output. without this constraint, if there are files that are not under rootDir, the compiler has two options 1. either they will be emitted in a totally unexpected place, or 2. not emitted at all. i would say an error message is much better than a silent failure.

@mhegazy Thanks for clarification. By name rootDir I took it as the root dir of source code, much like baseURL for browser. Didn't know it is only for output folder structure.

Here is my tsconfig.json:

{
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "moduleResolution": "node",
        "sourceMap": true,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "removeComments": true,
        "noImplicitAny": true,
        "rootDir": "src",
        "outDir": "build"
    },
    "exclude": [
        ".idea",
        "build",
        "node_modules",
        "typings/globals",
        "typings/modules"
    ]
}

My folder structure looks like this:

  • project

    • build

    • node_modules (symlink to ../node_modules)

    • node_modules

    • src

I want my .ts files in the src folder to be compiled and output with the same folder structures to the build folder. I do this so that I can set my document root to the build folder and keep my src out of the doc root. I'm getting error messages about .ts files in the node_modules folder, same error messages as OP. Note above, I have node_modules excluded. Why is tsc complaining about those?

@HillTravis your issue seems to be the same as https://github.com/Microsoft/TypeScript/issues/9552.

@mhegazy I'm not sure it's the same. Yes, my structure includes a symlink, but anyplace where that symlink exists should be ignored.

In #9552, there is no src folder, meaning the node_modules folder exists in the path that is being built. In my scenario, the root directory that tsc should be looking at is src, which does not contain node_modules. So it shouldn't be looking at that folder at all. On top of that, I have the node_modules folder explicitly excluded via my tsconfig.json. So it should be doubly ignored. I also have the build folder excluded, and that should cover the symlink to node_modules inside it.

Also, in #9552, there was no mention of the error messages or failed compilation.

I should also mention that I'm using TypeScript 1.8.10.

Specifically, the error messages read:

error TS6059: File '/Users/thill/Projects/nrc/client/node_modules/ng2-datetime/src/ng2-datetime/ng2-datetime.module.ts' is not under 'rootDir' '/Users/thill/Projects/nrc/client/src'. 'rootDir' is expected to contain all source files.

error TS6059: File '/Users/thill/Projects/nrc/client/node_modules/ng2-datetime/src/ng2-datetime/ng2-datetime.ts' is not under 'rootDir' '/Users/thill/Projects/nrc/client/src'. 'rootDir' is expected to contain all source files.

error TS6059: File '/Users/thill/Projects/nrc/client/node_modules/ng2-datetime/src/ng2-datetime/timepicker-event-interface.ts' is not under 'rootDir' '/Users/thill/Projects/nrc/client/src'. 'rootDir' is expected to contain all source files.

@HillTravis you can trust me on this one :) it is our inconsistent handling of symlinks that throws the compiler off. we follow symlinks only for node module resolution, then use that name as the file name; then the we perform a check to make sure all files are under the source directory using the real path, instead of the user specified path. and that is what generates the error.

As a test, I tried removing the symlink. Actually, I deleted the whole build folder. Then I ran tsc again, and still saw the error.

During more testing, I tried commenting out the in-code import and uses of that particular node module (ng2-datetime) and the error went away. So it's only when I attempt to import that node module that I see the error, regardless of symlink.

If you look at the source for that package on GitHub, you'll see that the package.json file has the "main" parameter set to "ng2-datetime.js", although the author also published the .ts file with the same name. This seems to be causing the issue. If I create .d.ts files and delete the .ts files, the issue goes away. It compiled with no issues even after adding the symlink back.

So with all respect, I don't see how this issue could be related to the symlink specifically. It seems to just generally cause issues when the author includes the .ts files in the package.

Do you know if there is any workaround for this? The OP above said the worked around it by adding "exclude" back to the tsconfig.json. I already have the directory excluded, but it's still trying to compile it.

Do you know if there is any workaround for this? The OP above said the worked around it by adding "exclude" back to the tsconfig.json. I already have the directory excluded, but it's still trying to compile it.

This may be related to another, but I can't find it right now. When resolving for a type, tsc always try to look it up from node_modules, while it is not there (related to typings).

"tsc should not care and fail with ts files outside of rootDir" I agree.

I cannot understand why typescript would be looking at JS files outside the rootDir, since that's where all the source should be.
Error message says "'rootDir' is expected to contain all source files", then anything outside that directory should not be expected or assumed to be a source file, as far as TS is concerned!

If this is "Working as Intended", then what exactly is the intent or motivation of this behavior? Is there a case where TS would want to compile source files located outside the rootDir?

On the other hand, adding an "include" section in tsconfig solves the problem. When provided with files or directories to include, TS does not go looking at other files outside that.

with typescript 1.8.10

I had a node_modules folder in my homedir ~/node_modules. Running tsc from my project dir ~/projects/myProject/ caused typescript to complain about the following files

../../node_modules/aws-sdk/index.d.ts(7,1): error TS1128: Declaration or statement expected.
../../node_modules/aws-sdk/index.d.ts(7,11): error TS1005: ';' expected.
../../node_modules/aws-sdk/index.d.ts(7,24): error TS1005: '{' expected.
../../node_modules/aws-sdk/lib/config.d.ts(1,1): error TS1084: Invalid 'reference' directive syntax.
../../node_modules/aws-sdk/lib/config.d.ts(29,84): error TS1110: Type expected.
../../node_modules/aws-sdk/lib/config.d.ts(36,62): error TS1110: Type expected.
../../node_modules/aws-sdk/lib/config.d.ts(68,133): error TS1110: Type expected.
../../node_modules/aws-sdk/lib/config.d.ts(75,111): error TS1110: Type expected.
../../node_modules/aws-sdk/lib/request.d.ts(1,1): error TS1084: Invalid 'reference' directive syntax.
../../node_modules/aws-sdk/lib/services/glacier.d.ts(1,1): error TS1084: Invalid 'reference' directive syntax.

very confusing - although not sure why I had a node modules folder in my home dir to begin with.

@iwllyu can you check your project on [email protected] and file a new issue if you are still having trouble.

Still have the issue with 2.1.4 although it complains about a different set of files

Using tsc v1.8.10
../../node_modules/aws-sdk/index.d.ts(7,1): error TS1128: Declaration or statement expected.
../../node_modules/aws-sdk/index.d.ts(7,11): error TS1005: ';' expected.
../../node_modules/aws-sdk/index.d.ts(7,24): error TS1005: '{' expected.
../../node_modules/aws-sdk/lib/config.d.ts(27,84): error TS1110: Type expected.
../../node_modules/aws-sdk/lib/config.d.ts(34,62): error TS1110: Type expected.
../../node_modules/aws-sdk/lib/config.d.ts(66,133): error TS1110: Type expected.
../../node_modules/aws-sdk/lib/config.d.ts(73,111): error TS1110: Type expected.
Using tsc v2.1.4
../../node_modules/aws-sdk/lib/config.d.ts(38,37): error TS2304: Cannot find name 'Promise'.
../../node_modules/aws-sdk/lib/request.d.ts(164,16): error TS2304: Cannot find name 'Promise'.
../../node_modules/aws-sdk/lib/s3/managed_upload.d.ts(15,16): error TS2304: Cannot find name 'Promise'.

I'll create a project to isolate the test case

Well I tried with a fresh project and it wasn't happening. Since this is an older project and I already fixed it by removing the extra node_modules folder, I'm not gonna spend any more time trying to recreate it. Thanks for the help!

About this case. If all you import are types (like I'm really surfing on this "front-typescript, back-typescript" wave thing) from outside - maybe tsc can distinguish what I'm doing?
I'm sharing just types of my entities from backend - to my frontend application. Output of course without types and imports of those classes (because they are used only for compile time and intellisense).

Had this as well.
Typescript is too constraining for our team - considering flow now.

guys, any resolution on this now? using types from client code inside backend code and otherwise.

what's the best option? copy-paste there and there and modify if I change types?

making one root dir for 2 projects is not an option, since why backend tsconfig.json will have info on jsx and compile into commonjs when I can use es2015

Does baseUrl work for you?

@unional won't changing baseUrl break all of node_modules imports - requiring to write not

import * as request from "superagent";

but

import * as request from "node_modules/superagent/..something";

?

Bleh, this is really working as intended? I just ran into this, I told typescript that my source was all in a particular folder (via rootDir) and then it proceeded to _completely ignore what I told it_ because I happened to have a single file with a .ts extension sitting outside of that folder.

This really feels like an error, if I tell the compiler where my source is via rootDir, it shouldn't completely disregard that setting and change my output folder structure because it thinks maybe I did something wrong. It should just ignore any files that are not in rootDir. Alternatively, it should hard fail, not continue to emit output that doesn't align with my desired output folder structure at all!


For anyone coming after me, the following will do what you _actually_ want:

{
    "compilerOptions": {
        "outDir": "./output",
        "rootDir": "./source",
    },
    "include": [
        "source"
    ]
}

I told typescript that my source was all in a particular folder (via rootDir)

This is not what rootDir means! rootDir means "Use this folder as the relative basis for computing the paths in outDir". Which files are part of your compilation is a separate question that is answered by files and/or include/exclude

Even if I accept that, the current behavior is still in error IMO. If I have any TS files outside of the specified directory then rootDir will _not_ be the basis for computing the paths in outDir, meaning the configuration I specified will be completely ignored.

I think hard failing would be much better because the compiler effectively thinks I have given an invalid configuration. Ignoring configuration when it is invalid is (IMO) not the right way to handle this class of failure.

Example error message that would be far more clear (along with hard failure):

TSC found typescript files outside of rootDir and therefore cannot proceed with compilation. Either change rootDir to include all typescript files, or exclude files outside rootDir, or include only files inside rootDir.

Note: A big part of the reason the current behavior of rootDir feels very wrong is precisely because it doesn't make sense to have files outside rootDir. When one asks oneself (or the compiler) what does it mean to compile files outside rootDir the only answer is "it doesn't make sense". Therefore one comes to the natural conclusion that rootDir specifies sourceDir as well.

The evil is in the naming. It really means relativePathBaseDir or something like that.

I have also noticed that include normally means "look here as well" but in this case it means "only look here".
I would be sensible for it to have another name and be mandatory.

IMO this comment should be especially taken into account to confirm that this is indeed a bug and should be re-opened as such:

Note: A big part of the reason the current behavior of rootDir feels very wrong is precisely because it doesn't make sense to have files outside rootDir. When one asks oneself (or the compiler) what does it mean to compile files outside rootDir the only answer is "it doesn't make sense".

Indeed, it does not make sense. "Source files outside rootDir" simply are not source files, no matter their file extension or content. The compiler will never compile them. So why does he complain about their existence? Is he jealous that there are files not under his rule?

+100 on this being a bug (a nasty one actually). Consider following directory structure:

- src/
  - module.ts
- test/
  - module.test.ts
- out/
  - module.js

I would like tsc to compile only the src, because I run tests with ts-node. Having rootDir: 'src' will cause compiling error currently. If you mark tests and other stuff that you only intend to execute with ts-node (or even compile separately maybe?) as "excluded", then bad things start to happen (e.g. vscode highlighting becomes broken in tests)

In fact, no combination of rootDir, excluded and all other options satisfies all requirements (exclude tests from compilation while still being able to run them and to work with them).

And I don't really think my use case is unique, so it's worth reconsidering the "work as intended" label at least from UX point of view.

The src / test / out setup was a key scenario addressed by project references

I found this issue because I was googling why typescript was trying to compile my webpack.config.js file after I set rootDir.

I told typescript that my source was all in a particular folder (via rootDir)

This is not what rootDir means! rootDir means "Use this folder as the relative basis for computing the paths in outDir". Which files are part of your compilation is a separate question that is answered by files and/or include/exclude

This is the answer! The docs for rootDir say

Specifies the root directory of input files

Which I took to mean "input _source_ files". I recommend that @RyanCavanaugh's comment replace the current explanation for this option.

Linking here - https://github.com/Microsoft/TypeScript/issues/11299 is a duplicate of this issue. (Solution: use include/exclude to specify files to compile, rootDir does NOT specify which files to compile.)

@mhegazy Would you add a comment on that thread to this effect? That appears to be what the asker is referring to (confused about why TS is looking at any files outside of RootDir, same misunderstanding as to what rootDir means).

I am also facing this issue and have been unable to resolve it.

tsc compilation is walking the tree out of my included directory and into parent folders, causing duplicate type errors.

I have a nested folder structure which looks like this:

└─src
└─package.json
└─node_modules
└─tsconfig.json
└─example
│ └─src
│ └─package.json
│ └─node_modules
│ └─tsconfig.json

Now when i try and run tsc from within the example directory, i get the following error:

node_modules/@types/react/index.d.ts:2809:14 - error TS2300: Duplicate identifier 'LibraryManagedAttributes'.

2809         type LibraryManagedAttributes<C, P> = C extends React.MemoExoticComponent<infer T> | React.LazyExoticComponent<infer T>
                  ~~~~~~~~~~~~~~~~~~~~~~~~

  ../node_modules/@types/react/index.d.ts:2814:14
    2814         type LibraryManagedAttributes<C, P> = C extends React.MemoExoticComponent<infer T> | React.LazyExoticComponent<infer T>
                      ~~~~~~~~~~~~~~~~~~~~~~~~
    'LibraryManagedAttributes' was also declared here.

As you can see by the error, tsc is walking the tree up and out of the example folder, and looking in the node_modules folder of the parent directory! What feels even worse is that i have tried explicitely ignoring the parent node_modules directory with little effect.

Here is the tsconfig of the example diretory:

{
  "compilerOptions": {
    "target": "es5",
    "experimentalDecorators": true,
    "module": "commonjs",
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "moduleResolution": "node",
    "lib": ["es2017", "dom"],
    "jsx": "react"
  }
,
  "typeRoots": [
    "./node_modules/@types"
  ],
  "include": [
    "src"
  ],
  "exclude": [
    "node_modules",
    "../node_modules"
  ]
}

How can i solve this issue? None of the above seems to work out for me

@lexwebb I had the same issue, check in your yarn.lock files that @types/react have the same versions.
See https://stackoverflow.com/questions/52399839/typescript-duplicate-identifier-librarymanagedattributes.

I landed here because my assets folder contains an "MPEG Transport Stream" video file with extension .ts so it made tsc crash even if the folder wasn't in my rootDir path 😄

I fixed it by adding the assets folder in the exclude paths but it was a disturbing behaviour 🤔

So it turns out i had an ever so slightly different version of react in both the parent and the child package json. Matching the versions exactly removed the issue. Still a pain that you cannot exclude the parent directories form type checking.

I'm guessing this is never going to be fixed. Just ran into the same issue. How this "works as intended" is beyond me. I guess the intention was to make the compiler run in a nonsensical manner.

From my own personal experience the reason a typescript build wanders off out of your rootDir tree and tries to build stuff you don't expect is the fact the you have (deliberately or inadvertently) directly or indirectly referenced something in that area from within your rootDir. If you do that then the build will follow the reference tree regardless of whether or not you have tried to exclude the area containing the referenced file. Sometimes it can be a PITA trying to find out how/where you have done it - but invariably I find it is my own fault - not the fault of tsc. It is merely doing what I have told it to do.

Whenever this hits me it is because of types. I import an interface or something that lives in a file that imports something else that lives in a file that imports something else......until eventually I end up importing something that lives in a file outside my rootDir and very often in something I am trying to explicitly exclude in my config.

HTH some of your get to the root of your pain. No amount of config will get you out of it - you just have to keep strict control of your import structure.

@kpturner That sounds super reasonable, but — a barebones test project with no cross-folder imports will produce the behavior everyone is complaining about.

Really? That I’ve never seen :)

Excluded folders are always excluded in all my projects except where the circumstances to which I allude are present.

Are there bare bones examples on this thread? Difficult to tell from my phone.

Apologies, exclusion is not part of the 'bare bones' situation I refer to. I'm just noting that imports do not appear to be a cause of the behavior (though it would make more sense if that was the case).

i.e. compiling something like this will result in an error.

-src / src.ts <- no imports -test / test.ts -out -tsconfig.json {"rootDir": "src", "outDir": "out"}

But if your tsconfig.json looks like that I would expect typescript to compile everything in the src directory and everything in the test directory. If you only want it to transpile only stuff in src and output to the out directory then your tsconfig.json would have to be

{
    "compilerOptions": {
        "rootDir": "src",
        "outDir": "out"
    }
}

Then it would throw an TS6059 error because you have some files outside of the rootDir. So you then have to exclude those:

{
    "compilerOptions": {
        "rootDir": "src",
        "outDir": "out"
    },
    "exclude": [
        "test"
    ]
}

which, when transpiled, will generate a folder called out with subfolder of src containing src.js. The test folder is ignored completely. This is what I would expect to happen - no?

Er, indeed, my example tsconfig should've included rootDir and outDir under compilerOptions as you show in your first example. You are also correct that when you exclude test, test and its files are excluded. Originally, I thought you were saying that a (or the) culprit of the TS6059 error is a file in rootDir importing something from outside rootDir—though I think I misread you. Regardless, I'm in agreement with others here in being surprised that directories outside of rootDir must be explicitly excluded as you describe.

No I was (previously) saying that the reference tree can result in things being transpiled in your excluded list - slightly different.

The TS6059 error is a little disconcerting initially - but I see it as TS trying to help prevent me from making mistakes. If I’ve gone to the trouble of defining a rootDir and then plonk typescript flies outside of that rootDir then TS is effectively saying “oi - you’ve created typescript that I can’t get to - is that right?”

I either answer “no it’s not right I messed up” and move the files into rootDir
OR
I say “yes it is right and I’m sorry I didn’t let you know” - and then I add it to the list of excluded files.

This is way too complicated. I have a monorepo and I can't seem to find a way to properly compile each package for itself, emitting the correct directory structure, while importing local packages (from the same monorepo) using the paths option.

Either the directory structure in the output directory contains unwanted parent folders or tsc complains about files being outside the root directory.

Can't be this complicated right?

No matter which options in whatever combination I use, tsc seems not to be able to handle a monorepo with paths referencing local packages in the same monorepo. I tried --build, --project, references, paths, extends, rootDir, rootDirs, include, exclude, baseUrl, outDir in all kind of combinations with different values and in the end it complains about files not being within the rootDir or it screws up by emitting a wrong directory structure inside outDir.

I would not describe what I have as a monorepo but I do have three different projects in the same repo - and I can transpile each independently without issue or interference from the others. Each project has its own tsconfig

After quite some time trying and tinkering I was able to get a monorepo with lerna and typescript to run. The typescript configuration uses references in combination with paths and doesn't choke anymore on using rootDir.

the rootDir is used to build the output folder structure. all files have to be under rootDir for the compiler to know where to write the output.

What about files that don't require any output? Having a json file outside root:

const errors = require("../../../../errors.json");

Because I can't compile the ES6 version:

import * as errors from "../../../../errors.json";

However, with the first version, I'm losing type safety.

@mhegazy This issue was closed 3 years ago. However it seems that it's still a source of confusion 3 years later.

Could you please revisit this "Working as Intended" status? Given the reaction of the community in this thread, it's pretty safe to say that it is not behaving as the community expects it to behave. Whether it's a documentation issue, renaming that option, changing its behavior, or adding a new option, I believe there is actually an action item on your side to be determined.

I can understand that you have way higher priorities than this one, but keeping this issue closed without any sign that the feedback given here has ever been valued doesn't really convey a very good image for Typescript to be honest.

@ngryman What next step do we have? rootDir has a definition - this is the common root of my source files. When a file isn't in that common root, by the very definition of rootDir, that is an error. That definition is documented. I can't do anything about the fact that people have invented their own definitions for rootDir and are subsequently surprised.

This is like going to Starbucks and saying that asking for a latte shouldn't result in getting a mixture of espresso and steamed milk. It's unactionable because you're arguing with a definition. If you want some different drink, order something else. If Starbucks isn't offering the kind of drink you want, make a suggestion for what that drink should be and what it should be made out of.

As I mentioned in https://github.com/microsoft/TypeScript/issues/9858#issuecomment-370653478, I think the solution to this problem is simply better error messaging. Back when I ran into it, and eventually found this GitHub issue, my biggest frustration came from how much time I wasted trying to figure out the problem, and then thinking it was a compiler bug, and then creating a repro case, and then finding a duplicate bug that says "working as intended". Failing fast with a clear error message would have saved me all of that.

If there are source files outside of rootDir then it means the TypeScript project is configured incorrectly. Giving the user a clear message indicating this along with guidance on how to fix it I think would resolve the issue for a lot of people.

@RyanCavanaugh Thanks for your quick answer.

If I go to this page: https://www.typescriptlang.org/docs/handbook/compiler-options.html. I read the following definition:

Specifies the root directory of input files. Only use to control the output directory structure with --outDir.

I'm literally understanding that it's "only used to control the output directory structure" and that's it. The "only use" is important here. How could I even guess that it's not the only thing that the compiler is doing in reality, but that it will also emit errors if I have Typescript files outside of that directory.

Perhaps I'm missing something, but if that's the case, I'm far from being the only one. So if you have a product, and a good amount of your users don't understand your product, there are only 2 paths to take: either you consider your users stupid, or either you failed to communicate something clearly. I hope you're not leaning toward the first 😕

To finish with your Starbuck example, well write "mixture of expresso & steamed milk" in the ingredients list, so I know what a latte is, and I don't have to guess.

The implication of a file outside the rootDir would be that TS would output a file outside the outDir, which I think is a self-evidently bad behavior.

The error message could be better - I'd love a suggestion or a PR for that.

The docs could always be better - again, would love concrete ideas there on how to communicate things more clearly.

Insinuating that I'm calling people stupid isn't something I'd like any more of.

The implication of a file outside the rootDir would be that TS would output a file outside the outDir, which I think is a self-evidently bad behavior.

I'm still very unclear about why the compiler wouldn't just "chroot" to rootDir and don't consider files outside of that directory. As a user, probably lacking context about tsc internals and history, this is definitely not a predictable behavior to me.

One example that I can give out of my head is Jest which offers the same option: https://jestjs.io/docs/en/configuration#rootdir-string. AFAIK Jest doesn't consider test files outside of rootDir. I would expect the same behavior here for example.

The docs could always be better - again, would love concrete ideas there on how to communicate things more clearly.

I'm glad that you are considering improvements here. The suggestion of @MicahZoltu could be interesting to explore.

On my end, I can restate that I don't think rootDir is predictable (hence people reaction), but as I understand it, changing the behavior of that option is not something to be considered. So as a compromise one thing I could also propose would be to rename rootDir to a more meaningful name, outBaseDir could be a candidate. But there is probably a be a better name.

Insinuating that I'm calling people stupid isn't something I'd like any more of.

I did not insinuate anything and I'm sorry if you took it that way. I only stated a very simple fact: when users complain about something they don't understand, there are effectively only 2 paths to take. I concluded that I hope you were not leaning to the first one. The fact that you associate yourself with one of these paths is honestly left to you.

I think I've said what I had to say, especially on giving candid feedback about how this thread was handled. So I'll leave room for others to propose solutions.

@ngryman I agree that the definition of rootDir and what it's actually doing do not seem to line up. When you create a new ts project (via tsc --init) the comment in tsconfig.json for rootDir is similar to what you quoted /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */. If I supply a directory of _input_ files, I expect the only ones that it is even going to consider compiling are in that directory, by the definition of input.

@RyanCavanaugh It may have made sense to report an error like this 4 years ago when typescript was only used to be compiled into javascript. Of course I don't want missing files! But now with the popularity of typescript execution, like ts-node, it seems like tsc is being a little jealous to me.

And the idea that typescript files outside of the rootDir implies an incorrect project configuration is simply wrong. I don't need my unit tests, which are written in typescript, to be compiled to javascript. ts-node is simply faster and I get to enjoy all the benefits of typescript when writing tests.

I agree with @CodySchrank . I'm ok with having TS erroring out when some source file inside the rootDir references/imports a file outside of it - that is to be expected.

However, I can't understand TypeScript to by itself look through unrelated files outside of rootDir and erroring out while those are simply configuration files for different tools, like jest or whatever. I mean, what's the point? These are not even parts of my application and not referenced/imported anywhere directly, and my God, why would I event want them to be bundled into the resulting build in the first place...

I wonder what the implications of turning this error into a warning would be. Having briefly skimmed through the code it certainly seems possible but I'm definitely no expert on the tsc build process. It seems like a happy medium between keeping us notified about a potential problem but also letting us bypass it if we want.

The src / test / out setup was a key scenario addressed by project references

This requires at the least a separate project config for each root folder (https://www.typescriptlang.org/docs/handbook/project-references.html). Having read through the instructions, I came away without a good understanding of how to implement the pattern and I feel like I'm being handed a jackhammer when what I requested was a flyswatter.

Sure, maintaining adjunct modules as separate sub-projects within my source folder is more efficient, but I'm not compiling lodash here. What I want is to include many files, like test and benchmark files, under a common project folder for static analysis, and when it comes time to compile, only emit a single directory for use by the outside world. This seems easy enough to specify and I'm struggling to see why it is not only difficult for the user to do, but apparently _verboten_ from the perspective of the TS spec.

What I want to emphasize is that the only reason this feature is disallowed is due to the compiler's assumption of corrupt intent on the part of the developer. When the developer sets the "outDir" to ./src and the compiler finds a TS file in ./test, it _could_ assume that this file does not need to be emitted (assuming of course that it's not referenced from any file in "outDir").

But it doesn't. It assumes that the developer _wanted_ to emit this file, and by incompetence or malice specified a subdirectory that does not contain it. In other words, the compiler has the opportunity to match the developer's instructions to a common and reasonable use case, and instead matches it to something absurd and refuses to proceed.

As it stands, I don't even see what the legitimate use case for "outDir" is. I'm assuming it's to support a project structure where the tsconfig.json is at the top level along with other configuration and metadata files, and the owner wants the compiled folder to represent only the source folder that's nested inside it. (Just make sure none of those meta-files end with .ts: that would be absurd.)

It seems to me that the design of the spec is user-antagonistic. There was a reference made to ordering a drink at Starbucks and being upset when it wasn't what you expected, despite the ingredients being listed on the menu. This is apt as far as it goes, but I would liken this feature more to a hot dog stand that does not sell hot dogs.

Sure, the irritated customers should have gone round back and saw the big sign boldly proclaiming that the hot dog stand does not sell hot dogs. But as the confusion accumulates, there might be an opportunity for owners of the hot dog stand to reflect on how they are communicating the capabilities of their product to customers.

Hmm, considering this more, I wonder if there's a kind of invariant on the part of the core team that underlies this problem:

All source code must be compiled. If it is not compiled, it is not source code.

In userland, there is a need to get past this somehow. I don't know whether we do this by extending the definition of source code viz. TypeScript, or by creating a second category (“supporting code”, “validation code”, “peripheral code”...). This would be code that is expected to operate under the same type definitions as the source, and which must successfully validate for the source to be considered correct, but which is not part of the program's actual functionality with respect to its interface.

As a consumer of TypeScript I lack an appreciation for the specific difficulties of implementing such a category. However, it seems like it would fit right in with "outDir": anything inside "outDir" is part of the source, anything outside of it but inside the project folder is part of the supporting code. The main thing that makes this useful/necessary is the advent of ts-node, which means I can execute this supporting code as a separate step, completely independent of compilation; and of course I want to be able to take advantage of that.

not implicitly included an any input files provided to the tool

This problem is caused exactly _because_ the user is implicitly trying to compile files outside of the rootDir. You basically told the compiler, "only compile files in this directory" and then you proceeded to reference files outside of that directory. The compiler is confused and doesn't know how to proceed.


/project/source/index.ts

import '../external.ts'

/project/tsconfig.json

{
  "compilerOptions": {
    "rootDir": "./source"
  }
}

In this example, you tell the compiler "source files are only in the 'source' directory". Then one of those files references a file that is _not_ in the source directory. TypeScript now has two conflicting instructions, one says to only compile files in the source directory, the other says to compile files outside of the source directory.

@MattiasMartens you're implicitly conflating include and rootDir. If you have /src and /test, if include is src/* then you can't get errors about things in /test unless something from src incorrectly imported something from test, which you should be happy about!

It is not user-hostile or antagonistic to provide a mechanism for writing down what an incorrect import in your program looks like and to issue an error when such an incorrect import occurs. Again I think this is coming from a place of misunderstanding what the flags mean - they are primarily opt-in enforcement checks to help you validate that your program is configured correctly.


Anyway!

Given the definition of rootDir, when TypeScript sees a file outside the provided rootDir, it has some options:

  • Emit a file above the outDir (assuming this is even possible!), which contradicts the user's instruction about the output location
  • Process the file but don't emit it, which absent other build steps will produce a non-working program
  • Issue an error because the project appears to be misconfigured

I strongly object to the notion that emit a non-working program should be the default. If the feature request here is simply to treat .ts files found outside the rootDir as .d.ts, that's reasonable, it is just a separate thing from rootDir meaning something other than rootDir. What would you call such a flag?

@MattiasMartens you're implicitly conflating include and rootDir. If you have /src and /test, if include is src/* then you can't get errors about things in /test unless something from src incorrectly imported something from test, which you should be happy about!

That's true. I futzed about with include and found that it works better than I thought for this case, including with VSCode's tooling.

I would still like to have code that is validated on compile but not emitted on compile, which is something that include doesn't cover. But that is a separate request that needn't be discussed here.

I think this is coming from a place of misunderstanding what the flags _mean_ - they are primarily _opt-in enforcement checks_ to help you validate that your program is configured correctly.

I'm confused by this. Do you consider rootDir an opt-in enforcement check? It affects what eventually gets emitted so it is clearly not _only_ an enforcement check.

Some flags, like target, drastically change what will be emitted -- they're not enforcement checks but compiler instructions. rootDir reads like, and is documented like, a compiler instruction.

I strongly object to the notion that _emit a non-working program_ should be the default.

From what I've read of this thread so far there seems to be a broad consensus that a reference from a file inside of rootDir to a file outside of rootDir _should be an error_. The aspect of the feature which I and others have found frustrating is that it does not stop there, but in fact throws an error if it finds any TS files in the project folder outside of rootDir whether a file in rootDir imports them or not.

It bears repeating: I set rootDir to the folder where my source code resides, the compiler notices other TS code in the project that has nothing to do with what's in rootDir, and it halts! This specific behaviour does not help me understand my code. It is simply annoying.

It is true that from the perspective of the TypeScript definition of rootDir, as elaborated in this thread, the behaviour is perfectly reasonable, and everyone who has come here to express their frustration about it is in error. But it is not reasonable from the conventional, expected definition of a root directory. I put it to you that in virtually any context where I provide a root directory, I am telling the program where to begin traversing the file structure, not asking it if it's absolutely sure there isn't something else in the filesystem I might also be interested in.

Definitions can be wrong. They can be changed. For backwards compatibility, it is not necessary to retain definitions, only functionality.

If the feature request here is simply to treat .ts files found outside the rootDir as .d.ts

I... don't think I want that, for pretty much the same reasons that I want the compiler to throw an error when something in rootDir imports code from outside of it. I do think the contents of a rootDir if I provide one should be encapsulated from the surrounding code.

I think the simplest way to avoid the behaviour I and others do not want is to implicitly exclude files outside of rootDir if the files in rootDir don't refer to them. If the files in rootDir do refer to them, an error should be thrown, as it is currently.

Every paragraph there seems to boil down to "rootDir should change the default value of include", which would be a reasonable thing to suggest if we were adding those flags today at the same time, but isn't a build-breaking change we could make at this time. Though arguably making all the configuration flags interact with each other more increases cognitive load rather than decreases it.

Maybe we should just issue a custom error when include isn't explicitly set and an included file is outside rootDir.

File "tests/foo.ts" is outside rootDir 'src'. Did you forget to set an "include" pattern?

Logged #33515

I've had issues with this, as well over in https://github.com/microsoft/TypeScript/issues/31757#event-2480427393

The interplay between rootDir, include and exclude is just designed so weirdly.

I always thought that rootDir is the folder that the compiler starts traversing and include are additional folders (e.g. vendor folders) that might be referenced from rootDir.

The current definition is weird as hell and extremely unintuitive.

Ironically, what's confusing is that they don't interact: The settings do not affect each other at all. include is 100% in control of what's in the program and rootDir is 100% in control of computing the layout of outDir.

Just to add another example of the problem, this is my folder sturcture:

tsconfig.json
package.json
src/index.ts

I have in my tsconfig:

"rootDir": "src",
"outDir": "lib",

And in my "index.ts" (src folder)

import pkg from '../package.json'; // I read the package.json object

export class SomeClass {
  public version = pkg.version;
}

I _know_ that, after the build, the "index.js" file will be inside the "lib" folder. And I _know_ that the package.json will be 1 level up.
I don't think that Typescript should know better than me my folder structure and break the build. It should simply log a warning about some files outside the rootDir folder that weren't included in the output folder.

@sebelga I'd probably consider that a bug - allowing .json from outside rootDir would be OK since TS doesn't copy that to the output folder

@sebelga I'd probably consider that a bug - allowing .json from outside rootDir would be OK since TS doesn't copy that to the output folder

Thanks not true.. We have some special rules for emitting .json files... If the file is going to be emitted at same location at itself then and then only it is not emitted. In all cases it is emitted (eg when --outDir is specified, it will be emitted into that folder)

@RyanCavanaugh I have a similar use case to @sebelga where I want to console.log out the version number from package.json at runtime.

So my solution was to change my "rootDir": ".", then I have this includes

"include": [
    "src",
    "typings"
  ]

And as the package.json file is included in the lib (built) folder, that contains a "src" subfolder, I wrote a bash script that runs right after the build to clean up things.

#!/bin/bash

rm lib/package.json
mv $(pwd)/lib/src/* $(pwd)/lib
rm -rf lib/src

It does work, but feels quite hacky to me.

@sebelga Do you have .ts files in the typings folder? If not, it would be legal to set rootDir to src and skip the script.

@sebelga Instead of const { version } require('../../package.json') or import { version } from 'package.json'), have you tried doing something like this:

import { sync as loadJsonFileSync } from 'load-json-file';

const { version } = loadJsonFileSync('../../package.json');

TypeScript won't care about it being outside of the rootDir this way.

https://github.com/sindresorhus/load-json-file

If you want to avoid hardcoding all the ../.. you can try this:
https://github.com/sindresorhus/read-pkg-up

@RyanCavanaugh I can't set it to "src" as mentioned above. I am importing "package.json" outside our src (import pkg from '../package.json';) and tsc does not allow that.

Thanks for sharing this @dylang ! I will try it.

I told typescript that my source was all in a particular folder (via rootDir)

This is not what rootDir means! rootDir means "Use this folder as the relative basis for computing the paths in outDir". _Which files are part of your compilation_ is a separate question that is answered by files and/or include/exclude

@RyanCavanaugh But it is what rootDir should mean, and this is what this issue is about.

I'm not that good at maintaining my composure when I see issues that have been open since 2016 that could probably be very fixed without introducing new issues for existing users by introducing a new "compilerOption" to let the compiler know it can ignore the error in a given use case.

For this, I apologize. I can't help it, because I'm relied upon to provide a good service, yet I occasionally encounter issue threads like these. Why is it so difficult for the authors of TypeScript to implement one simple configuration flag?

I don't think it's because the developers are lazy, or because they're unwilling to help. I don't think anyone here is a bad person, except for myself.

It's this conflict, this debate, which has gone on for years over what appears to be the definition of the property "rootDir". And what to do about this issue.

People get thrown into this debate because they believe that they are right. Yes, "rootDir" serves as the base directory for the source code. Yes, the error is valid.

Yet, the underlying issue still persists. My tooling compiles the code perfectly, yet my IDE displays this error on my screen. It's a perversion. I want those red lines gone. My files don't have errors in them. And if they do, I should focus on those rather than being distracted by these non-issues.

All I want is to suppress this error message. I think that's what most of us want. I don't know if there are other cases here and maybe there are people who would like to speak up about that.

Can we please work together and solve this problem? It would make me so happy to see those red lines gone...

@nullquery If tsc works but your IDE is showing your red squigglies then it means your IDE is not using the same compiler version or configuration as tsc.

@nullquery file a bug that shows how to reproduce the problem and we will either fix it or tell you what's wrong with your project configuration

Following on from @sebelga workaround, here is is a variation that is suitable to go inside your npm scripts

tsc --module commonjs --target ESNext --outDir ./edition-esnext --project tsconfig.json && test -d edition-esnext/source && ( mv edition-esnext/source edition-temp && rm -Rf edition-esnext && mv edition-temp edition-esnext ) || true

Example usage here.

Boundation will apply it for you automatically.

The solution is Project References.

To use this, edit your "tsconfig.json" file and add this to the end of the file (outside of the "compilerOptions"):

"references": [
    { "path": "../common" }
]

This will allow you to reference files in the "../common/" folder (and all sub-folders).


My earlier confusion was stemming from the fact that IntelliJ and my untainted gulp-typescript task were giving me different results despite using the same "tsconfig.json" file.

It turns out that for some reason, "gulp-typescript" is trying a best-effort approach to compilation.

An error in TypeScript (such as let z: number = '') will be flagged in IntelliJ but "gulp-typescript" compiles it just fine.
Only JavaScript syntax errors (such as let 1z = '') are flagged by "gulp-typescript".

So, if you're a "gulp-typescript" user, you may experience such an issue depending on your version of "gulp-typescript" and your configuration. (I'm using the latest version at the time of writing, but I'm assuming someone from the future might be reading this. I hope times are better for you.)


I know the use of Project References is buried somewhere in this avalanche of responses, but it would have been better if this were documented better.

The top 3 results for "TS6059" on Google all lead to GitHub issues. It would have been a lot clearer to me if there was a page documenting these common errors and their solutions.

@MicahZoltu Is this something that can be addressed? Should I make a new issue, or is this something that would go faster if it were discussed internally between the main contributors?

(FWIW I'm not part of the TypeScript team)

Generally, I recommend against using paths unless you absolutely have to. I believe its purpose is to allow for compatibility with legacy JS projects with weird dependency structures but I don't believe its intended to be used on any new projects, and if you find yourself needing it you may want to consider migrating away from it when you have the opportunity. I don't use it personally so I can't really help with issues you may have with it, but I have seen lots of people struggle with it and the solution is usually "stop using it".

As for gulp-typescript, it sounds like perhaps the version of TSC being used by gulp-typescript is different from the version of TSC being used by IntelliJ. When you see symptoms like you describe, that is usually the problem (the other being that they are not using the same tsconfig.json).

The version used by "gulp-typescript" should be identical; both versions are derived from node_modules. I've tried various ways both of catching errors and modifying settings related to it (including messing around with the various "reporters" provided by "gulp-typescript" and attempting to override settings to emit more errors. Nothing seems to work.

It's fine though. As long as IntelliJ gives me errors, I'm happy to accept that the Gulp task will ignore them.


I'm not a fan of overhauling my method in favour of the preferred method of the week. The project structure I have makes sense to me. It keeps everything contained yet gives me the flexibility to split things up into multiple components.

I don't believe in "allowing compatibility for legacy reasons," and especially when it comes to project references. It _must_ be possible to reference files outside of the rootDir or you're simply not going to have the freedom to define the project structure in a way that makes sense to you & your team.


When I first began working with TypeScript, I ran into numerous issues. Among them:

  • For including JavaScript files the go-to solution seemed to be AMD at the time. The website sounded official and there were many examples online of AMD in production use. But apparently, Webpack was the new big thing and so some projects simply didn't work in an AMD environment. Lesson learned: Not everyone is going to adopt the new method, so you're always screwed.

  • The road from TypeScript to the finalized minified JavaScript (with source mappings) was not clear to me. Several different methods were available at the time, but none of them seemed to work natively with "tsconfig.json" files and/or my IDE at the time. Lesson learned: If you want something done right, you've got to do it yourself; don't rely on other people to do the work for you, no matter how well-intentioned they are nobody is perfect and nobody can predict _your_ workspace.

  • There was one custom thing I wanted: a way to convert the content of HTML files to a JavaScript dictionary for inclusion. This should have been my only stumbling block, but in the end, this turned out to be one of the easier scripts to write.

I ended up having to develop my own Gulpfile to handle everything. It does the following:

  • Copies the minified JavaScript files for distribution from node_modules folder to my webroot. It sounds simple, but almost every JavaScript library I tried to include had a different place where they stored their minified JavaScript files. There was no configuration, no clear path to the files. I ended up having to write separate scripts for each JavaScript library I wanted to include. It works, assuming the location doesn't change in future versions, but if it does I can always change the copy script.

  • Compiles TypeScript according to the local "tsconfig.json" file which generates separate files in an output directory (using outDir) so I can be sure that TypeScript is compiled the same way in my IDE as it will be in Gulp (hahahahaha!), then uses the "rollup" plugin to compress those files into a single JavaScript file, then runs UglifyJS to minify this file, all while maintaining a source mapping that maps back to the original TypeScript file.

  • (Custom task) Generates a JavaScript file containing a dictionary called html_templates for each HTML file in the TypeScript source root and places it in my webroot folder as a minified JavaScript file.

There may be more elegant ways out there, but after jumping from one open-source project to the next, I had quite enough of juggling between the solutions each of them was offering.

The approach I've chosen works, it's simple enough to understand (especially now that I understand that "gulp-typescript" does something different) and I'll probably continue using this for years to come.

The best solution is one that you understand yourself. While this doesn't help anybody who tries to help me (which I appreciate very much! the help I mean, not the confusion) I can speak from experience that there will always be something wrong with _any_ solution so it's better to stick with something you own yourself than something that is designed, developed and produced by a multicultural team of various beliefs, sexual orientations and gender identities.


tl;dr please don't break project references. I need that for my tooling to work. thank you

I gave up setting up multi-project TS setup proerly and just do tsc -w in every project and npm link, and then you can just ignore all the complexities of multi-project tsconfig and just refer to other project like it's plain node.js dependencies in node_modules.

But today in one project I changed target from es5 to es6 and it won't compile anymore because File '.../lib.ts' is not under 'rootDir'.

File structure:

/app
 - tsconfig.json // rootDir = "."
 - app.ts (

/app/node_modules/lib
 - tsconfig.json // rootDir = "."
 - lib.ts
 - lib.js
 - lib.d.ts

Why tsc in /app cares about /app/node_modules/lib/lib.ts file? It shouldn't use that file at all. There's compiled /app/node_modules/lib/lib.js and /app/node_modules/lib/lib.d.ts.

If /app/node_modules/lib/lib.ts deleted - surprise - it compiles fine.

@alexeypetrushin you can take a look at some of my projects. It seems to be working fine. e.g.:

import * as pkg from '../package.json';

NOPE. And there's no way to turn this error off I can find.

@SephReed: I've spent 200 rep points on StackOverflow and someone contributed this very simple solution for importing ../package.json. All you have to do is place a typings.d.ts file in the same directory as package.json, with this contents:

declare module '*.json';

I have no idea how this works, but honestly, I don't care. At that stage, TS was forking for itself, not for me.

I surprised this is such a heated debate. But I understand the reasoning. For me, I have of course the source folder, but I also have build scripts outside of that folder that isn't meant to be compiled but definitely include type support, because let's be honest your best IntelliSense is TypeScript.

repo/
⊢ config/  << provide TS support, but don't build this directory
  ⨽ webpack.config.ts 
⊢ scripts/  << provide TS support, but don't build this directory
  ⨽ release.ts
⊢ src/        << build this directory
  ⨽ example.ts
⨽ tsconfig.json

So I use "rootDir": "src" to make sure the files are directed into dist and not dist/src. But of course, TypeScript likes to complain about this setup, but since I use Webpack it doesn't error for me.

I finally got what this flag exactly means without several comments that don't make it any clearer. Just cheers with this.

The options that sets folders and files for tsc to take account into compliation: "files", "include", "exclude".

Then what you use "rootDir" for?
rootDir is the root of the directory structure(hierarchy) to your source files, which tsc uses to emit the same directory structure starting from the tsconfig.json directory or "outDir" if it's specified. So this is not the path that tsc uses as the base path for "outDir". "outDir" is just the root that tsc emits output files.
For an instance, let's say your java project has this directory structure.

- src
  - main
    - java
    - resources
      - static
        - js
        - ts
          test.ts

When you set "include" and "rootDir" as down below, tsc will generate the output as:

"include": "src/main/resources/static/ts" *
"rootDir": "." *

- src
  - main
    - java
    - resources
      - static
        - js
        - ts
          test.js *
          test.ts

When you also set "outDir" as down below, tsc will generate the output as:

"include": "src/main/resources/static/ts"
"rootDir": "."
"outDir": "build" *

- build
  - src
    - main
      - resources
        - static
          - ts
            test.js *
- src
  - main
    - java
    - resources
      - static
        - js
        - ts
          test.ts

Probably you wanted to have this by setting "rootDir" option and changing "outDir" option as down below.

"include": "src/main/resources/static/ts"
"rootDir": "src/main/resources/static/ts" *
"outDir": "src/main/resources/static/js" *

- src
  - main
    - java
    - resources
      - static
        - js
          test.js *
        - ts
          test.ts

So when you have set "rootDir" option that doesn't cover the scopes of files except from "exclude" option, tsc fails to build because it doesn't make sense with the purpose of "rootDir" option.

Yeah it's completely a bad naming practice. It should've been just "rootOfStructureOfInputs" or something. And also, "outDir" should've been just "rootOfOutputs".

What you see on the SS is not a WebStorm problem - it comes from TS DevServer.
The second recommended import is crap. It picks up from the "communication" source folder. I don't know who the heck should want that. Compiling a file with such an import produces a bunch of problems.

I also tried:

"exclude": ["lib", "node_modules",
        "..", "../..", "../../..", "../../../..", "../../../../..", "../../../../../.."
    ]

Didn't solve the problem either

Bildschirmfoto 2020-07-22 um 10 08 28

@mhegazy You labeled this issue with "Working as Intended" but thats not true. As you can see in my screenshot, tsc-server provides files outside of my rootDir (in this case mobiservice/src) as import suggestion. To me this looks like a bug...

Was this page helpful?
0 / 5 - 0 ratings

Related issues

weswigham picture weswigham  ·  3Comments

DanielRosenwasser picture DanielRosenwasser  ·  3Comments

siddjain picture siddjain  ·  3Comments

uber5001 picture uber5001  ·  3Comments

manekinekko picture manekinekko  ·  3Comments