A plan for evolving Piggieback's internals. This is a direction, not a contract: items can be reordered or dropped as we learn more. For how the current code works, see the architecture document.
Piggieback's job is narrow and worth doing well: be the thin, correct adapter
between nREPL and the official cljs.repl environments, and CIDER's built-in
default cljs REPL. shadow-cljs and figwheel-main own the heavyweight build-tool
space and bring their own nREPL integration, so Piggieback should not chase
features they already cover (hot reload, asset serving, file watching). Almost
every item below is "make the adapter simpler or more correct," not "add
surface."
The instinct to start small is right, and for a stronger reason than low risk: two of the small items are the seams that make the one scary refactor safe.
repl* driving)
is a change behind a known interface instead of open-heart surgery.So the order is: seams and correctness first, structural simplification next, the core refactor once the seams are in place, and upstream work filed early because it has the longest lead time.
deps.edn alongside Leiningen, clj-kondo added, cljfmt and
Eastwood on current versions, dependencies refreshed.cider.piggieback.compat namespace.cider.piggieback.cljs core namespace; the middleware no longer requires any
cljs.* namespace directly.describe response surfaces per-session ClojureScript
status via a :describe-fn.:close metadata hook.load-file evaluates the source sent in the message (unsaved
buffers included) instead of re-reading from disk.load/in-ns structure is gone; Piggieback is
now a public cider.piggieback namespace that lazily loads the
cider.piggieback.cljs implementation, and the clj-kondo workarounds it
forced are removed.eval-codegen delegating env is now a single DelegatingReplEnv type, and the
:print side-channel is gone (the session ns is set explicitly after setup).
Full setup-ownership was deferred (see below).Low risk, high clarity, and partly preparatory for Phase 4.
Today, support for the supported nREPL range is handled by resolve-based
feature detection scattered through the implementation (nrepl-1-3+?,
replying-PrintWriter lookup, the enqueue double-binding dance). Pull these
into one small compatibility namespace with named, documented predicates and
shims.
Gather the direct reaches into cljs.analyzer, cljs.env, cljs.closure,
cljs.tagged-literals, and the non-public parts of cljs.repl into a single,
thin, version-guarded "cljs eval core" namespace. The handler code should talk to
that core, not to compiler internals directly.
describe (done)Advertise Piggieback in the describe response and expose whether a session is
currently in ClojureScript mode (and ideally which repl-env). Optionally echo
cljs-ness on eval responses.
Replace the current string-name matching against specific print functions with a more robust signal for choosing plain vs pretty printing.
A long-running JS eval cannot be cancelled cleanly. Document this clearly rather than leaving it implicit, and decide whether any best-effort behaviour is worth offering.
Independent of everything else; ship as soon as ready.
Register cleanup on session close / expiry so the active repl-env's -tear-down
runs even when the client never sends :cljs/quit (dropped connection, editor
killed, client exit). Today the node subprocess or browser connection leaks.
load-file evaluates the sent content (done)Make load-file compile the source provided in the message against the right
namespace and path, rather than re-reading the file from disk. This matches
Clojure nREPL semantics ("load buffer" should load what is in the buffer).
load-file path and the path/namespace handling;
needs tests for both saved and unsaved content.requiring-resolve (done)Collapse the three-file if-ns + (load ...) + (in-ns) structure into one
namespace that resolves the ClojureScript machinery lazily at first use and
returns a no-op middleware when ClojureScript is absent.
redefined-var / unresolved-symbol friction we currently
paper over in clj-kondo config, and reads far more honestly. Clojure 1.10
(our floor) has requiring-resolve.repl* setup warts (pragmatic scope, done)The original plan was to stop driving cljs.repl/repl* for setup entirely: run
-setup once, establish the analyzer/compiler bindings explicitly, run the
initial requires as an ordinary evaluate-form, and use one uniform evaluation
path for everything. Investigating it showed that owning setup means replicating
repl*'s version-sensitive setup block (the analyzer bindings, the warnings
merge, with-core-cljs/-setup, merge-opts) - trading coupling to repl*'s
loop for coupling to its setup internals, with the matrix only exercising the
Node env.
So the pragmatic subset was done instead, keeping repl* for the setup it does
correctly while removing the two warts that don't earn their keep:
eval-codegen delegating env became a single DelegatingReplEnv
type. The optional cljs.repl protocols are delegated to the wrapped env when
it implements them and otherwise fall back to cljs.repl's own default, so one
type mirrors each env per instance without generating a class per env class.:print side-channel is gone. The #62 fix already meant the compiler env
is created up front and captured unconditionally; the post-setup namespace is
always cljs.user, so the session ns is set explicitly after setup rather than
smuggled out through :print.Still open, if the appetite and broader env coverage (browser, graal) arrive: fully replicate setup and collapse to a single evaluation path, deleting the delegating env entirely.
repl* internals; the pragmatic scope
already captured most of the maintainability win at low risk.File these early; they have the longest lead time and help the wider ecosystem (shadow-cljs and figwheel-main hit the same walls).
cljs.repl.node per-thread state (issues #105, #88)cljs.repl.node keys its results/output state by thread name, which collapses
nREPL's worker threads and prevents two node REPLs in one JVM. Propose keying by
env identity instead. Until/unless it lands upstream, a vendored corrected node
env is a fallback.
*out* capture (root cause of #111)Propose letting the node env take an explicit writer / output-fn instead of
capturing *out* at setup. If accepted, Piggieback's forwarding-writer
workaround can eventually be retired.
flowchart LR
M1["M1 nREPL compat seam"] --> B1["B1 own evaluation"]
M2["M2 cljs eval core seam"] --> B1
C1["C1 session-close teardown"]
C2["C2 load-file content"]
S1["S1 single-namespace rewrite"]
M3["M3 describe surfaces cljs"]
M4["M4 robust print mode"]
M5["M5 document interrupt"]
U1["U1 node per-thread state"]
U2["U2 node *out* capture"]
U2 -.->|"eventually retires"| FW["forwarding-writer workaround"]
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 |