Compiling executables from CoffeeScript
You may know that you can prepare command line executables for others to install with NPM. It's easy. Just add bin
field to your package.json
file:
{ "bin" : { "myapp" : "./cli.js" } }
Here is more about it: https://docs.npmjs.com/files/package.json#bin
Now in order for this to work you need to provide a [so-called shebang
][shebang] at the beginning of cli.js
file, so that your shell knows how to handle it. You can't expect it to understand JavaScript
on it's own.
Here is how the cli.js
file may look like:
#!/usr/bin/env node
console.log("Hello, I'm bin :)");
Now, from the projects directory you can install it like that:
npm install --global .
or if you [publish it to npm repository][publish], then anybody can install it like that:
npm install --global coffee-bin
It will install the myapp
command on the system, that one can execute on the command line, just like that:
myapp
With CoffeeScript
You may like to write your program in CoffeeScript. Recently I wanted to have a command line tool in one of my Coffee projects and I've promptly run into [some troubles with shebang][issue]. Let me dissect it for you:
If you just add the shebang
to your cli.coffee
file:
#!/usr/bin/env node
console.log "Hello, I'm a coffee bin :)"
and then compile it as usual:
coffee --compile cli.coffee
then you will get this:
// Generated by CoffeeScript 1.10.0
(function() {
console.log("Hello, I'm a coffee bin :)");
}).call(this);
If you try to run myapp
, you will get following errors:
myapp: line 1: //: Is a directory
myapp: line 2: syntax error near unexpected token `('
myapp: line 2: `(function() {'
Note that there is no trace of shebang in the compiled JavaScript. That's because in CoffeeScript everything starting with a #
character is a comment and won't make it to the compiled JS. This won't work.
You can however add so called [Embedded JavaScript, using backticks][embedded]. Whatever is inside will just get passed verbatim to the compiled code. Let's use this to add a shebang
that survives compilation!
`#!/usr/bin/env node`
console.log "Hello, I'm a coffee bin :)"
Then compile and fail again:
// Generated by CoffeeScript 1.10.0
(function() {
#!/usr/bin/env node;
console.log("Hello, I'm a coffee bin :)");
}).call(this);
This time there is a shebang
, but it's not on the first line. That's because by default CoffeeScript compiler wraps every file in so called [Immediately-Invoked Function Expression][iife]. Usually it's a good thing, but in a CLI we don't need that and it swallows our hashbang
. Easy to solve, just call the compiler with --bare
option.
Second thing is the Generated by CoffeeScript
comment on the first line. This have to go as well. Add --no-header
and you will be almost fine with JS like that:
#!/usr/bin/env node;
console.log("Hello, I'm a coffee bin :)");
Almost. Executing your script now will give you:
/usr/bin/env: node;: No such file or directory
There is a semicolon at the end of our shebang
line and shells won't stand it. The CoffeeScript compiler added it and it had it's reasons for that. The solution is little hacky, but very easy. Just add a newline before the termination backtick. The newline will be preserved and the semicolon will go to it's own line. There it won't hurt anybody, because in JavaScript you can have as many semicolons as you like.
TL/DR
That's how your source should look like:
`#!/usr/bin/env node
`
console.log "Hello, I'm a coffee bin :)"
You compile it like that:
coffee --compile --bare --no-header cli.coffee
And this is what you get:
#!/usr/bin/env node
;
console.log("Hello, I'm a coffee bin :)");
It works :)
You can see [this all wrapped up on GitHub][wrapped] and also a real project that uses this trick: [lzrski/npm-git-install][npm-git-install].
[shebang]: https://en.wikipedia.org/wiki/Shebang_(Unix)
[publish]: https://docs.npmjs.com/getting-started/publishing-npm-packages
[issue]: https://github.com/jashkenas/coffeescript/issues/2215
[embedded]: http://coffeescript.org/#embedded
[iife]: http://benalman.com/news/2010/11/immediately-invoked-function-expression/
[wrapped]: https://github.com/lzrski/coffee-bin
[npm-git-install]: https://github.com/lzrski/npm-git-install