We have the same code working using node
, deno
, and bun
.
E.g.,
bun run index.js
deno run -A --unstable-byonm index.js
node --experimental-default-type=module index.js
which each produce a Signed Web Bundle and that is an Isolated Web App.
We have a node_modules
folder that node
, deno
and bun
each utilize for
module source.
For deno
we pass --unstable-byonm
flag to use the node_modules
folder.
For node
we use the --experimental-default-type=module
flag to use Ecmascript
modules with .js
extension.
- Bun: Executables
- Deno: Compiling Executables
- Node.js Single executable applications
That's it. Let's see how simple or complicated it is to compile a JavaScript application to a single executable containing your source code and the given JavaScript runtime.
bun build ./index.js --compile --outfile=bun_exe
[35ms] bundle 38 modules
[115ms] compile bun_exe
./bun_exe
isolated-app://<ISOLATED_WEB_APP_ID>/
signed.swbn, 8450 bytes.
bun build --compile
works on first run.
deno compile -A --unstable-byonm ./index.js --output=deno_exe
Check file:///home/user/index.js
error: Uncaught Error: Could not find a matching package for 'npm:@types/node' in '/home/user/package.json'. You must specify this as a package.json dependency when the node_modules folder is not managed by Deno.
at ext:deno_tsc/99_main_compiler.js:644:32
at Array.map (<anonymous>)
at Object.resolveTypeReferenceDirectives (ext:deno_tsc/99_main_compiler.js:633:33)
at actualResolveTypeReferenceDirectiveNamesWorker (ext:deno_tsc/00_typescript.js:119495:154)
at resolveTypeReferenceDirectiveNamesWorker (ext:deno_tsc/00_typescript.js:119871:22)
at resolveTypeReferenceDirectiveNamesReusingOldState (ext:deno_tsc/00_typescript.js:120033:16)
at processTypeReferenceDirectives (ext:deno_tsc/00_typescript.js:121349:158)
at findSourceFileWorker (ext:deno_tsc/00_typescript.js:121245:11)
at findSourceFile (ext:deno_tsc/00_typescript.js:121115:22)
at ext:deno_tsc/00_typescript.js:121064:24
Why would deno
, a TypeScript runtime need npm:@types/node
?
Whatever, alright, we'll install npm:@types/node
.
bun add @types/node
bun add v1.0.22 (b400b36c)
installed @types/node@20.10.8
2 packages installed [36.00ms]
bun install
bun install v1.0.22 (b400b36c)
Checked 10 installs across 11 packages (no changes) [29.00ms]
Let's try Deno again, and make sure --output=deno_exe
is not expected to be index.js
deno compile -A --unstable-byonm --unstable --output deno_exe ./index.js
Check file:///home/user/index.js
error: Uncaught Error: Could not find a matching package for 'npm:@types/node' in '/home/user/package.json'. You must specify this as a package.json dependency when the node_modules folder is not managed by Deno.
at ext:deno_tsc/99_main_compiler.js:644:32
at Array.map (<anonymous>)
at Object.resolveTypeReferenceDirectives (ext:deno_tsc/99_main_compiler.js:633:33)
at actualResolveTypeReferenceDirectiveNamesWorker (ext:deno_tsc/00_typescript.js:119495:154)
at resolveTypeReferenceDirectiveNamesWorker (ext:deno_tsc/00_typescript.js:119871:22)
at resolveTypeReferenceDirectiveNamesReusingOldState (ext:deno_tsc/00_typescript.js:120033:16)
at processTypeReferenceDirectives (ext:deno_tsc/00_typescript.js:121349:158)
at findSourceFileWorker (ext:deno_tsc/00_typescript.js:121245:11)
at findSourceFile (ext:deno_tsc/00_typescript.js:121115:22)
at ext:deno_tsc/00_typescript.js:121064:24
deno compile -A --unstable-byonm --unstable --output deno_exe ./index.js
Check file:///home/user/index.js
Compile file:///home/user/index.js to deno_exe
Alright! Deno created the self-contained executable!
Let's run the output executable file
./deno_exe
error: Parsing version constraints in the application-level package.json is more strict at the moment.
Not implemented scheme 'https'
Foiled again.
My estimation is that the error has to do with an entry in package.json
is
pointing to a .git
extension on GitHub. I have not confirmed that is the case, yet.
I got deno
to compile by using an import map, deno.json
with the NPM mime
package, which is CommonJS, pointing to "https://esm.sh/mime@2.6.0"
; making sure the cborg
package points to cborg.js
in the esm
folder in the library; and including "node:"
specifier before "fs"
and "path"
.
deno compile -A --unstable-byonm --unstable --output deno_exe ./index.js
Check file:///home/user/index.js
Compile file:///home/user/index.js to deno_exe
./deno_exe
isolated-app://efjntlnfcij5k2sourpthwhbyqhfxy34bihkw4bimnxrl6hdwsfqaaic/
signed.swbn, 8524 bytes.
There's a bit to unpack and re-read in the Node.js version. Some key points which essentially prevent us from proceeding as-is
The single executable application feature currently only supports running a single embedded script using the CommonJS module system.
I'm using Ecmascript Modules, not CommonJS. We should try anyway.
echo '{ "main": "index.js", "output": "sea-prep.blob" }' > sea-config.json
bun x postject node_exe NODE_SEA_BLOB sea-prep.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
Error: Can't read resource file
I bundled index.js
to a browser format with bun
bun build index.js --target=browser --outfile bun_node_bundle.js
bun_node_bundle.js 1103.25 KB
75 | const parsedAssetPath = path.parse(relativeAssetPath);
^
warn: Browser polyfill for module "node:path" doesn't have a matching export named "parse"
at /home/user/wbn-bundle.js:75:32
75 | const parsedAssetPath = path.parse(relativeAssetPath);
^
note: Bun's bundler defaults to browser builds instead of node or bun builds. If you want to use node or bun builds, you can set the target to "node" or "bun" in the bundler options.
at /home/user/wbn-bundle.js:75:32
96 | const filePath = path.join(dir, fileName);
^
warn: Browser polyfill for module "node:path" doesn't have a matching export named "join"
at /home/user/wbn-bundle.js:96:27
96 | const filePath = path.join(dir, fileName);
^
note: Bun's bundler defaults to browser builds instead of node or bun builds. If you want to use node or bun builds, you can set the target to "node" or "bun" in the bundler options.
at /home/user/wbn-bundle.js:96:27
[248ms] bundle 41 modules
then ran postject
bun x postject node_exe NODE_SEA_BLOB sea-prep.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
Start injection of NODE_SEA_BLOB in node_exe...
warning: Can't find string offset for section name '.note.100'
warning: Can't find string offset for section name '.note.100'
warning: Can't find string offset for section name '.note.100'
warning: Can't find string offset for section name '.note.100'
warning: Can't find string offset for section name '.note.100'
warning: Can't find string offset for section name '.note.100'
warning: Can't find string offset for section name '.note.100'
warning: Can't find string offset for section name '.note.100'
warning: Can't find string offset for section name '.note.100'
warning: Can't find string offset for section name '.note.100'
warning: Can't find string offset for section name '.note.100'
warning: Can't find string offset for section name '.note'
💉 Injection done!
then ran the single executable application
./node_exe
bun_node_bundle.js:21686
globalThis.Buffer ??= (await Promise.resolve().then(() => (init_buffer(), exports_buffer))).Buffer;
^^^^^^^
SyntaxError: Unexpected identifier 'Promise'
at internalCompileFunction (node:internal/vm:77:18)
at wrapSafe (node:internal/modules/cjs/loader:1290:20)
at embedderRunCjs (node:internal/util/embedding:19:27)
at node:internal/main/embedding:18:8
Node.js v22.0.0-nightly2024010657c22e4a22
which throws a syntax error for the bundled representation of globalThis.Buffer ??= (await import("node:buffer")).Buffer
in the original script.
-
bun
successfully compiled the standalone executable. Afterstrip bun
the resulting executable is 89.1 MB. -
deno
compiled the standalong executable, after installing@types/node
(which also installsundici-types
), however, the standalone executable throws and error. Update: Gotdeno
to compile a working executable. Afterstrip deno
the resulting executable is 98.1 MB. -
node
only supports CommonJS. We tried anyway where we know the source is Ecmascript Modules.bun x
equivalent ofnpx postject hello NODE_SEA_BLOB sea-prep.blob \ --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
per the Node.js Single Executable Application documentation throws an error.
These are the empirical results I'm sharing experimenting and testing compiling a standalone executable that achieves the same result when run in the given JavaScript runtime using the same source code.
If you don't need the node_modules dir as an input, it's not needed for compiling to a binary