denoland/deno
Standalone binary (deno compile)
Active contributors: Bartek Iwańczuk, David Sherret, Luca Casonato
Purpose
deno compile produces a single self-contained executable that bundles a user's module graph and a copy of the Deno runtime. The output runs on systems that have no Deno installed and no internet access — useful for shipping CLI tools, Lambda images, and anything else where you don't want runtime dependencies.
The implementation spans cli/tools/compile.rs (the orchestrator), libs/eszip (the archive format), cli/standalone/ (the runtime side), and the denort binary in cli/rt/ (the entry point of the compiled output).
How it works
graph TD
User["deno compile main.ts"] --> Compile["cli/tools/compile.rs"]
Compile --> Graph["build module graph<br/>(cli/graph_util.rs)"]
Graph --> Eszip["serialize to eszip<br/>(libs/eszip)"]
Eszip --> Bin{"target binary"}
Compile -->|select| Bin
Bin --> Embed["append eszip to denort"]
Embed --> Out["./mybin (or mybin.exe)"]
Run["./mybin args..."] --> Denort["cli/rt/ — denort entry"]
Denort --> Locate["locate appended eszip"]
Locate --> Vfs["materialize VFS from eszip"]
Vfs --> Standalone["cli/standalone/<br/>standalone module loader"]
Standalone --> Worker["MainWorker (runtime/)"]
Worker --> UserCode["evaluate user code"]Components
cli/tools/compile.rs
Driver for the subcommand. Steps:
- Build the module graph for the entrypoint (using the same
cli/graph_util.rsasdeno run). - Run type checks if requested.
- Select the right base binary for the target (cross-compile support).
- Serialize the graph to an eszip via
libs/eszip. - Append the eszip + a fixed metadata footer to the base binary.
- Set the output's executable bit and write to disk.
Recently added flags include --include for stitching in extra files (data, native libraries) and cross-platform options for the target triple.
libs/eszip
The archive format. libs/eszip/v2.rs (4,018 lines) is the current version. An eszip is roughly:
- A header with version + checksum
- A list of modules: URL, kind (JS/TS/JSON/etc.), source bytes, optional source map
- Optional npm-package metadata for npm specifiers in the graph
The format is append-only; eszips can be stitched together (used for sharing common bases). Backwards-compat with older versions is maintained because Deno Deploy ships eszips long-term.
denort and cli/rt/
denort is the runtime side of deno compile — it's a separate binary built from cli/rt/. The compiled output is essentially denort with an eszip appended. When you run the output:
- The binary's first instruction is
denort'smain. cli/rt/reads its own bytes, locates the appended eszip via the trailer.- It builds a "standalone" module loader (
cli/standalone/) that reads sources from the in-memory eszip rather than disk or HTTP. - It constructs a
MainWorker(fromruntime/) and evaluates the entrypoint.
cli/rt/ is a lighter-weight version of cli/. It doesn't bundle the LSP, the type checker, the formatter, etc. — only what's needed to run an existing graph.
cli/standalone/
The standalone-mode glue:
cli/standalone/binary.rs— code for embedding/extracting eszipscli/standalone/virtual_fs.rs(where present) — virtual filesystem backed by the eszip- The standalone module loader
ext/rt_helper
A small extension crate (ext/rt_helper/, 1 Rust file) with denort-specific glue ops.
What's bundled vs not
Bundled into the output:
- The full Deno runtime (V8, all extensions, the JS bootstrap)
- Every module the entrypoint statically reaches
- Source maps
- npm packages used by the graph (their
node_modules/layout is reconstructed at runtime from the eszip)
NOT bundled:
- Code that's loaded only via dynamic
import()with non-static specifiers — these have to be either compiled in via--includeor available at runtime. - The system's V8 — V8 is statically linked into the binary.
- Native shared libraries — if your code calls
Deno.dlopen('libfoo.so'), that's a runtime dependency.--includecan stitch in the .so for the standalone VFS.
Cross-platform
The base binary for the target platform is downloaded by deno compile based on --target:
x86_64-pc-windows-msvcx86_64-apple-darwinaarch64-apple-darwinx86_64-unknown-linux-gnuaarch64-unknown-linux-gnu
The CLI fetches the right denort for that target and builds the output on top of it. This means cross-compiling from a Linux machine to a Windows binary works without a Windows toolchain.
Permissions in compiled binaries
Permission flags can be baked in at compile time (deno compile --allow-read script.ts produces a binary that runs with --allow-read granted). The flags are stored in the eszip metadata and applied by denort before user code runs. Users can still grant more via runtime flags but can't reduce baked-in permissions.
Tests
tests/specs/compile/— spec tests fordeno compile.- The integration tests around eszip round-tripping live in
libs/eszip/'s own tests.
Entry points for modification
- eszip format change —
libs/eszip/v2.rs(or bump the version with a new module). Backwards compat is mandatory. - Compile-side flag —
cli/args/flags.rsfor the flag,cli/tools/compile.rsfor the handling. - Runtime-side change —
cli/rt/andcli/standalone/. - VFS or
--includesemantics —cli/standalone/.
Key source files
| File | Purpose |
|---|---|
cli/tools/compile.rs |
The deno compile subcommand (~20K bytes) |
cli/standalone/ |
Standalone-mode module loader + VFS |
cli/rt/ |
The denort binary's source |
libs/eszip/v2.rs |
eszip v2 format (4,018 lines) |
libs/eszip/ |
Other format versions, helper API |
ext/rt_helper/ |
denort-specific extension |
ext/bundle/ |
Hooks used by the bundle subcommand (related but separate from compile) |
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.