Liking cljdoc? Tell your friends :D

Architecture

Result

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:

  • the active cljs.env/*compiler* atom;
  • the active cljs.repl/*repl-env*;
  • the merged compiler/REPL options; and
  • the REPL output streams.

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. 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.

-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.

Reload primitive

cljs.repl/load-file is sufficient for version 1. In ClojureScript 1.12.145 it:

  1. parses the source with the analyzer;
  2. processes JavaScript modules and dependency sources;
  3. force-compiles the source in interactive mode;
  4. loads missing dependencies;
  5. evaluates the generated dependency declaration; and
  6. evaluates a wrapped 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.

Source graph

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.
  • The result supplies fresh :requires edges even when the active analyzer atom still describes the previous successful compilation.
  • Edges used for affected-namespace closure are restricted to namespaces found under those roots.
  • 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.

Watch orchestration

The watcher is one daemon ScheduledExecutorService. It recursively polls the configured source roots, compares content signatures, and waits for a stable signature for :debounce-ms before invoking one reload. 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. Version 1 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. The reload state itself remains ordinary maps, sets, and vectors in one atom.

Reference implementations studied

  • clj-reload informed the changed / affected / topological-plan model and recovery expectations. Its unload and var-preservation mechanisms are deliberately not imitated because JavaScript runtime semantics differ.
  • ClojureScript cljs.main, cljs.cli, and cljs.repl establish the lifecycle and reload primitive.
  • Krell demonstrates practical extension through 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.
  • Pavlov was assessed for bthread orchestration and intentionally not added for the reason above.

End-state report

  • True cljs.main composition: yes.
  • Exact seam: -re plus cljs.repl/IReplEnvOptions and cljs.repl/IJavaScriptEnv delegation.
  • Existing browser connection reused: yes.
  • cljs.repl/load-file sufficient: yes, for v1 and unoptimized development output.
  • Unavoidable wrapping: one protocol decorator around the stock browser REPL environment; no cljs.main wrapper.
  • Custom transport: none.
  • Known semantic limitations: old closures/objects may retain old function references; removed vars and deleted namespaces may remain in JavaScript; state preservation follows ordinary ClojureScript/JavaScript semantics.

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close