18

How can an ES6 module be run as a script in Node?

When I try this shebang I get an error:

#!/usr/bin/env node --experimental-modules

/usr/bin/env: ‘node --experimental-modules’: No such file or directory

If I use this shebang it has syntax errors (of course):

#!/usr/bin/env node

SyntaxError: Unexpected token import

The work around I'm using is to use a shell script to call the module:

#!/usr/bin/env sh

BASEDIR=$( dirname "$0" )
node --experimental-modules $BASEDIR/script.mjs "$@"

Is it possible to get this working without a second file?

curiousdannii
  • 1,658
  • 1
  • 25
  • 40
  • What version of node are you using? Apart from a couple of rarely used features, and apart from tail call recursion, node.js has almost full support of ES6 by default since July 2017. The easiest solution is probably to upgrade node.js. I personally use the `n` package (`npm install -g n` then `n stable`) – slebetman Jan 11 '18 at 02:45
  • @slebetman You must still pass the --experimental-modules flag. They're anticipating this will be enabled by default only in Node v10. – curiousdannii Jan 11 '18 at 02:47

3 Answers3

14

I've patched Ishan Thilina Somasiri solution to work with Node 13 without the .mjs extension:

#!/usr/bin/env bash
":" //# comment; exec /usr/bin/env node --input-type=module - "$@" < "$0"

import { hostname } from 'os';

console.log(hostname());

The trick is pretty much the same, but using stdin, which is the only documented way when you don't have a package.json nor the .mjs extension. Thus, a standalone extensionless script.

However, global variables like __dirname or __filename won't be available.

gibatronic
  • 1,661
  • 2
  • 17
  • 22
9

You need to pass the argument to node on shebang. This is explained very well in the article at http://sambal.org/2014/02/passing-options-node-shebang-line/.

The code will be as follows.

#!/bin/sh 
":" //# comment; exec /usr/bin/env node --harmony "$0" "$@"

console.log("It Works!");

I used harmony instead of --experimental-modules. The explanation from the web site is follows.

The #!/bin/sh causes the script to be identified as a shell script, and passed to /bin/sh for execution. /bin/sh reads and executes scripts one line at a time, and we’re taking advantage of that below.

The second line, as interpreted by the shell, consists of two commands.

  • 2a. The first command is ":", which is the quoted version of the rarely-used bash command :, which means “expand arguments and no-op”. The only argument to : is //, which is a valid path. The following # is a bash comment, which is valid until the command separator ;.

  • 2b. The second command is exec /usr/bin/env node --noharmony "$0" "$@" which executes the node interpreter with the desired arguments and passes argument 0 (this script file) and the rest of the arguments to the bash script ("$@")

The exec causes the bash process to be replaced by the node process, so bash does not attempt to process any further lines.

tadman
  • 208,517
  • 23
  • 234
  • 262
Ishan Thilina Somasiri
  • 1,179
  • 1
  • 12
  • 24
  • This is also great because even though that line never gets run by JavaScript, the syntax is valid. `//` makes it work. – tadman Oct 17 '19 at 17:29
3

I'm not sure exactly when it changed, but in recent Node versions no special care is needed, if the script is part of a package (ie, there is a package.json file in the folder or any parent folder). Just ensure these steps are followed:

  1. In package.json, set "type": "module"
  2. Give the file a .js extension
  3. Use the shebang #!/usr/bin/env node
curiousdannii
  • 1,658
  • 1
  • 25
  • 40