Skip to content

Instantly share code, notes, and snippets.

@WebReflection
Last active March 4, 2024 20:55
Show Gist options
  • Select an option

  • Save WebReflection/8840ec29d296f2fa98d8be0102f08590 to your computer and use it in GitHub Desktop.

Select an option

Save WebReflection/8840ec29d296f2fa98d8be0102f08590 to your computer and use it in GitHub Desktop.
NodeJS Executable Standalone Module

In this tweet I've shown how the incoming version of NodeJS could be used to run a standalone file as ESM through the flag --input-type=module.

This gist is an attempt to describe the technique I've used.

TL;DR - The Solution

#!/usr/bin/env sh
J=S//;sed "1,2d" "$0"|echo "\n\n$(cat -)"|node --input-type=module "$@";exit $?
/* your 100% ESM code here */

The First line

It's a regular hashbang, that could usually be written either as #!/bin/sh or, in a wider compatible way, as #!/usr/bin/env sh, to carry on the environment, and execute via sh, or even bash.

This first line requires that the file is executable, something easy to obtain via chmod, as in chmod +x filename.

The Second Line

Technically speaking, the J=S//; part of the string could be omitted, but then any JavaScript editor would show errors due usage of incompatible syntax.

Using J=S//; as prefix instead, would simply assign, in the shell world, the string S// to a variable J, but it won't trigger errors, neither it will ever execute, in the NodeJS world.

However, it will start an inline comment for your IDE of choice, so that no errors would be shown.

In shell world, it will simply keep executing whatever is after the semicolon.

Keep in mind, shell scripts, as well as bash scripts, execute as a stream, instead of requiring a while parsing, with syntax validation, upfront, meaning that exiting any file at any time, won't cause any syntax error for whatever content is presetn after the program ended.

The sed part

The micro utility sed is a quite universal way to replace, hence transform, some content.

sed "1,2d" "$0"

Above command strips out the first two lines from the file specified by $0. In shell, as well as in bash, it's always good to expand variables that could contain spaces via double quotes, which is why $0 is wrapped as "$0", so that any path would be accepted.

In shell, as in bash, the $0 refers to the file that is currently executing, like __filename would be in CommonJS.

The pipe

In shell, as in bash, the pipe operator | streams output produced by the left hand side, as right hand side input.

In this example, the pipe passes the sed output, the file without the first two lines, to the echo command.

The echo command

The echo command simply prints, usually via the standard output, some content.

The reason echo is part of this technique is that we'd like to maintain the line number, in case any error occurs during NodeJS execution.

I'm not sure sed could do that for me right away, but echo works just as well.

It's important to use doublequotes to concatenate two new lines, "\n\n", and the received standard input, throught the pipe, via cat.

In shell, as in bash, double quotes can contain the output produced by system calls, as it is for echo "1$(echo 2)3", simply outputting 123.

The cat utility

Similarly to sed, cat is the most common way to stream some content to the standard outoput.

You can, as example, see the content of any file via cat filename, but it's usually more handy combined with other tools, like less, as example, so that you can read through the console even big files via cat filename | less.

In this specific case, cat is used due its handy - feature that simply passes through whatever input the current execution is receiving.

To recap, echo "\n\n$(cat -)" will pass along an output containing two new lines \n plus whatever sed outputted before.

The node execution

After long discussions, it became obvious that the previously proposed flag --entry-type=module would've caused more confusion than expected, because it wasn't equivalent to the "type": "module" field in a package.json.

Accordingly, since node --entry-type=module filename wouldn't have run filename as if it was within a module scoped as ESM via its package.json, they decide to remove such flag and use instead one that would only accept an input, which is specially handy for evaluation -e cases.

But fear not, this technique perfectly addresses that evaluation case, making the file itself, the source of the ESM code that will be evaluated.

node --input-type=module "$@"

Above shell, or bash, command switches node input parsing goal as module, which practically means CommonJS is not available, and everything would run as pure ESM, even if an input couldn't really ever be used as module, but that's another story (naming is hard, and this whole module story is not scope of this gist).

The "$@" in shell, as well as in bash, would properly expand any optional extra arguments passed to the initial, shell, program.

The exit

Coincidentally with the end of this gist, the last part of the technique ;exit $? simply split in two different commands the previous node execution, and the exiting of the initial shell program itself, so that shell won't parse any extra content and it will exit passing along the same exit code node produced.

This is thanks to the $? special shell, and bash, variable.

@chardskarth
Copy link

chardskarth commented Aug 16, 2022

@WebReflection , this is awesome, thousand thanks for this! 🙏 💯

However, I can't seem to pass a param. The "$@" doesn't work and is instead treating params as if I was requiring/running other scripts.

tmp sPkJwmUv


EDIT, Workaround:

I was able to pass a parameter by adding a string substitution instead. and seeing that "$@" doesn't seem to work I removed it:

#!/usr/bin/env sh
J=S//;title=$1
J=S//;cd $( npm root -g ) && echo "\n\n$(sed -e "1,3d" -e "s/\$notestitle/'$title'/" "$0")"|node --input-type=module;exit $?
/* your 100% ESM code from this line on */

then added an undefined variable in my esm script, $notestitle.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment