Typescript: Should `tsc file.js`build a file with the "compilerOptions" in TypeScript's config. Or should it compile a file and ignore the config?

Created on 3 Jan 2019  Â·  44Comments  Â·  Source: microsoft/TypeScript

With Node v11.6.0, and TSC, 3.2.2, I am getting

node_modules/rxjs/internal/Observable.d.ts:82:59 - error TS2585: 'Promise' only refers to a type, but is being used as a value here. Do you need to change your target library? Try changing the `lib` compiler option to es2015 or later.                                                                   

82     toPromise<T>(this: Observable<T>, PromiseCtor: typeof Promise): Promise<T>;                                                                    
                                                             ~~~~~~~


Found 1 error.

When I run

type P = typeof Promise;

Using @types/node

This is a bug push up from rxjs https://github.com/ReactiveX/rxjs/issues/4448
You can see the issue documented at https://stackoverflow.com/questions/54014405/ts2585-promise-only-refers-to-a-type-but-is-being-used-as-a-value-here

Question

Most helpful comment

Nope, here's the exact issue - you're telling tsc to transpile a single file. In that case it ignores the tsconfig.json and just transpiles the file in a vacuum, using all defaults for the tsconfig fields. Since there is no /// tag in the source for the es2015 lib you get the Promise error.

Wow, even if documented that's extremely counter intuitive. I can't think of any program that ignores the configuration file when given an argument that isn't explicitly either conflicting with the configuration file, or demanding to override it. That's not a good user interface at all. Git and NPM being two examples that don't behave in that way that typescript users are likely to be using in the same project.

All 44 comments

The error message says you should be targeting lib: "es2015”. Are you? Note that lib and target are distinct because of polyfills (you can polyfill builtins but not syntax)

@fatcerberus I have a sample repo with it, in that repo i'm doing es2018 for lib, but you can try es2015 same thing.

https://github.com/EvanCarroll/rxjsissue4448

Just a hunch, have you tried using lowercase for es2018? I know the docs show uppercase but I think that might be a mistake as I’ve always seen it lowercase in practice. Otherwise I have no clue what’s wrong here, it seems bizarre.

It is lower case, no joy. It's also confirmed by the rxjs guys and the stackoverflow commenter. Nothing that uses promises works on node 11

What I don’t understand is what the node version has to do with this - typescript and node are completely separate tools.

oh okay - I see, the issue is when the typescript compiler is run on node 11, not when targeting node 11. My bad.

So I tried to reproduce this using the exact tsconfig, dependencies, and sample code as in the SO question... and it seems to work fine on my end. Node.js version is 11.6.0.

npm install typescript
npm install rxjs
npm install @types/node
npm install ts-node
D:\temp>npx tsc

D:\temp>type main.js
import { range } from 'rxjs';
import { map, filter } from 'rxjs/operators';
range(1, 200).pipe(filter(x => x % 2 === 1), map(x => x + x)).subscribe(x => console.log(x));

Did you pull down my repository run npm install and then try to tsc the file? I'm confused, you say this

oh okay - I see, the issue is when the typescript compiler is run on node 11

Which is true, and then you say " it seems to work fine on my end. Node.js version is 11.6.0."

Yeah, that was before I tested it - that was just stating I understood why you mentioned the node version.

I don't know what else to say, I got the prob. Guy on SO and on RXJS question got the prob. Repo is simple works (to show the problem) with the latest version of all tools. It's giving that error on running tsc on my box (running Linux).

This is the line that's problematic for you, this is working?

type P = typeof Promise;

Yeah, that works, as does:

C:\temp>git clone https://github.com/EvanCarroll/rxjsissue4448
Cloning into 'rxjsissue4448'...
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 6 (delta 0), reused 6 (delta 0), pack-reused 0
Unpacking objects: 100% (6/6), done.

C:\temp>cd rxjsissue4448

C:\temp\rxjsissue4448>npm install
npm WARN [email protected] No description
npm WARN [email protected] No repository field.

added 3 packages from 39 contributors and audited 3 packages in 1.825s
found 0 vulnerabilities


C:\temp\rxjsissue4448>npx tsc

C:\temp\rxjsissue4448>node -v
v11.5.0

v11.5 because I switched machines--still Node 11 though. Tested on both Windows and Linux (Ubuntu 16.04, Node.js installed from snap).

Only thing I can think of is you're somehow picking up a stale tsconfig.json from elsewhere.

no idea what npx does, or what it's doing differently can you just run tsc runme.tsc?

It's the same thing - tsc by itself requires typescript installed globally, npx is when it's installed locally (in node_modules). Works either way.

No idea, all I know is it's erroring with me and I'm not using any special layer so I'm trying to keep it simple and figure out why it's not erroring for you, and I'm thinking your npx is doing something.

tsc runme.ts

Nope, here's the exact issue - you're telling tsc to transpile a single file. In that case it ignores the tsconfig.json and just transpiles the file in a vacuum, using all defaults for the tsconfig fields. Since there is no ///<reference> tag in the source for the es2015 lib you get the Promise error.

What you want to do is just run tsc. You can include/exclude files as necessary by specifying them in the tsconfig (which is your project file).

This behavior, by the way, is documented:
https://www.typescriptlang.org/docs/handbook/tsconfig-json.html

When input files are specified on the command line, tsconfig.json files are ignored.

Nope, here's the exact issue - you're telling tsc to transpile a single file. In that case it ignores the tsconfig.json and just transpiles the file in a vacuum, using all defaults for the tsconfig fields. Since there is no /// tag in the source for the es2015 lib you get the Promise error.

Wow, even if documented that's extremely counter intuitive. I can't think of any program that ignores the configuration file when given an argument that isn't explicitly either conflicting with the configuration file, or demanding to override it. That's not a good user interface at all. Git and NPM being two examples that don't behave in that way that typescript users are likely to be using in the same project.

The use case here being

  1. that a project needs Typescript so they install it.
  2. they run tsc file.ts which they believe runs Typescript on the file.
  3. they get an error
  4. They google the error
  5. The error is remedied by adding something to their typescript config.
  6. They do that and run tsc file.ts again and get the same error

At the absolute least I would expect tsc to be verbose by default and tell us it's ignoring the configuartion file which specifies the values of lib needed. I imagine many of your users are doing the above. I use TSC regularly but I've never used it with Rx.js outside of a project that had tsc. So now I'm just wanting to use tsc for Rx.js.

It makes sense to me at least - you either tell it to compile a whole project file, or you pass one or more source files and specify the options on the command line. Everything you can specify in tsconfig.json has a corresponding command-line option.

It would be probably be bad if it used the tsconfig when specifying files on the command line - since then if you used tsc in, say, a shell script, the behavior of the script would change depending on whether the CWD contained a tsconfig.

Essentially the tsconfig is a “build script” - you tell tsc to use that instead of specifying everything you want to do on the command line. It’s not a “compiler preferences” file (admittedly the naming of “tsconfig” is a bit confusing in that regard), it’s a self-contained project in itself, like what .vcproj is for MSVC.

Basically: think of tsconfig.json as a makefile, and tsc as a make tool + compiler in one.

since then if you used tsc in, say, a shell script, the behavior of the script would change depending on whether the CWD contained a tsconfig.

Isn't that how everything works though?

  • make reads .Makefile in the current directory
  • npm reads package.json in the current directory
  • git reads .gitignore in the current directory

None of them forget about their config file when you provide an option. They simply override that option and use the config file to create the execution environment for the command. I understand what you're talking about but this is the only utility that works in this fashion -- where providing an argument means it forgets about the config file.

I want the config file to configure TSC for the project. Not to specify the exact item i'm wanting to compile. It just intuitive to me that the working directory be significant, especially if there is a config file inside it and not only if there are no arguments provided to the program.

Let me make it clear: What you’re essentially asking for is analogous to wanting gcc to honor options in a makefile even when invoked directly. It just so happens that tsc combines these two functions into one program.

What you may be able to do is (not tested):

tsc --project ./tsconfig.json runme.ts

Let me make it clear: What you’re essentially asking for is analogous to wanting gcc to honor options in a makefile even when invoked directly. It just so happens that tsc combines these two functions into one program.

Not exactly, because GCC and make are separate; there is a distinction there so I would never expect gcc to accept a configuration file anyway. But tsc does with the --project command you've provided which makes it similar to make, and npm, and git.

So with make if you have a basic Makefile,

.PHONY: ALL                                                                                                                                            

CFLAGS="COMPILER FLAGS"                                                                                                                                

ALL: foo bar                                                                                                                                           

.DEFAULT:                                                                                                                                                     
  @echo "${CFLAGS}" caught target $@    

And you run make it builds foo and bar, now if you run

make baz

It still uses the make file. It doesn't forget it and act like a compiler. It just builds instead baz. And it does it with the CFLAGS. The same applies btw if you want to run one specific rule or compile one file outside. For example if you run make foo.c inside of a build directory it will use the Makefile to build that one file.

Does it seem intuitive to you to say that the default for --project is ./tsconfig.json in all instances except when you provide it arguments? In which case if you want to use tsconfig.json you have to manually supply it?

Using the above analogy:
tsc file.ts is gcc.
tsc --project is make.

It just so happens that, for convenience, the specific case of tsc with no arguments implies --project. The alternative would be an error because you didn’t provide any input files.

I understand how things are, now that you've explained them and did immediately on my first response here. I just find that entirely unintuitive and I imagine I'm in the majority here. You've severed tsc into two modes, neither of which are apparent to the user and used those two modes to justify a different user interface. I wasn't aware that I was supposed to

  • think of tsc file.ts as a compiler
  • tsc as a make system
  • the file ./tsconfig.json as a default configuration for the make system, but also an acceptable and optional configuration file for the compiler environment via --project
  • think of tsc file.ts as an invocation of the compiler, rather than tsc file.ts as being a directive to build the file with the configuration file.

I'll just leave it off with this, because I'm honestly just trying to provide some guidance to build a better system. Ask some people who do NOT know the default behavior this question

"If tsc builds a project by reading from tsconfig.json, would you expect tsc file.ts to build that file with the configuration in tsconfig.json, or to ignore tsconfig.json and compile the file?"

Just for fun https://twitter.com/TheEvanCarroll/status/1080899909522477061 (maybe I am in the minority)

I’ll agree with you that the “project builder” should probably be a separate tool (tsmake or something), but practically speaking, since I assume it was a conscious design decision not to have two distinct tools, how would you make tsc fully scriptable like a command line compiler (without the risk of picking up stray config files—just look to eslint for how many headaches that can cause) but still get the make-like functionality? Given those constraints, I would probably set up things up the same way.

Wouldn't you want to follow the lead of most all the other tools (eslint, babel, etc) and have an explicit CLI opt-out for avoiding the config file? In other words, "gcc" usage is nowhere near the common case, and seems surprising to most users, so it seems like a poor choice of default.

In any case, I don’t think this can be changed now without breaking peoples’ build environments, which would be bad considering how TypeScript’s release cycle seems to work (every major or minor version supersedes the last). But I’ll duck out at this point and let actual TypeScript team members chime in if they want before I accidentally make a fool of myself. :smile:

Would you consider changing the title of the issue to better reflect the ongoing conversation, to provide a better summary and be more convenient to find? For an example, “Inconsistent tsc behavior with and without file parameters”. @EvanCarroll @weswigham

Another use case: we have a pre-commit hook which runs tsc for all changed files, and as soon we can't mix --project with files list we wrote a code to convert compilerOptions to CLI args. Everything works fine until today when we want to use paths compiler option, which cannot be passed via CLI 😞

Even without implicit using tsconfig.json for compiler options why we can't use --project arg and files list together?

_(maybe this isn't that issue where I should post this comment, but my opinion is that tsc should use compiler options from a config while compiling only one file)_

Thanks for your help all. Problem solved here.

This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow.

@DanielRosenwasser @RyanCavanaugh It is very disappointing this hasn't been taken further into consideration. This is how 98% of javascript developers expect tsc to behave and would make tsc consistent with every other node tool out there.

Please reopen; nonhumans closing issues is exceedingly user-hostile.

Allowing to override tsconfig.json by passing a file path with tsc --noEmit -p tsconfig.json would be really useful and would avoid having multiple config files. Please reopen. Thank you!

Allowing to override tsconfig.json by passing a file path with tsc --noEmit -p tsconfig.json would be really useful and would avoid having multiple config files. Please reopen. Thank you!

This is already supported!

Please reopen; nonhumans closing issues is exceedingly user-hostile.

I will close this myself to help 😉

This is the intended behavior because we don't want to break thousands of msbuild-based compilations where people are using .csproj settings for one compilation and tsconfig.json for something else. Even issuing a warning when a tsconfig.json is present has the potential to break these builds because people might have warnings-as-errors turned on.

people are using .csproj settings for one compilation and tsconfig.json for something else

You can add --no-project flag for them (it's joke, don't bear on me 🙂).

Even issuing a warning when a tsconfig.json is present has the potential to break these builds because people might have warnings-as-errors turned on.

What about allowing to pass a list of files along with --project to override files to compile? It seems that it shouldn't break case you've described above.

Also, what's about compiler options which cannot be passed via CLI such paths (see https://github.com/microsoft/TypeScript/issues/29241#issuecomment-459789340)?

@RyanCavanaugh

What about allowing to pass a list of files along with --project to override files to compile?

See also #27379. I think there's also another issue where people expect to be able to augment the file list in this manner.

Also, what's about compiler options which cannot be passed via CLI

I think allowing some way of specifying additional tsconfig files (probably in a "config only" mode) would be a very reasonable suggestion. I know some teams are effectively doing this by auto-generating tsconfig files in their build scripts; it'd be nice to make that kind of automation unnecessary.

What about allowing to pass a list of files along with --project to override files to compile? It seems that it shouldn't break case you've described above.

Also, what's about compiler options which cannot be passed via CLI such paths (see #29241 (comment))?

This is exactly what I was trying to do and it doesn't work for me. I will look at versions for a new update if you're saying this is supported already.

Was this page helpful?
0 / 5 - 0 ratings