ende compile
Flags
As with ende install, the runtime flags
used to execute the script must be specified at compilation time. This includes
permission flags.
ende compile --allow-read --allow-net jsr:@ende/http/file-server
Script arguments can be partially embedded.
ende compile --allow-read --allow-net jsr:@ende/http/file-server -p 8080
./file_server --help
Watch mode
Pass --watch to rebuild the executable whenever a file in the compile graph
changes:
ende compile --watch main.ts
Use --watch-exclude to keep specific paths from triggering a rebuild, and
--no-clear-screen to preserve the terminal output between rebuilds:
ende compile --watch --watch-exclude=./dist --no-clear-screen main.ts
Cross Compilation
You can cross-compile binaries for other platforms by using the --target flag.
# Cross compile for Apple Silicon
ende compile --target aarch64-apple-darwin main.ts
# Cross compile for Windows with an icon
ende compile --target x86_64-pc-windows-msvc --icon ./icon.ico main.ts
Supported Targets
Ende supports cross compiling to all targets regardless of the host platform.
| OS | Architecture | Target |
|---|---|---|
| Windows | x86_64 | x86_64-pc-windows-msvc |
| Windows | ARM64 | aarch64-pc-windows-msvc |
| macOS | x86_64 | x86_64-apple-darwin |
| macOS | ARM64 | aarch64-apple-darwin |
| Linux | x86_64 | x86_64-unknown-linux-gnu |
| Linux | ARM64 | aarch64-unknown-linux-gnu |
aarch64-pc-windows-msvc (Windows on ARM) is supported .
The endert binary
ende compile embeds your program into endert ("Ende runtime"): a stripped
build of Ende that contains only what's needed to run a compiled program, with
none of the tooling subcommands. Using endert as the base instead of the full
ende binary is what keeps compiled executables smaller.
The first time you compile for a given Ende version and target, Ende downloads
the matching endert-<target>.zip from dl.ende.sh and caches it in
ENDE_DIR. This is also how cross-compilation works: compiling with --target
fetches that platform's endert. Subsequent compiles reuse the cached binary
and work offline.
To use a custom or locally built runtime as the base, set the ENDERT_BIN
environment variable to its path. Ende also picks up a endert binary placed
next to the ende executable.
Choosing a JavaScript engine
Caution
--engine quickjs is experimental and subject to change.
Compiled binaries run on V8 by default. The --engine flag lets you build
against QuickJS instead, which produces a smaller executable:
# Default, same as omitting the flag
ende compile --engine v8 main.ts
# Build a binary that runs on QuickJS
ende compile --engine quickjs main.ts
The same flag is available on ende desktop,
where it selects the engine embedded in the app's runtime library.
Trade-offs to weigh before shipping a QuickJS binary:
- Security updates. The QuickJS backend does not receive the same security updates as V8. Don't use it for programs that run untrusted input.
- Performance. QuickJS is an interpreter with no JIT, so compute-heavy code runs slower than on V8. Startup and memory use are lower.
- Maturity. The backend is experimental; behavior differences from V8 are possible, so test your program with the engine you intend to ship.
Each engine has its own prebuilt runtime: with --engine quickjs, Ende
downloads endert-quickjs-<target>.zip (or libendert-quickjs-<target>.zip for
ende desktop) instead of the default endert-<target>.zip. Both variants are
published for every supported target, so cross-compilation
works the same way. If you override the base runtime with ENDERT_BIN (or a
endert binary next to ende), that binary is used as-is: --engine is then
ignored and Ende prints a warning.
Icons
It is possible to add an icon to the executable by using the --icon flag when
targeting Windows. The icon must be in the .ico format.
ende compile --icon icon.ico main.ts
# Cross compilation with icon
ende compile --target x86_64-pc-windows-msvc --icon ./icon.ico main.ts
Dynamic Imports
By default, statically analyzable dynamic imports (imports that have the string
literal within the import("...") call expression) will be included in the
output.
// calculator.ts and its dependencies will be included in the binary
const calculator = await import("./calculator.ts");
But non-statically analyzable dynamic imports won't:
const specifier = condition ? "./calc.ts" : "./better_calc.ts";
const calculator = await import(specifier);
To include non-statically analyzable dynamic imports, specify an
--include <path> flag.
ende compile --include calc.ts --include better_calc.ts main.ts
Including Data Files or Directories
You can include files or directories in the executable by specifying them via
the --include <path> flag.
ende compile --include names.csv --include data main.ts
Then read the file relative to the directory path of the current module via
import.meta.dirname:
// main.ts
const names = Ende.readTextFileSync(import.meta.dirname + "/names.csv");
const dataFiles = Ende.readDirSync(import.meta.dirname + "/data");
// use names and dataFiles here
Note this currently only works for files on the file system and not remote files.
--include treats embedded .js and .ts files as module-graph roots, so it
resolves and transpiles them. To embed files exactly as they are, without any
module resolution, use --include-as-is instead. This is the right choice for
pre-built frontend bundles (for example Vite or webpack output) that are already
processed and would fail to resolve as Ende modules:
ende compile --include-as-is ./dist main.ts
Configuring include / exclude in ende.json
The --include and --exclude paths can be set declaratively in ende.json so
you don't have to repeat them on every ende compile invocation:
{
"compile": {
"include": ["names.csv", "data", "worker.ts"],
"exclude": ["data/secrets", "**/*.test.ts"]
}
}CLI flags are merged with the config: --include and --exclude add to the
lists in ende.json rather than replacing them. See the
Compile config section in the
configuration guide for more details, including how to declare permissions on
the same block.
Workers
Similarly to non-statically analyzable dynamic imports, code for workers is not included in the compiled executable by default. There are two ways to include workers:
- Use the
--include <path>flag to include the worker code.
ende compile --include worker.ts main.ts
- Import worker module using a statically analyzable import.
// main.ts
import "./worker.ts";
ende compile main.ts
Self-Extracting Executables
By default, compiled executables serve embedded files from an in-memory virtual
file system. The --self-extracting flag changes this behavior so that the
binary extracts all embedded files to disk on first run and uses real file
system operations at runtime.
ende compile --self-extracting main.ts
This is useful for scenarios where code needs real files on disk, such as native addons or native code that reads relative files.
The extraction directory is chosen in order of preference:
<exe_dir>/.<exe_name>/<hash>/(next to the compiled binary)- Platform data directory fallback:
- Linux:
$XDG_DATA_HOME/<exe_name>/<hash>or~/.local/share/<exe_name>/<hash> - macOS:
~/Library/Application Support/<exe_name>/<hash> - Windows:
%LOCALAPPDATA%\<exe_name>\<hash>
- Linux:
Files are only extracted once — subsequent runs reuse the extracted directory if it already exists and the hash matches.
Trade-offs
Self-extracting mode enables broader compatibility, but comes with some trade-offs:
- Initial startup cost: The first run takes longer due to file extraction.
- Disk usage: Extracted files take up additional space on disk.
- Memory usage: Higher memory usage since embedded content can no longer be referenced as static data.
- Tamper risk: Users or other code can modify the extracted files on disk.
Code Signing
macOS
By default, on macOS, the compiled executable will be signed using an ad-hoc
signature which is the equivalent of running codesign -s -:
ende compile -o main main.ts
codesign --verify -vv ./main
./main: valid on disk
./main: satisfies its Designated Requirement
You can specify a signing identity when code signing the executable just like you would do with any other macOS executable:
codesign -s "Developer ID Application: Your Name" ./main
Refer to the official documentation for more information on codesigning and notarization on macOS.
Windows
On Windows, the compiled executable can be signed using the SignTool.exe
utility.
ende compile -o main.exe main.ts
signtool sign /fd SHA256 main.exe
Persistent storage in executables
A compiled binary is treated as a standalone application, so origin-bound
storage persists across runs in the platform's application data directory
(%LOCALAPPDATA% on Windows, ~/Library/Application Support on macOS,
$XDG_DATA_HOME on Linux):
localStorage and the
Web Cache API read and write to that directory.
Each compiled app gets its own location derived from its identity, so separate
apps do not share storage. That identity comes from the --app-name flag, which
is baked in at compile time and falls back to the output file name when omitted:
ende compile --app-name my-app main.ts
Because the name (not the module path) drives the directory, the store stays
stable across runs even if you rename the binary, two binaries built with the
same --app-name share a store, and differently named apps stay isolated.
Recompiling with a different --app-name starts a fresh store.
Command line usage:
compileCompile the script into a self contained executable
Options
--app-name
Stable identity for the compiled app.
Determines where origin-bound storage such as localStorage and caches
is persisted (under the platform's app data directory).
Defaults to the output file name. Set this to keep storage stable across renames.
--cached-only
Require that remote dependencies are already cached.
--cert
Load certificate authority from PEM encoded file.
--check
Set type-checking behavior. This subcommand type-checks local modules by default, so passing --check is redundant; pass --check=all to also type-check remote modules. Alternatively, use the 'ende check' subcommand.
--config, -c
Configure different aspects of ende including TypeScript, linting, and code formatting.
Typically the configuration file will be called ende.json or ende.jsonc and
automatically detected; in that case this flag is not necessary.
--engine
JS engine the compiled binary runs on (quickjs is smaller and experimental, and does not receive the same security updates as v8).
--env-file
Load environment variables from local file Only the first environment variable with a given key is used. Existing process environment variables are not overwritten, so if variables with the same names already exist in the environment, their values will be preserved. Where multiple declarations for the same environment variable exist in your .env file, the first one encountered is applied. This is determined by the order of the files you pass as arguments.
--exclude
Excludes a file/directory in the compiled executable. Use this flag to exclude a specific file or directory within the included files. For example, to exclude a certain folder in an included directory.
--ext
Set content type of the supplied file.
--frozen-lockfile
Error out if lockfile is out of date.
--icon
Set the icon of the executable on Windows (.ico).
--import-map
Load import map file from local file or remote URL.
--include
Includes an additional module or file/directory in the compiled executable. Use this flag if a dynamically imported module or a web worker main module fails to load in the executable or to embed a file or directory in the executable. This flag can be passed multiple times, to include multiple additional modules.
--inspect
Activate inspector on host:port [default: 127.0.0.1:9229]. Host and port are optional. Using port 0 will assign a random free port.
--inspect-brk
Activate inspector on host:port, wait for debugger to connect and break at the start of user script.
--inspect-publish-uid
--inspect-wait
Activate inspector on host:port and wait for debugger to connect before running user code.
--location
Value of globalThis.location used by some web APIs.
--lock
Check the specified lock file. (If value is not provided, defaults to "./ende.lock").
--min-dep-age
(Unstable) The age in minutes, ISO-8601 duration or RFC3339 absolute timestamp (e.g. '120' for two hours, 'P2D' for two days, '2025-09-16' for cutoff date, '2025-09-16T12:00:00+00:00' for cutoff time, '0' to disable).
--no-check
Skip type-checking. If the value of "remote" is supplied, diagnostic errors from remote modules will be ignored.
--no-clear-screen
Do not clear terminal screen when under watch mode.
--no-code-cache
Disable V8 code cache feature.
--no-config
Disable automatic loading of the configuration file.
--no-lock
Disable auto discovery of the lock file.
--no-remote
Do not resolve remote modules.
--no-terminal
Hide terminal on Windows.
--output, -o
Output file (defaults to $PWD/
--preload
A list of files that will be executed before the main module.
--reload, -r
Reload source code cache (recompile TypeScript). With no value, reloads everything. Pass a comma-separated list of specifiers to reload only those modules; jsr:@ende/http/file-server,jsr:@ende/assert/equals reloads specific modules.
--seed
Set the random number generator seed.
--self-extracting
Create a self-extracting binary that extracts the embedded file system to disk on first run and then runs from there.
--target
Target OS architecture.
--unsafely-ignore-certificate-errors
DANGER: Disables verification of TLS certificates.
--v8-flags
To see a list of all available flags use --v8-flags=--help
Flags can also be set via the ENDE_V8_FLAGS environment variable.
Any flags set with this flag are appended after the ENDE_V8_FLAGS environment variable.
--vendor
Toggles local vendor folder usage for remote modules.
--watch
Watch for file changes and restart process automatically. Only local files from entry point module graph are watched.
--watch-exclude
Exclude provided files/patterns from watch mode.