ENHANCEMENT: Standalone executable compile option for local machines
LLRT, as many have already noticed, is the smallest JavaScript runtime implementing the largest number of Node.js APIs and WinterCG proposals. Major runtimes like Bun and Deno approach feature completeness and Node.js compatibility, but none match LLRT's current usable feature set within such a small footprint.
Standalone executables weren't part of LLRT's initial design. However, its local binaries (i.e. non-Lambda and non-Container) work effectively as a JavaScript CLI runtime and REPL. This is unsurprising, given that the underlying QuickJS engine provides these features and the option to compile standalone executables.
Just look at the current standalone executable sizes between Bun, Deno, and LLRT (LLRT + bytecode size, hypothetical)
| Windows x64 Runtime | Standalone Executable Size |
|---|---|
| Bun | 107 MB |
| Deno | 81 MB |
| LLRT + bytecode | 9 MB |
| LLRT, upx -9 --ultra-brute, + bytecode | 3 MB |
Here are some of the use cases for a tiny standalone executable runtime:
- rapid prototyping of CLI programs
- distribution of development prototypes
- teaching JavaScript in K-12, without the burden of the whole package management ecosystem
- alternative to Python or Bash scripting for day-to-day administration task
- game development
tjs is also a very small js runtime
llrt's small size and rust's cross-platform compilation also make it very suitable for running some dynamic scripts in resource-constrained environments such as embedded systems and routers. These devices usually have only 50mb of storage space.
https://github.com/saghul/txiki.js
https://github.com/ahaoboy/js-engine-benchmark
@darcyclarke @jasongodev Not quite sure what you mean by "Standalone executable compile option".
Something like llrt --compile --target arm64 bundle.js -o llrt-executable?
@richarddavison yes, something like what you shared would be ideal.
If it's not clear, we want the program compiled with the runtime. Today, llrt compile only produces an optimized program meaning that you still have to have llrt somewhere on your system.
As @jasongodev pointed out, bun, deno & even node*WIP have this ability to bundle/compile the runtime & program together but the size is massive compared to what llrt could produce. If you added this, I could see many dev tools implementing their CLIs with llrt as it would massively shrink their package distributions as well as have comparative if not better performance to bun (at least in the benchmarks I've run on short-lived tasks - again, the ideal for dev tools/CLI use-cases).
I know that today llrt distributes itself as a target-specific binary so it may be a big ask for cross-compilation (not sure). That said, it would be nice to default generate all known/supported targets with some kind of common target prefix/suffix & be able to pass one or many targets to overwrite that default (ie. let me run llrt --compile once with the right config instead of n times & in n envs to generate all required targets).
Afaik right now the relevant supported targets would be:
darwin-arm64darwin-x64linux-arm64linux-x64windows-x64
Note: I believe the
container&lambdatargets are specific to hosted envs (not sure what's different about them) which I might suggest not making part of a default target set for this.
refs. https://bun.sh/docs/bundler/executables, https://docs.deno.com/runtime/reference/cli/compile/ & https://nodejs.org/api/single-executable-applications.html
P.S. I feel like you could reuse the existing llrt compile command & just add a flag like --with-runtime or --executable as a non-breaking change & the expected output would be the program with the runtime you're using in that moment. This would get us 90% the way there without nice defaults/cross-compilation which I imagine could take much longer to support.
I implemented this feature based on llrt. If anyone needs it, you can check out this repo:
https://github.com/Ray-D-Song/lexe