Jonathan Neal shared this little snippet on Twitter:
Execute JavaScript modules like:
bash ./command.js
1. Add the funky header.
2. Optional: Omit the extension to not write `.js`.
3. Optional: `chmod +x command` to not write `bash `.https://t.co/rhlPg2XPRJ pic.twitter.com/nbAvTFtt0w— Jonathan Neal (@jon_neal) July 25, 2021
Here’s the code:
":" //#;exec /usr/bin/env node --input-type=module - "$@" < "$0"
import process from 'process';
const { argv } = process
console.log(argv)
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 ;
":" //#
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:
-
Use the
--input-type=module
-
Give your file the
.mjs
extension -
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: