k1n70a
Last Updated: February 25, 2016
·
675
· lazurski
Tadeusz lazurski square

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 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, 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. 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. 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. 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 and also a real project that uses this trick: lzrski/npm-git-install.

Say Thanks
Respond