Requirements
Use Emacs 29 or newer. Bun 1.4 or newer is needed for the reference execution path and complete test suite.
emacs --version
bun --version
Install the exact JavaScript dependencies used by the examples and integration tests:
bun install --frozen-lockfile
The compiler itself has no Bun or Node.js dependency. A clean Emacs installation can produce the module.
Compile a file
Create hello.eli:
(module hello
(defun greet (name)
(str "Hello, " name "!"))
(print (greet "ECMAScript"))
(export greet))
Compile it through the public command:
./bin/eliscript --output dist/hello.mjs hello.eli
Without --output, generated ECMAScript is written to standard output.
For debugger-ready output, generate an external Source Map v3 file:
./bin/eliscript --source-map --output dist/hello.mjs hello.eli
This writes dist/hello.mjs.map and links it from the generated module.
Run the module
bun run dist/hello.mjs
The generated file is standard ESM. Bun is the reference host, but the compiler does not emit proprietary Bun syntax.
Build a source graph
bun run build:stdlib-cli
bun run dist/project/examples/stdlib-cli/main.mjs
The Emacs project builder discovers relative .eli imports after macro expansion, keeps every source below the selected root, mirrors the directory tree as .mjs, and emits a source map beside each module. A deterministic eliscript-project.json records the complete graph, SHA-256 identities, and separately verified incremental metadata. Repeat builds reuse unchanged modules; pass --no-cache to force compilation. This example builds sequence, text, object, and application sources together. Package, JavaScript, CSS, and image imports remain available to the eventual host.
Tooling can request a versioned report with stable cache and per-module decisions plus cache-read, build-work, manifest-write, and total timings:
./bin/eliscript-build --json --root . --out-dir dist/project \
examples/stdlib-cli/main.eli
Timings are observational and never enter the deterministic manifest or cache identity. Without --json, successful builds continue to print only the generated entry path.
Compile live in the browser
Any web page can compile and hot-reload Eliscript directly in the browser without a server or repository checkout. Load the standalone browser loader and declare either a data-eliscript entry or inline/external <script type="text/eliscript"> tags:
<script defer src="https://unpkg.com/eliscript@0.1.1/dist/browser/loader.js"></script>
<script type="text/eliscript">
(module web.hello
(print "Compiled live in the browser!"))
</script>
For programmatic in-memory compilation, Closure-style IR/JS optimization ({ optimize: true, minify: true, closureCompiler }), extensionless package imports (such as (import "eliscript/runtime/core/order" sort) alongside explicit .mjs), and multi-module execution, import eliscript/browser directly:
import { compileSource, compileProject, moduleGraphUrls, runSource, mountEliscriptScripts } from "eliscript/browser";
const { module } = await runSource(
"(module demo (defun double (x) (* x 2)) (export double))",
"demo.eli"
);
console.log(module.double(21)); // 42
Use persistent values & 1+5+2 Emacs-compatible extensions
(module collections
(import "eliscript/runtime/core/collection" assoc get)
(defconst original {:name "Eliscript" :tags #{:lisp :esm}})
(defconst updated (assoc original :status :stable))
(print (get original :status :missing))
(print (get updated :status)))
Vector, Map, Set, List, Queue, Record, and sorted collection values are immutable. Updates return a new root while sharing unchanged internal structure. Generic collection operations dispatch through open protocols with reference-identity equality short-circuiting and compact O(1)-step Zipper navigation, and Transient builders provide an isolated high-throughput construction path for Vector, Map, Set, sorting, and data aggregation.
In Emacs 29+ or JavaScript, load the 0.1.0 1+5+2 unified entry ((require 'eliscript-compat) in Emacs or import "eliscript/runtime/emacs-compat" in JS) to combine 100% Emacs Lisp default semantics with defun-case, let-bind / defun-bind, cl-defstruct-persistent, defmodule, with-async-scope, js-false / js-null / js-undefined sentinels, and :abi (:numeric-f64 / :memory-u32) multi-target compilation.
Run optional applications
Application examples consume public ESM and host boundaries. They are useful interoperability probes, but no framework or bundler is part of the language compiler, runtime, standard library, or core maturity result.
React counter
bun run compile:react-counter
bun run react-counter
The first command emits a source-mapped ESM module through react/jsx-runtime. The second renders it with react-dom/server, proving the generated element tree without a bundler.
Start the interactive browser application or create its production bundle:
bun run dev:react-counter
bun run build:react-counter
The Vite adapter compiles .eli modules, composes source maps, handles CSS and image assets, and enables React Fast Refresh.
Org publishing
Export the example articles through Emacs as a deterministic ESM data module:
bun run org:export
Run the fully custom React publishing site or create its static production bundle:
bun run dev:org-site
bun run build:org-site
The application itself is written in Eliscript. Its adapter exposes the Org directory as a virtual module and reloads article changes during development.
Build the bootstrap modules
bun run build:bootstrap
The Emacs Lisp seed compiler writes thirteen source-mapped compiler modules below dist/bootstrap/, including the complete portable compiler driver, while make bootstrap-backends materializes all runtime, platform, browser, host, and tool .mjs artifacts from in-place .eli sources (leaving zero tracked .mjs files in Git). Compile through the generated compiler with:
./bin/eliscript-portable --output dist/program.mjs source/program.eli
The conformance suite uses Generation 1 to build Generation 2, then Generation 2 to build Generation 3. Every compiler module and Source Map remains byte-identical.
Measure the Emacs worker
(defportable score-values (values)
(length values))
./bin/eliscript --portable score-values --output dist/score.mjs source.eli
defportable validates the complete immutable dependency closure. The generated manifest lets Emacs call score-values without knowing its JavaScript identifier.
bun run benchmark:worker
The benchmark compiles one pure workload, starts a long-lived Bun worker, verifies equal Emacs and Eliscript results, and reports cold and warm compilation, startup, execution, serialization, transport, and client costs separately.
(require 'eliscript-index)
(let ((session (eliscript-index-start)))
(unwind-protect
(eliscript-index-search-sync
session '(("intro" . "Emacs and JavaScript")) "javascript")
(eliscript-index-stop session)))
The indexing adapter compiles a source-mapped portable project closure, follows its data and object dependencies, tokenizes editor-owned text, scores documents concurrently, and owns cleanup. Its graph manifest makes dependency changes restart the same client object automatically and maps imported-module errors to their Eliscript source.
Run the test suite
bun run test
This runs the Emacs ERT suite, compiler and runtime tests, public CLI fixtures, deterministic snapshots, source-map validation, Bun/Node execution, and separately partitioned application checks.
The portable standard library and protocol-driven persistent collection core run through the same Eliscript module pipeline. Application adapters are tested separately and contribute no core maturity credit.