Open-Source Wikis

/

Deno

/

Features

/

Standalone binary (`deno compile`)

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:

  1. Build the module graph for the entrypoint (using the same cli/graph_util.rs as deno run).
  2. Run type checks if requested.
  3. Select the right base binary for the target (cross-compile support).
  4. Serialize the graph to an eszip via libs/eszip.
  5. Append the eszip + a fixed metadata footer to the base binary.
  6. 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:

  1. The binary's first instruction is denort's main.
  2. cli/rt/ reads its own bytes, locates the appended eszip via the trailer.
  3. It builds a "standalone" module loader (cli/standalone/) that reads sources from the in-memory eszip rather than disk or HTTP.
  4. It constructs a MainWorker (from runtime/) 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 eszips
  • cli/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 --include or 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. --include can 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-msvc
  • x86_64-apple-darwin
  • aarch64-apple-darwin
  • x86_64-unknown-linux-gnu
  • aarch64-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 for deno compile.
  • The integration tests around eszip round-tripping live in libs/eszip/'s own tests.

Entry points for modification

  • eszip format changelibs/eszip/v2.rs (or bump the version with a new module). Backwards compat is mandatory.
  • Compile-side flagcli/args/flags.rs for the flag, cli/tools/compile.rs for the handling.
  • Runtime-side changecli/rt/ and cli/standalone/.
  • VFS or --include semanticscli/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.

Standalone binary (`deno compile`) – Deno wiki | Factory