microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.78k stars 12.46k forks source link

Typescript aborts on shebang. #2749

Closed jdfreder closed 9 years ago

jdfreder commented 9 years ago

The compiler should allow shebangs to pass through.

http://stackoverflow.com/questions/23298295/how-to-make-a-shell-executable-node-file-using-typescript

CyrusNajmabadi commented 9 years ago

I'm confused. Why would this be part of the typescript compiler? As your link shows, the more appropriate thing to do would be to add the script directive as part of the compilation pipeline with another tool.

mhegazy commented 9 years ago

cause ppl like to write their shell scripts in ts :)

jdfreder commented 9 years ago

Two reasons: 1) Even though shebangs may not be official Javascript spec, I'd expect Typescript to support Node.js Javascript fully, since Typescript is a Javascript superset and Node.js is popular Javascript platform. The following is parsed by node, but not Typescript.

#!/usr/bin/env node
console.log('Hello world.');

2) Is it not easier to ignore the shebang in the Typescript compiler (only one in existence), than it is to add kludge compilation step that prepends the shebang (for N projects that use Typescript for node.js CLIs)? ... I guess you could counter argue N is small, but I think it's actually big.

jdfreder commented 9 years ago

cause ppl like to write their shell scripts in ts :)

+1 : )

tomlarkworthy commented 9 years ago

+1 it be nice to build fully functioning shell scripts written in Typescript like I do with Python or Bash or Javascript, without involving an additional build step.

poelstra commented 9 years ago

+1

I would very much like not having to type "node ..." in front of every TS CLI util / 'shell script', or having to create an almost-empty .js wrapper for them.

@CyrusNajmabadi: arguments for handling it in the compiler:

I filed this on codeplex quite a while back, and apparently so did this user.

As noted in my codeplex issue though, there are some things to take into consideration:

RyanCavanaugh commented 9 years ago

Passing through only the first line this way seems plausible?

poelstra commented 9 years ago

@RyanCavanaugh yes, but that first line must end with (just) a line-feed, and the emitted file needs to have the executable bit set on *nix.

mhegazy commented 9 years ago

We do not modify file properties usually. would not that be something you do in your build script?

poelstra commented 9 years ago

@mhegazy yes, but then the script is still not executable (on non-Windows), so it won't help for the compile-on-save scenario.

CyrusNajmabadi commented 9 years ago

We could extend the TS language to call "#..." another form of trivia. We'd could then preserve it in the same way that we preserve copyright comments at the top of the file.

poelstra commented 9 years ago

That would be a useful part of the solution yes.

For reference, our use-case at work is that we have quite a few handy little utils in our codebase, to restart services, initialize databases, etc. Often these very tools are used and developed in your typical edit-compile-run cycle while working on the main library that the tool interacts with.

For that scenario, it would be great not having to run the build system over-and-over again, as it takes at least a few seconds to run (even though we try to skip compilation for unmodified files etc). At >100k lines of TS code, it takes quite some time just to 'initialize' gulp, and at this moment, the compile pipeline (TS, browserify, uglify, copyright, sourcemaps, etc.) is deemed too complex to 'fork' the pipeline to have a separate watch-version.

Visual Studio's compile-on-save produces almost instantaneous results, and we typically don't need more than that for most of these edit-compile-run cycles.

We develop and test on Windows, but use MSysGit's Bash as commandline. We also run all tests on a Linux machine. So, our source files are stored with CRLF, and the emitted .js file contains CRLF. Having a simple passthrough, or 'exactly preserved' trivia will thus lead to that shebang line ending in CRLF. This doesn't work when trying to run the script in (MSysGit) Bash; many projects seem to have run into this.

On Windows, having the file output just LF would fix it: MSysGit can't use an executable bit, so it appears to read the first line of a file to see if it's a shebang, and if so, effectively assumes it's an executable.

On Linux, it's the other way around: the file would already be output with LF, but it still wouldn't be executable due to the missing +x bit. It's not that weird for compilers to set an executable bit though, that's basically what every C-compiler does too ;)

Not sure how hard this would be to add to the compiler though. I'd gladly give this up for solid CommonJS module support, to be honest ;)

TL;DR I think this would be the ingredients:

Raynos commented 9 years ago

:+1:

redchair123 commented 9 years ago

There seem to be two independent questions here:

1) how should tsc handle files with a shebang line?

2) should tsc explicitly write shebang lines if requested?

To the first question, the node interpreter can handle files with leading shebang lines:

$ cat q.js
#!/usr/bin/env node
console.log("hello world");
$ chmod +x q.js
$ node q.js
hello world
$ ./q.js
hello world

To the second question, a simple compiler flag (--shebang) that adds the line would be sufficient. We already have options that control the output, like the --module option, so this isn't totally crazy

RyanCavanaugh commented 9 years ago

We got confused over which of two scenarios (or maybe both) people wanted here.

Someone might have a tool that compiles and runs a TypeScript file:

#!/usr/bin/env run-ts
var x: string = 'hello world';
console.log(x);

In this case, you wouldn't want #!/usr/bin/env run-ts emitted into the output JS.

Or, someone might want to compile their TS and then run the JS separately:

#!/usr/bin/env node
var x: string = 'hello world';
console.log(x);

In this case, you would want #!/usr/bin/env node emitted into the output JS.

Is one of these much more common than the other?

tomlarkworthy commented 9 years ago

I use typescript to write command line node programs. e.g. https://github.com/firebase/blaze_compiler So the latter is more common for me (I want #/usr/bin/env node emitted into the JS file)

On Tue, May 19, 2015 at 9:49 AM, Ryan Cavanaugh notifications@github.com wrote:

We got confused over which of two scenarios (or maybe both) people wanted here.

Someone might have a tool that compiles and runs a TypeScript file:

/usr/bin/env run-tsvar x: string = 'hello world';console.log(x);

In this case, you wouldn't want #/usr/bin/env run-ts emitted into the output JS.

Or, someone might want to compile their TS and then run the JS separately:

/usr/bin/env nodevar x: string = 'hello world';console.log(x);

` In this case, you *would* want#/usr/bin/env node` emitted into the output JS.

Is one of these much more common than the other?

— Reply to this email directly or view it on GitHub https://github.com/Microsoft/TypeScript/issues/2749#issuecomment-103588482 .

CyrusNajmabadi commented 9 years ago

It seems like we could address this with a general facility to add something to the top of the file we emit. i.e. you should be able to reference some sort of file containing preamble text that you'd like at the top of the emitted file. This would be good for things like copyright headers, shebangs, etc. And it would be nice be cause it would mean that TS wouldn't have to know about things like how people on unix want to execute files.

jdfreder commented 9 years ago

@RyanCavanaugh I think in the first scenario you mention, the "run-ts" tool would need to strip the shebang. My gut tells me the second scenario is more common, but that may due to a personal bias.

@CyrusNajmabadi that could work, but my initial impression is that just allowing shebang lines to pass through is the all-around simpler solution. Aside from leaking Unix-isms into Windows, is there any other issues you see with this approach? I'd like to point out that node.js handles shebangs on Windows without problem and that the npm package.json expects them to be present in files specified in bin, otherwise it won't work as mentioned in this SO response.

I know, my point is node.js & npm specific, but I still think it's important.

jdfreder commented 9 years ago

@RyanCavanaugh yes, but that first line must end with (just) a line-feed, and the emitted file needs to have the executable bit set on *nix.

@poelstra It seems like the appropriate solution here is for you to set the executable bit on your TS file, and TSC should set the same bits on the output file. I think even though it's related to the issue I opened (this one), I think that topic belongs it's own separate issue and shouldn't be discussed in detail here.

poelstra commented 9 years ago

@RyanCavanaugh Like @tomlarkworthy, I would use it in your second way (run the .js, not .ts). For your first scenario, this 'feature' wouldn't be necessary at all, because run-ts could strip the shebang.

Nitpick: the line should start with #!, not just #.

poelstra commented 9 years ago

@jdfreder Technically, the .ts file should not have its executable bit set, because that file cannot be executed.

Given TSC's default behavior to put compiled .js files next to the .ts, this will help with tab-completion on the shell too: it will just find the .js when I would ./he<TAB> for ./helloworld.js, instead of stopping at ./helloworld..

Let's see where this goes, if needed I'll file a separate issue for the executable bit. It will already help on Windows if we have the shebang passthrough by itself, so I certainly wouldn't want to block 'your' topic on it :)

jdfreder commented 9 years ago

so I certainly wouldn't want to block 'your' topic on it :)

Ah thanks :) Exactly why I brought it up.

jesseschalken commented 9 years ago

I have a Node.js project which I converted to TypeScript but now I have to manually add #!/usr/bin/env node to the top of the .js file after every change (which probably breaks the source map). It is not easy for me to arrange to have the .js file executed as node file.js rather than just file.js, so it would be handy if tsc just let the #!... pass through.

basarat commented 9 years ago

I have a Node.js project which I converted to TypeScript but now I have to manually add #!/usr/bin/env node to the top of the .js file

@jesseschalken hopefully its just .js file. In that case you can do what I describe here https://github.com/npm/npm/issues/6674#issuecomment-108132800 Duplicated below:


See https://github.com/Microsoft/TypeScript/blob/master/bin/tsc for an example. Basically have a dummy file without the .js extension and just require the actual .js file.

In file named tsc:

#!/usr/bin/env node
require('./tsc.js')
jesseschalken commented 9 years ago

@basarat Yeah, I have a few things which assume that the repo contains only a single .js file which can be deployed on it's own. I can go to the effort to fix those assumptions, but it would be handy if I didn't have to, that's all.

jesseschalken commented 9 years ago

@RyanCavanaugh I missed why your run-ts example required that tsc didn't emit the #!... line in it's output. I assume the theoretical run-ts command would be running the file through tsc and then to node, in which case it would be fine if the #!... was kept in the output because node ignores it.

It seems to me, after reading this thread, that the original request still stands: That tsc should tolerate a #!... line at the start of an input file and maintain it in the output file.

Regarding line endings: tsc apparently has the --newLine option to specify line endings (#1693), so you can just use that on Windows.

If you need the file to be executable, then you can chmod +x the .js file after the first compile and tsc will apparently keep it after subsequent compiles (according to my testing), and if you commit the .js file Git will also remember that it's executable.

basarat commented 9 years ago

@mhegazy I can implement this quite easily. Basically do it the same we parse out reference comments and store it in SourceFile, and then write it out on emit as is.

This is very similar to how babel does it as well (calls parseShebang at the start, and if the first line matches the regex stores it in a shebang property on what is effectively its sourceFile and then writes it out on emit). Would you be okay with a PR?

DanielRosenwasser commented 9 years ago

@basarat I think the problem is that there's no consensus yet on what should be done with a shebang.

@jesseschalken If a shebang is meant to be preserved, it implies that the .ts file is not run - the resultant .js is. But clearly there is a use case for other tools to run the .ts file directly with no resultant .js file.

One could imagine us omitting shebangs on output, but also allowing a prelude as @CyrusNajmabadi suggested.

basarat commented 9 years ago

I think the problem is that there's no consensus yet on what should be done with a shebang

Which is why I brought up babel to present a precedence. It just preserves it :rose:

mhegazy commented 9 years ago

@basarat we should do that. let me get back to you tomorrow, i will bring it up in the design meeting.

jesseschalken commented 9 years ago

@DanielRosenwasser "But clearly there is a use case for other tools to run the .ts file directly with no resultant .js file."

I must be missing something. The only two ways some tool could "run" a .ts file would be:

mhegazy commented 9 years ago

@basarat feel free to send a PR for this

CyrusNajmabadi commented 9 years ago

@basarat

This is very similar to how babel does it as well (calls parseShebang at the start, and if the first line matches the regex stores it in a shebang property on what is effectively its sourceFile and then writes it out on emit). Would you be okay with a PR?

Sounds good. However, i would prefer you parse it out as trivia instead of adding it as a special field on sourceFile. As an example of where we have specialized trivia parsing, see the code i added so we can detect and handle git merge markers (i.e. "<<<<<< HEAD"). Then we can even do nice stuff like classify this guy properly in the editor and not barf on it while formatting. yadda yadda yadda.

If you wanted to send me the shebang syntax (it's just #! <stuff to end of line> right?), then i'd be happy to whip this up myself.

basarat commented 9 years ago

Then we can even do nice stuff like classify this guy properly in the editor and not barf on it while formatting

:+1: :rose: SyntaxKind.ShebangTrivia

basarat commented 9 years ago

If you wanted to send me the shebang syntax (it's just #! right?), then i'd be happy to whip this up myself.

@CyrusNajmabadi Yup. I have some work on the scanner based on your suggestion that might be helpful https://github.com/Microsoft/TypeScript/compare/master...basarat:feat/shebang?expand=1

Looking forward to your work :rose:

basarat commented 9 years ago

I just PRed it

jdfreder commented 9 years ago

Awesome, thanks for moving forward with this everyone!

mhegazy commented 9 years ago

thanks @basarat

basarat commented 9 years ago

🌹

tomlarkworthy commented 9 years ago

[image: Office dance]

On Tue, Aug 4, 2015 at 2:42 PM, Basarat Ali Syed notifications@github.com wrote:

🌹

— Reply to this email directly or view it on GitHub https://github.com/Microsoft/TypeScript/issues/2749#issuecomment-127770910 .

ghost commented 9 years ago

Thank you for fixing this! :)

woodie commented 8 years ago

I came up with a simple solution, described here. https://medium.com/@JohnWoodell/time-for-typescript-e87b78a796d0#.a1694wfih

DanielRosenwasser commented 8 years ago

@woodie nice. As a heads up, check out our wiki's editor support page to get some vim plugins and more updated sytnax files.