Typescript: Write compiled output to stdout

Created on 20 Nov 2014  路  39Comments  路  Source: microsoft/TypeScript

It doesn't seem possible to compile and output to stdout. There was some discussion on CodePlex before the migration to github: https://typescript.codeplex.com/workitem/600

Declined Suggestion

Most helpful comment

This issue hasn't had any activity for quite some time. I think that Windows should be supported better, as *NIX can only output to stdout at this point (by specifying --out /dev/stdout).

It seems like the best solution to this, which has been proposed in a few locations (1, 2), is to allow a special value - to --out and --outFile which instructs tsc to, instead of writing to a file, writes to stdout.

Example usage:

tsc src/compiler/binder.ts --out -
tsc src/compiler/binder.ts --outFile -

If a compiler error occurs, the errors will be appended after the emitted output.

I've worked on a very simple implementation for this issue which is essentially just the following in _compiler/sys/writeFile_

// If `-` is provided as the fileName, output to stdout
if (fileName === '-') {
  process.stdout.write(data, "utf8");
  return;
}

I'm wondering how to author tests for a change like this, and what kind of documentation changes need to happen, and where those go. Thanks.

All 39 comments

Would a node package of the compiler with an API that allows you to pass strings and get strings back do the job?

That'd definitely be useful, but I'd much rather have an option to output to stdout in the cli.

so what happens if you have multiple outputs? how do you indicate file begin and end? and where do errors go? or should it just be a JSON.stringify output?

I think it'd be reasonable to only support --out functionality when printing to stdout, so it would print the concatenation of all outputs. Errors should be written to stderr.

It'd also be nice if it could take input through stdin, so tsc could be used in a series of commands using unix pipes.

If you're on a Unix system, you can look into mkfifo as a temporary workaround. I _think_ the following might work.

mkfifo -m 600 my_fifo
tsc file1.ts file2.ts --out my_fifo
YOUR_COMMAND_HERE < my_fifo
# or...
cat my_fifo | YOUR_COMMAND_HERE

but I can admittedly see the utility in having standard output as a target.

Open questions:

  • What should we call the flag?
  • What happens to external module emit? We currently emit those to their own file even under --out. It might be desirable to be compiling a single external module under this flag, so we wouldn't want to error, but we may also end up compiling more than one external module (and thus emitting more than one file).

Another question: how do you differentiate between output and message output? I guess we could use standard error as well.

What should we call the flag?

Following @jbowens's suggestion to invariably apply --out behavior, there doesn't have to be a _new_ flag: the Unix (POSIX) convention of using - to represent stdin or stdout (as in this case), could be applied: tsc --out - ....

While there's a hypothetical backward-compatibility concern -- currently, tsc literally creates a file named -, if you do that -- I don't think anyone would miss it.

As for the "Unix-centricness": I wouldn't expect Windows users to object, either.

I wanted this functionality for compiling little inline snippets of TypeScript within my templates which I could inject back into <script> tags. I wouldn't use it for anything that generated multi-file output.

have you considered typestring?

I agree with @mklement0's proposal. This would be very helpful.

@RyanCavanaugh It seems like you should treat stdout the same as if any filename was passed to the out argument. That will cover the majority of use cases anyway.

The rationale for this is wanting to pipe the output to something else, for example a minifier or gzip or ng-annotate or _whatever_. I think it is likely that people will generally be using this against single files, and using downstream tools to concatenate or whatever.

I agree. You can see the behavior of related node packages such as node-sass, less, coffeescript. Based on the commonalities, I propose:

tsc blah.ts

to print to stdout instead of carrying out the unwanted/unintended IO op with presumed destination name "blah.js"..

tsc blah.ts anotherBlah.js

should write to anotherBlah.js without requiring the --out flag.

There was a proposal somewhere in node community to provide some kind of standard CLI interface for transpiler and linter packages. That would have prevented everyone inventing their own way.

I propose:

tsc blah.ts

to print to stdout instead of carrying out the unwanted/unintended IO op with presumed destination name "blah.js"

That is a major breaking change in behavior, so we wouldn't be able to do that.

Perhaps this would be best implemented as a wrapper package.

@nicksloan How would you implement it as an external tool? Have tsc write to a temporary file, read it back in, and dump it to stdout? I could do that myself, but I don't want the overhead of an intermediate file.

I think they should just add an --stdout option. Just have it the throw an error if it needs to generate multiple output files.

If you need further control, a Node package as @mhegazy suggested would be ideal. AFAIK, gulp-typescript and other tools have to invoke a separate process and build up a command-line string to do anything. I've hit the maximum command length with other similar tools on many occasions.

How would you implement it as an external tool?

@teppeis has made typescript-simple which could be used for this purpose. You could also use our compiler API.

@DanielRosenwasser I assumed there would be a way to get at the compiler API, and it looks like you guys have made it easy enough. Typescript-simple makes it even easier. Either way, this is a perfectly viable approach.

I wonder if the Typescript team would be open to bundling such a thing as an alternative executable within Typescript once it showed some degree of maturity?

Supporting emitting to stdout by default would have been a good idea a few years back when tsc first came out; but now changing the behavior is a huge breaking change that we can not justify to our customers.

bundling another tool that does mostly what tsc does except slightly different would not be something we would pursue either.

I do not see any harm in taking a dependency on another package that wraps tsc or some of its functionality (e.g. typescript-simple). I think this is a decision you make based on your workflow and other tools you need to support. We have invested in making the API simple and usable to simplify such scenarios, and we will continue to invest into this space moving forward.

Supporting emitting to stdout by default would have been a good idea a few years back when tsc first came out; but now changing the behavior is a huge breaking change that we can not justify to our customers.

@mhegazy Is it possible to add this to the 2.0 release? As npm evolves, I think more people will use npm scripts as a build-tool in the future and for that use-case piping is quite essential to reduce overhead.

@youngroger i would be new typescript-cli tool that does that, or possibly adding a commandline flag to support this if there is enough user demand.

I can only see writing to stdout helpful if that works in watch mode too, but I have no idea how. Writing to stdout is a performance optimization, as is watch mode.

Use case:

tsc -out - -w | ng-annotate -a - > release.js

I can't think of an use case of reading from stdin.

Another question: how do you differentiate between output and message output? I guess we could use standard error as well.

Output errors to console: tsc
Redirect errors to a file: tsc 2> err.txt

@awerlang As I said before, reading from stdin and writing to stdout is useful for composing multiple commands to create a pipeline. It's not a performance optimization, it's a usability improvement.

If typescript wants to be a good Unix citizen, outputting to stdout is a minimal requirement.

For reference, the following does not work, while it should at the very least be supported.

tsc --out /dev/stdout example.ts
error TS5033: Could not write file '/dev/stdout': ESPIPE, invalid seek

@sebastien i like this proposal. i think this will resolve the issue and is not a breaking change. i have filed #4841 to track it.

PRs for #4841 are also welcomed.

@mhegazy This won't resolve the issue for Windows users.

@mnpenner we have not had a complete proposal for this issue so far.

@mnpenner, @mhegazy: Note that going with the POSIX convention of using - to represent stdout and/or stdin - would also work on Windows.

For instance, reading from stdin and writing to stdout would then take the form

tsc - --out -   # 1st '-' is stdin, 2nd '-' is stdout.

There is a hypothetical backward-compatibility issue, if there are people out there currently using files _literally_ named -, but I don't think that's a real-world concern.
(Going forward, you could still use that filename by specifying ./- to disambiguate.)

As an added convenience, if the input comes from stdin, you could consider outputting to stdout by default, given that no meaningful output filename can be derived; this would:

  • allow convenient use in pipes; e.g., ... | tsc - | ...
  • enable the following shortcut for tsc foo.js --out -: tsc < foo.js

Using - would be desirable, but in any case you should be able to write to/read from named pipes as well. I don't get why tsc is seeking on the output file, opening with write mode should be enough.

@sebastien Agreed. Fortunately, it looks like the behavior is an incidental byproduct of tsc using Node.js's fs.writeFileSync() function to write output files. (Why the Node.js function behaves that way, I don't know).

A na茂ve fix that implements - as representing stdout would be (file tsc.js):

        function writeFile(fileName, data, writeByteOrderMark) {
            if (writeByteOrderMark) {
                data = "\uFEFF" + data;
            }
            if (fileName === '-') {
                process.stdout.write(data, "utf8");
            } else {
                _fs.writeFileSync(fileName, data, "utf8");
            }
        }

A potential problem is that the Node.js documentation states that process.stdout is _non_-blocking when writing to _pipes_ on _Windows_, unlike on Unix-like systems - I'm not sure what the implications are.

P.S.: Here's a simple test that you can run in the Node.js REPL to reproduce the problem:

require('fs').writeFileSync('/dev/stdout', 'hello');   # breaks as of Node.js v0.12.7

Interestingly, Linux reports error 'ESPIPE, invalid seek', while OSX reports 'ENXIO, no such device or address'.

So this issue is not really about writing the compiled output to stdout anymore, that was fixed by #5454. This is about making it nice to use, so reading from stdin, writing errors to stderr and accepting - shortcuts, is that right?

That would be a nice addition, given that it's standard practice in UNIX . So yes, - should be aliased to stdin for input and stdout for output.

@sebastien with https://github.com/Microsoft/TypeScript/pull/5454 --outFile /dev/stdout works on *NIX systems.

:+1:

--outFile - on Windows please (neither - nor CON work with 1.8.9).

This issue hasn't had any activity for quite some time. I think that Windows should be supported better, as *NIX can only output to stdout at this point (by specifying --out /dev/stdout).

It seems like the best solution to this, which has been proposed in a few locations (1, 2), is to allow a special value - to --out and --outFile which instructs tsc to, instead of writing to a file, writes to stdout.

Example usage:

tsc src/compiler/binder.ts --out -
tsc src/compiler/binder.ts --outFile -

If a compiler error occurs, the errors will be appended after the emitted output.

I've worked on a very simple implementation for this issue which is essentially just the following in _compiler/sys/writeFile_

// If `-` is provided as the fileName, output to stdout
if (fileName === '-') {
  process.stdout.write(data, "utf8");
  return;
}

I'm wondering how to author tests for a change like this, and what kind of documentation changes need to happen, and where those go. Thanks.

This would definitely be the expected behaviour from a Unix perspective.

Using --outfile=/dev/stdout works now, but if there is an error on compilation, the error is printed in-place of/alongside the compiled output. This, again, is not standard practice and can cause issues when scripting. To work around this, I use the following.

(tsc -m system ./path/to/file.ts --outfile /dev/stderr 3>&2 2>&1 1>&3-) | someOtherCommand

Because the errors are printed on stdout, I'm telling tsc to print code on stderr, and then switching the file descriptors on it, resulting in errors on stderr, and code on stdout.

Hope this helps someone!

Declining this one - outFile compilations (which is the only place this makes sense) are definitely getting increasingly rare and it's straightforward enough to use ts.transpileModule if you need this in a scripting context of some sort.

This becomes a necessity again. I am currently experimenting with deno. As you know deno and typescript go hand in hand. Node modules on the other hand not really. So ts.transpileModule is not an option at the moment. This issue stops some creative workflow rn.
Why would the result code not be piped to stdout if no --outFile is declared?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

manekinekko picture manekinekko  路  3Comments

uber5001 picture uber5001  路  3Comments

zhuravlikjb picture zhuravlikjb  路  3Comments

kyasbal-1994 picture kyasbal-1994  路  3Comments

DanielRosenwasser picture DanielRosenwasser  路  3Comments