Direct Answer
The ClojureScript compiler does automatically invalidate cache entries when a namespace's source file changes—cache keys incorporate a hash of the source content. However, the REPL will still load a stale definition when:
- The cache key matches (same source hash + compiler options) but the in-memory REPL state was populated from an earlier compilation.
- A macro namespace (
.clj/.cljc) changed; dependent namespaces are not automatically recompiled.
- Multiple REPL processes share the same cache directory with different compiler options (e.g.,
:optimizations :none vs :advanced).
- A namespace was deleted or renamed; its orphaned cache entry persists until manually removed.
You do not need cljsbuild clean for routine source edits. The standard REPL workflow (require 'my.ns :reload-all) forces recompilation of that namespace and its transitive dependencies from source, bypassing the cache for those namespaces.
Why Stale Definitions Appear
The compiler writes analysis (.analysis) and JavaScript (.js) artifacts to a cache directory (.cpcache for cljs.main, .shadow-cljs for Shadow-CLJS, target/cljsbuild for lein-cljsbuild). When you require a namespace at the REPL, the toolchain checks the cache first. If a valid entry exists—source hash and compiler options match—it loads the cached JS instead of recompiling. This is by design for speed.
Staleness occurs when the cache looks valid but the running REPL holds an older function object, or when a macro change invalidates downstream namespaces that the cache still considers current.
Resolution Steps (Least to Most Disruptive)
- Targeted reload: At the REPL, evaluate
(require 'your.namespace :reload-all). This recompiles your.namespace and every namespace it depends on, reading source files directly.
- Verify recompilation: Introduce a deliberate syntax error in the suspect file, save, then run the
:reload-all require. If the compiler reports the error, the namespace was recompiled; if the old definition persists, the cache was used.
- Macro namespace change: If you edited a
.clj/.cljc macro file, restart the host Clojure (JVM) REPL (or run (clojure.tools.namespace.repl/refresh) if using tools.namespace), then :reload-all the consumer namespaces in the ClojureScript REPL.
- Orphaned/renamed namespaces: Delete the specific cache subdirectory for that namespace (e.g.,
.cpcache/your/namespace/) or, more broadly, the entire cache directory (.cpcache, .shadow-cljs, or target/cljsbuild), then restart the ClojureScript REPL.
- Concurrent REPL conflict: Ensure each REPL process uses a distinct
:cache-dir (or Shadow-CLJS :build-id). Stop all but one REPL, clear the shared cache, restart.
Toolchain-Specific Notes
- Shadow-CLJS: Use
npx shadow-cljs cljs-repl <build-id> after npx shadow-cljs clean for a full reset, or (shadow.cljs.devtools.api/force-reload 'your.ns) for a single namespace.
- Figwheel Main: Relies on
cljs.repl/load-file and :reload metadata; :reload-all on require works the same way.
- cljs.main REPL: Uses
cljs.repl/require with :reload or :reload-all.
One Diagnostic Detail
Which toolchain are you using (cljs.main, Figwheel Main, Shadow-CLJS, lein-cljsbuild) and are you running multiple REPL sessions against the same cache directory? The answer determines whether step 5 (cache isolation) applies.