True composition with cljs.main is achieved. cljs-reload does not wrap
cljs.main/-main and does not create a browser transport.
The extension seam is cljs.main -re cljs-reload.repl, the standard custom
REPL-environment option. cljs-reload.repl/ReloadEnv implements the public
cljs.repl protocols and delegates setup, evaluation, loading, teardown,
stacktrace parsing, error retrieval, browser options, and browser command-line
options to one stock cljs.repl.browser/BrowserEnv instance.
Its IJavaScriptEnv/-setup method runs inside cljs.repl/repl*, after
cljs.main has bound:
cljs.env/*compiler* atom;cljs.repl/*repl-env*;The watcher captures those values. Every reload rebinds them on its serial
worker and calls cljs.repl/load-file for each namespace in the calculated
order. CSS changes use cljs.repl/evaluate to run the stylesheet swap script
through that same environment. The stock browser environment therefore reuses
the existing browser connection. There is no second compiler environment and
no custom compile/eval transport.
The browser REPL keeps one pending return-value callback. The decorator shares one re-entrant evaluation lock with the complete reload batch, preventing an interactive evaluation from interleaving with reload evaluations while still using the browser REPL's original request/response path.
Each decorator also owns a ReloadSession: one lifecycle lock, runtime atom,
state atom, and optional watcher. Setup and teardown operate on that session
only. Multiple decorators can consequently retain distinct compiler atoms,
browser environments, output streams, source roots, and REPL ports in the same
JVM. Their REPL special functions close over the owning session rather than
consulting JVM-global reload state. Zero-argument JVM-side functions use the
most recently started session for single-build compatibility; multi-build
orchestrators retain and pass explicit sessions.
-i, :init, and :inits were investigated but are not sufficient to own the
watcher. They are ClojureScript forms or scripts evaluated inside the target
JavaScript environment; the watcher and compiler atom live on the JVM side.
cljs.repl/load-file is sufficient for version 1. In ClojureScript 1.12.145 it:
goog.require with reload enabled.The library adds orchestration around that primitive, not custom compilation.
The only compiler-state intervention is a per-file snapshot and reset when
load-file throws. ClojureScript's own wrapped REPL special functions use the
same rollback pattern so a partial failed analysis does not poison recovery.
CSS does not participate in compilation or the namespace graph. Configured CSS
roots are content-scanned independently. When their snapshot changes, a small
JavaScript expression is evaluated through the active IJavaScriptEnv. It
cache-busts and swaps external stylesheet links while keeping each old link
until the replacement loads. Every external stylesheet is refreshed because
the relationship between a filesystem path and a browser URL belongs to the
application's web server and cannot be inferred portably.
The graph is a hybrid of fresh compiler parsing and the active compiler state:
cljs.analyzer.api/parse-ns reads every .cljs and .cljc ns form below the
configured project roots when a reload is planned.:requires edges even when the active analyzer atom
still describes the previous successful compilation.cljs.repl/load-file then analyzes and compiles into the existing analyzer
atom.This uses ClojureScript's parser instead of implementing another ClojureScript reader. Reading ns forms afresh costs a project-source scan per logical save, but avoids the stale-analyzer bug where changing an ns form would calculate dependents from yesterday's edges. Content hashes serve both accurate change detection and native/editor event deduplication; mtimes remain in public state for familiar inspection.
Old and new dependent maps are combined while calculating affected namespaces.
That makes removal of an old dependency edge, source deletion, and namespace
rename visible to the plan. Only namespaces still backed by a source file can
appear in :reload-order; removed namespaces remain visible in :changed,
:affected, and :deleted.
Each active reload session has one daemon ScheduledExecutorService. It
recursively polls that session's configured source and CSS roots, compares their
combined content signature, and waits for a stable signature for
:debounce-ms before invoking one reload against the same session. Polling is
portable and handles editors that save through temporary-file rename sequences
without a recursive native watch-key registry.
Pavlov and its bthreads were evaluated for orchestration. Behavioral threads are useful when multiple independent behaviors request, wait for, and block a shared event stream. Each session has one linear state machine—observe, debounce, scan, reload—so adopting a behavioral program would add a dependency and a second orchestration model without reducing complexity. Each session's reload state remains ordinary maps, sets, and vectors in one atom.
cljs.main,
cljs.cli,
and cljs.repl
establish the lifecycle and reload primitive.IReplEnvOptions, IJavaScriptEnv, cljs.cli hooks,
analyzer state, and a watcher. cljs-reload needs only the standard browser
decorator, not Krell's React Native compilation and transport.cljs.main composition: yes.-re plus cljs.repl/IReplEnvOptions and
cljs.repl/IJavaScriptEnv delegation.cljs.repl/load-file sufficient: yes for namespace reloads; CSS refreshes
use cljs.repl/evaluate against the same environment.cljs.main wrapper.Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |