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: