Compile & ship binaries
cljgo build is the ClojureScript model with Go as the JavaScript: the
compiler emits plain Go source — one Clojure namespace becomes one Go
package — then invokes go build on the generated module. The result is
a single static native binary. No JVM, no runtime install, nothing to
ship but the file.
$ cljgo run hello.clj # interpreted$ cljgo build hello.clj # -> ./hello, a static native binary$ ./hello # byte-identical outputMeasured on the reference machine (Apple M5 Pro, go1.26.3): a hello binary is 5.3 MB stripped, starts in ~5 ms, peaks at 14.7 MB RSS. Numbers, methodology, and head-to-head comparisons are on the benchmarks page.
cljgo build is the one command that needs the Go toolchain on PATH
(cljgo run and cljgo repl do not — see
Install). Release binaries pin the published runtime
module in the generated go.mod; the first build fetches it from the Go
module proxy once per machine.
Single-file mode
Section titled “Single-file mode”cljgo build [-o out] [-gen dir] [-runtime dir] <file.clj>-o out— output binary path. Default: the file’s base name, or the parent directory name for acore.clj(examples/hello/core.clj→hello). On Windows the default gets.exe; an explicit-ois honored verbatim, same rule asgo build -o.-gen dir— keep the generated Go module there instead of a temp dir. The output is honest,gofmt-ed Go source you can read; a// Code generated by cljgo build. DO NOT EDIT.header marks each file.-runtime dir— the cljgo source tree used for the generatedgo.mod’s replace directive (defaults to$CLJGO_SRC; release binaries pin the published module instead).
Source files may be .clj, .cljc, .cljg, or .cljgo (ADR 0055).
The build strips by default (-trimpath -ldflags="-s -w"), and a
compiled binary links the compiled clojure.core — never the
interpreter. The tree-walk evaluator contributes zero symbols to the
link set, CI-enforced (ADR 0046); that is why a compiled binary starts
in ~5 ms instead of the interpreter’s ~32 ms boot.
Project mode
Section titled “Project mode”With no source-file argument, cljgo build [step] loads the project’s
build.cljgo (probed as build.cljgo > build.cljg > build.clj),
evaluates its (defn build [b] …) plan, and runs the requested step —
the zig-build model (ADR 0021):
;; build.cljgo — what `cljgo new --template cli` generates(defn build [b] (let [app (exe b {:name "newapp" :main "src/newapp/core.cljg"})] (install b app) (run b app)))$ cljgo build # default step: install ./newapp$ cljgo build run # build it and run it (mirrors `zig build run`)Project builds also resolve declared dependencies and write/verify
build.lock.edn — see
Dependencies & publishing. Third-party Go
modules declared with (go-require …) are fetched and linked into the
binary here.
Cross-compilation
Section titled “Cross-compilation”Because the output is plain Go, the standard Go cross-compilation environment variables work directly:
$ GOOS=linux GOARCH=amd64 cljgo build -o hello-linux hello.clj$ file hello-linuxhello-linux: ELF 64-bit LSB executable, x86-64, statically linked, stripped(Verified from macOS/arm64.) All the usual Go targets apply —
macOS/Linux/Windows, amd64/arm64. The usual Go caveat applies too:
cgo-based dependencies generally break cross-compiles (you need a target
C toolchain and sysroot). A first-class per-artifact :target surface in
build.cljgo is planned but not wired yet.
Two things worth knowing
Section titled “Two things worth knowing”- Top-level forms are evaluated at compile time. The emitter
discovers namespaces by evaluating require forms through the
interpreter, so a bare top-level
(println …)in your source prints duringcljgo buildas well as at runtime. Put program behavior in-main. - Compiled output is byte-identical to the interpreter. The dual-harness conformance suite (416 oracle-cited files) runs every test through both paths on every commit; a REPL-vs-binary divergence is treated as a release blocker. See the REPL guide and architecture.
Compiled code is also where the performance work lands: chunk-aware sequence ops, direct-call emission, and int64 numeric inference (ADRs 0063–0067) put emitted hot loops at ~5× handwritten Go — details on the benchmarks page.
Questions or feedback on this page? Comment below with your GitHub account — comments are public and live in the project's GitHub Discussions.