Execute ES Modules on the CLI

Jonathan Neal shared this little snippet on Twitter:

Here’s the code:

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

import process from 'process';
const { argv } = process

Save your file as command.js and you can run bash command.js on the shell.


What intrigued me here was this special shebang at the very top. I did expect to see #!/usr/bin/env node in there, but not the script to be fed into node as an argument again … weird, right?

Going down the rabbit hole, I found this post from 2014 that explains the funky version. There’s two commands in there, split by a ;

  1. ":" //#
  2. exec /usr/bin/env node --input-type=module - "$@" > "$0"

The first part does nothing beyond expanding arguments (//) and a no-op. The # indicates the start of a comment, but the comment itself remains empty.

The second part feeds the scriptname ($0) and the rest of the arguments ($@) into the node binary. Via the --input-type=module flag, node is configured to treat the file itself as an ES Module.


Digging at bit deeper I learned that there are three ways to configure node to treat your file as an ES Module:

  1. Use the --input-type=module

  2. Give your file the .mjs extension

  3. Place a package.json with the follow contents near the file

      "type": "module"

I like the --input-type=module version with the shebang trick Jonathan shared there, as it requires no extra files (package.json) and allows you to keep the .js extension (or even drop it entirely).


🔥 Like what you see? Want to stay in the loop? Here's how:

Published by Bramus!

Bramus is a frontend web developer from Belgium, working as a Chrome Developer Relations Engineer at Google. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since (more …)

Leave a comment

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.