{"id":13442443,"url":"https://github.com/nrepl/piggieback","last_synced_at":"2025-12-12T01:33:38.541Z","repository":{"id":4237114,"uuid":"5361077","full_name":"nrepl/piggieback","owner":"nrepl","description":"nREPL support for ClojureScript REPLs","archived":false,"fork":false,"pushed_at":"2025-01-01T14:23:58.000Z","size":255,"stargazers_count":484,"open_issues_count":9,"forks_count":48,"subscribers_count":23,"default_branch":"master","last_synced_at":"2025-05-07T00:02:29.216Z","etag":null,"topics":["clojure","clojurescript","nrepl","nrepl-middleware","repl"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nrepl.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2012-08-09T20:51:49.000Z","updated_at":"2025-05-01T20:40:21.000Z","dependencies_parsed_at":"2023-07-07T10:16:26.263Z","dependency_job_id":"85e1e2d3-67c6-4584-b990-ecc5367485bf","html_url":"https://github.com/nrepl/piggieback","commit_stats":{"total_commits":270,"total_committers":23,"mean_commits":11.73913043478261,"dds":0.6666666666666667,"last_synced_commit":"540a57e871b72cb494d0049cb8c6b7485458ad25"},"previous_names":["cemerick/piggieback"],"tags_count":33,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nrepl%2Fpiggieback","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nrepl%2Fpiggieback/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nrepl%2Fpiggieback/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nrepl%2Fpiggieback/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nrepl","download_url":"https://codeload.github.com/nrepl/piggieback/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252789073,"owners_count":21804386,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","clojurescript","nrepl","nrepl-middleware","repl"],"created_at":"2024-07-31T03:01:45.755Z","updated_at":"2025-12-12T01:33:38.533Z","avatar_url":"https://github.com/nrepl.png","language":"Clojure","readme":"[![CircleCI](https://circleci.com/gh/nrepl/piggieback/tree/master.svg?style=svg)](https://circleci.com/gh/nrepl/piggieback/tree/master)\n[![Clojars Project](https://img.shields.io/clojars/v/cider/piggieback.svg)](https://clojars.org/cider/piggieback)\n\n# Piggieback\n\n[nREPL](http://github.com/nrepl/nrepl) middleware that enables the\nuse of a ClojureScript REPL on top of an nREPL session.\n\n## Why?\n\nTwo reasons:\n\n* The default ClojureScript REPL (as described in the\n[\"quick start\"](https://clojurescript.org/guides/quick-start)\ntutorial) assumes that it is running in a teletype environment. This works fine\nwith nREPL tools in that environment (e.g. `lein repl` in `Terminal.app` or\n`gnome-terminal`, etc), but isn't suitable for development environments that\nhave richer interaction models (including editors like vim ([vim-fireplace][]) and Emacs\n([CIDER][]), and IDEs like Intellij ([Cursive][]) and Eclipse ([Counterclockwise][CCW])).\n\n* Most of the more advanced tool support for Clojure and ClojureScript (code\n  completion, introspection and inspector utilities, refactoring tools, etc) is\n  packaged and delivered as nREPL extensions (e.g. [cider-nrepl][] and [refactor-nrepl][]).\n\nPiggieback provides an alternative ClojureScript REPL entry point\n(`cider.piggieback/cljs-repl`) that changes an nREPL session into a\nClojureScript REPL for `eval` and `load-file` operations, while accepting all\nthe same options as `cljs.repl/repl`. When the ClojureScript REPL is terminated\n(by sending `:cljs/quit` for evaluation), the nREPL session is restored to it\noriginal state.\n\n## Installation\n\nPiggieback is compatible with Clojure 1.10.0+, and _requires_ ClojureScript\n`1.10` or later and nREPL `1.0.0` or later.\n\nTo use the default Node.js REPL (`cljs.repl.node`) you'll also need to install a recent version of Node.js.\n\n### Leiningen\n\nThese instructions are for Leiningen. Translating them for use in Boot should be\nstraightforward.\n\nModify your `project.clj` to include the following `:dependencies` and\n`:repl-options`:\n\n```clojure\n:profiles {:dev {:dependencies [[cider/piggieback \"0.6.1\"]]\n                 :repl-options {:nrepl-middleware [cider.piggieback/wrap-cljs-repl]}}}\n```\n\nThe `:repl-options` bit causes `lein repl` to automagically mix the Piggieback\nnREPL middleware into its default stack.\n\n_If you're using Leiningen directly, or as the basis for the REPLs in your local\ndevelopment environment (e.g. CIDER, fireplace, counterclockwise, etc), you're\ndone._ [Skip to starting a ClojureScript REPL](#usage).\n\n### Boot\n\nContributions welcome!\n\n### Clojure CLI (aka `tools.deps`)\n\n**The instructions below require nREPL 1.0.0 or newer**\n\nAdd this alias to `~/.clojure/deps.edn`:\n\n``` clojure\n{\n;; ...\n:aliases {:nrepl\n          {:extra-deps\n            {nrepl/nrepl {:mvn/version \"1.3.0\"}\n             cider/piggieback {:mvn/version \"0.6.1\"}}}}\n}\n```\n\nThen you can simply run a ClojureScript-capable nREPL server like this:\n\n``` shell\nclj -R:nrepl -m nrepl.cmdline --middleware \"[cider.piggieback/wrap-cljs-repl]\"\n```\n\nWhen you connect to the running server with your favourite nREPL client\n(e.g. CIDER), you will be greeted by a Clojure REPL. Within this Clojure REPL,\nyou can now [start a ClojureScript REPL](#usage).\n\n### Embedded\n\nIf you're not starting nREPL through a build tool (e.g. maybe you're starting up\nan nREPL server from within an application), you can achieve the same thing by\nspecifying that the `wrap-cljs-repl` middleware be mixed into nREPL's default\nhandler:\n\n```clojure\n(require '[nrepl.server :as server]\n         '[cider.piggieback :as pback])\n\n(server/start-server\n  :handler (server/default-handler #'pback/wrap-cljs-repl)\n  ; ...additional `start-server` options as desired\n  )\n```\n\nAlternatively, you can add `wrap-cljs-repl` to your application's hand-tweaked\nnREPL handler.  Keep two things in mind when doing so:\n\n* Piggieback needs to be \"above\" nREPL's\n  `nrepl.middleware.interruptible-eval/interruptible-eval`; it\n  doesn't use `interruptible-eval`'s evaluation machinery, but it does reuse its\n  execution queue and thus inherits its interrupt capability.\n* Piggieback depends upon persistent REPL sessions, like those provided by\n  `nrepl.middleware.session/session`.)\n\n\n## Usage\n\nBefore you run the following, you must have gone through the [setup\nsteps](#installation). Instead of using `lein repl`, you might also connect to a\nheadless nREPL using your development environment.\n\n```\n$ lein repl\n....\nuser=\u003e (require 'cljs.repl.node)\nnil\nuser=\u003e (cider.piggieback/cljs-repl (cljs.repl.node/repl-env))\nTo quit, type: :cljs/quit\nnil\ncljs.user=\u003e (defn \u003c3 [a b] (str a \" \u003c3 \" b \"!\"))\n#\u003c\nfunction cljs$user$_LT_3(a, b) {\n    return [cljs.core.str(a), cljs.core.str(\" \u003c3 \"), cljs.core.str(b), cljs.core.str(\"!\")].join(\"\");\n}\n\u003e\ncljs.user=\u003e (\u003c3 \"nREPL\" \"ClojureScript\")\n\"nREPL \u003c3 ClojureScript!\"\n```\n\nSee how the REPL prompt changed after invoking\n`cider.piggieback/cljs-repl`? After that point, all expressions sent to the\nREPL are evaluated within the ClojureScript environment.\n`cider.piggieback/cljs-repl`'s passes along all of its options to\n`cljs.repl/repl`, so all of the tutorials and documentation related to it hold.\n\n*Important Notes*\n\n1. When using Piggieback to enable a browser REPL: the ClojureScript compiler\n   defaults to putting compilation output in `out`, which is probably not where\n   your ring app is serving resources from (`resources`,\n   `target/classes/public`, etc). Either configure your ring app to serve\n   resources from `out`, or pass a `cljs-repl` `:output-dir` option so that a\n   reasonable correspondence is established.\n2. The `load-file` nREPL operation will only load the state of files from disk.\n   This is in contrast to \"regular\" Clojure nREPL operation, where the current\n   state of a file's buffer is loaded without regard to its saved state on disk.\n\nOf course, you can concurrently take advantage of all of nREPL's other\nfacilities, including connecting to the same nREPL server with other clients (so\nas to easily modify Clojure and ClojureScript code via the same JVM), and\ninterrupting hung ClojureScript invocations:\n\n```clojure\ncljs.user=\u003e (iterate inc 0)\n^C\ncljs.user=\u003e \"Error evaluating:\" (iterate inc 0) :as \"cljs.core.iterate.call(null,cljs.core.inc,0);\\n\"\njava.lang.ThreadDeath\n        java.lang.Thread.stop(Thread.java:776)\n\t\t....\ncljs.user=\u003e (\u003c3 \"nREPL still\" \"ClojureScript\")\n\"nREPL still \u003c3 ClojureScript!\"\n```\n\n(The ugly `ThreadDeath` exception will be eliminated eventually.)\n\nPiggieback works well with all known ClojureScript REPL environments, including\nNode and browser REPLs.\n\n*Support for Rhino was dropped in version 0.3, and Nashorn support\nwas dropped from ClojureScript in 1.10.741.*\n\n## Design\n\nThis section documents some of the main design decisions in Piggieback\nand the differences between similar functionality in nREPL and Piggieback.\n\nPerhaps the most important thing to remember is that Piggieback is written in\nClojure and runs on Clojure. It drives ClojureScript evaluation by using\nClojureScript's Clojure API (`cljs.repl/IJavaScriptEnv`).  This allows you to\nhost both Clojure and ClojureScript evaluation sessions on the same nREPL\nserver, which is pretty cool. On the other hand it also means that you can't use\nPiggieback with self-hosted ClojureScript REPLs (e.g. Lumo).\n\n**Note:** For self-hosted ClojureScript you'll need an nREPL implementation that can run\nnatively on it (e.g. [nrepl-cljs](https://github.com/djblue/nrepl-cljs)).\n\n### No hard dependency on ClojureScript\n\nPiggieback doesn't have a hard dependency on ClojureScript, as users are\nexpected to provide the necessary ClojureScript dependency themselves. If\nClojureScript is not present, Piggieback simply won't do anything (see\n`piggieback_shim.clj` for details).\n\nThis allows tools to safely load Piggieback without\nhaving to consider whether something would blow up.\n\n### Session type based dispatch\n\nClients don't have to specify explicitly whether they are doing a ClojureScript eval\noperation (e.g. by passing some `:env :cljs` request params). As Piggieback operates\nat the nREPL session level all clients need to do is to pass a Piggieback session\nto ops like `eval` and that would trigger the Piggieback version of those ops.\n\n### Evaluation\n\nAs noted above Piggieback provides alternative versions of the standard nREPL\nops `eval` and `load-file` for ClojureScript evaluation. Due to some differences\nbetween Clojure and ClojureScript they don't behave exactly the same.\n\nMost notably - for performance reasons we don't spin separate instances of `cljs.repl`\nfor each evaluation, as nREPL does for Clojure. In practice this means that if you try\nto evaluate multiple forms together only the first of them would be evaluated:\n\n```clojure\n;; standard ClojureScript REPL behaviour\ncljs.user\u003e\n(declare is-odd?)\n(defn is-even? [n] (if (= n 0) true (is-odd? (dec n))))\n(defn is-odd? [n] (if (= n 0) false (is-even? (dec n))))\n#'cljs.user/is-odd?\n#'cljs.user/is-even?\n#'cljs.user/is-odd?\ncljs.user\u003e (is-even? 4)\ntrue\n```\n\nLet's compare this to a REPL powered by Piggieback:\n\n```clojure\ncljs.user\u003e\n(declare is-odd?)\n(defn is-even? [n] (if (= n 0) true (is-odd? (dec n))))\n(defn is-odd? [n] (if (= n 0) false (is-even? (dec n))))\n#'cljs.user/is-odd?\ncljs.user\u003e (is-even? 4)\nCompile Warning   \u003ccljs repl\u003e   line:1  column:2\n\n  Use of undeclared Var cljs.user/is-even?\n\n  1  (is-even? 4)\n      ^---\n\n#object[TypeError TypeError: Cannot read property 'call' of undefined]\n\t (\u003cNO_SOURCE_FILE\u003e)\ncljs.user\u003e\n```\n\nNormally that's not a big deal in practice, as you'd rarely want to evaluate multiple expressions together, but it's\nsomething to be kept in mind.\n\n**Note:** Check out [this discussion](https://github.com/nrepl/piggieback/pull/98) for more details on the subject.\n\n### Pretty-printing\n\n**Note:** Piggieback introduced support for nREPL's pretty-printing interface\nin version 0.5.\n\nSupport for pretty printing ClojureScript evaluation results is not\nentirely straightforward. This is because Piggieback mostly relies on\nthe underlying nREPL server implementation to support the features of\nthe nREPL protocol and on the `cljs.repl/IJavaScriptEnv` interface for\nClojureScript evaluation.\n\nnREPL 0.6 introduced `nrepl.middleware.print` to facilitate printing\nevaluation results in a configurable way. Since nREPL is implemented\nin Clojure and runs on the JVM, the middleware relies on receiving\nClojure values for printing them. Conversely when evaluating a\nClojureScript expression in a JavaScript environment, the resulting\nClojure value of the evaluation is always a string. If this value\nwould simply be passed on as is to the middleware, only the string\nitself could be printed by it instead of the evaluation result within\nthe string.\n\nThere are multiple approaches for working around this issue with\nvarious trade-offs. The current implementation has the following main\nconsiderations:\n\n1. `nrepl.middleware.print` is used to print ClojureScript evaluation\n   results whenever possible, so that the same nREPL (pretty) printing\n   configuration is applied to both Clojure and ClojureScript.\n\n2. For cases where the above is not possible (see below), there is a\n   fallback to support basic pretty printing.\n\nIn order to support `nrepl.middleware.print` for ClojureScript\nevaluation results, they first need to be _read_. The resulting\nClojure values can then be normally printed by the middleware. However\nthere are various cases where ClojureScript evaluation results can not\nbe read by the default Clojure reader. Some examples:\n\n- Functions: `#object[Function]`\n- Objects: `#object[cljs.user.Cheese]`, `#object[Window [object Window]]`\n- `#js` literals: `#js {:foo 1, :bar 2}`\n- `#queue` literals: `#queue [1 2 3]`\n- Custom tagged literals: `#user/cheese \"Pálpusztai\"`\n- Types implementing `IPrintWithWriter` in a way that is incompatible\n  with the Clojure reader\n\nTo work around some of these cases Piggieback provides its own\n`UnknownTaggedLiteral` type. It is used as the default tag reader when\nreading ClojureScript evaluation results. It doesn't parse the\ncontents of the literal and has `print-method` defined to simply print\nthe original.\n\n**Note:** When a pretty-printer which doesn't rely on `print-method` to\nserialize values (such as fipp, puget, etc.) is used,\n`UnknownTaggedLiteral` will be serialized in the output instead of the\noriginal literal.\n\nThere are still cases left which can prevent the Clojure reader from\nsuccessfully reading ClojureScript evaluation results (mostly custom\n`IPrintWithWriter` implementations). In order to support pretty\nprinting these results as well, the ClojureScript expression to be\nevaluated is always wrapped with `cljs.pprint/pprint` (unless\n`:nrepl.middleware.print/print` is set to `nrepl.util.print/pr` or `cider.nrepl.pprint/pr`, in\nwhich case `cljs.core/pr` is used instead). This means that whenever\nthe Clojure reader fails to read the value for any reason, we can\nsafely fall back on an already (pretty) printed string, albeit\ndisabling `nrepl.middleware.print` and hence effectively ignoring the\n`nrepl.middleware.print` configuration. Special care is taken that\noutput written to `*out*` during evaluation is not affected by the\nwrapping.\n\nFor the cases where the (pretty) printing configuration is not being\napplied, the reader probably failed to read the evaluation results and\nthe above fallbacks are being used instead.\n\n**Note:** See [this pull request](https://github.com/nrepl/piggieback/pull/108)\nfor more background and discussion on the current solution.\n\n## FAQ\n\n### Why \"piggieback\" instead of \"piggyback\"?\n\nThat's one of life's greatest mysteries. Only Chas can answer that one.\n\n### Why is the artifact group id \"cider\" instead of \"nrepl\"?\n\nBozhidar took over the maintenance of Piggieback before taking over\nthe maintenance of nREPL. That's why for a period of time Piggieback lived under\nCIDER's GitHub org and back then it made sense to use CIDER's group id.\nEventually, it got reunited with nREPL, but we've opted to preserve\nthe CIDER group id to avoid further breakages.\n\nFor the same reason the main namespace is `cider.piggieback` instead of\n`nrepl.piggieback`.\n\n### Does Piggieback work with self-hosted ClojureScript REPLs (e.g. Lumo)?\n\nNo, it doesn't. Piggieback is implemented in Clojure and relies on Clojure's ClojureScript evaluation\nAPI (`cljs.repl/IJavaScriptEnv`).\n\nFor self-hosted ClojureScript you'll need a native ClojureScript nREPL implementation like\n[nrepl-cljs](https://github.com/djblue/nrepl-cljs).\n\n### Does shadow-cljs use Piggieback?\n\nNo, it doesn't use it. It's most recommended for shadow-cljs users to avoid including the `cider.piggieback/wrap-cljs-repl` middleware.\n\nUnlike `figwheel`, which relies on Piggieback, `shadow-cljs` provides\nits own nREPL middleware. That's why some features of Piggieback (e.g. pretty-printing)\nmight not be available with `shadow-cljs`.\n\nYou can find `shadow-cljs`'s middleware [here](https://github.com/thheller/shadow-cljs/blob/faab284fe45b04328639718583a7d70feb613d26/src/main/shadow/cljs/devtools/server/nrepl.clj).\n\n## Need Help?\n\nFeel free to create a Github issue or ask on `#cider` on Clojurians Slack if you\nhave questions or would like to contribute patches.\n\n## Acknowledgements\n\n[Nelson Morris](http://twitter.com/xeqixeqi) was instrumental in the initial\ndevelopment of piggieback.\n\n## License\n\nCopyright © 2012-2023 Chas Emerick, Bruce Hauman, Bozhidar Batsov and other contributors.\n\nDistributed under the Eclipse Public License, the same as Clojure.\n\n[vim-fireplace]: https://github.com/tpope/vim-fireplace\n[Cursive]: https://cursive-ide.com/\n[CIDER]: https://github.com/clojure-emacs/CIDER\n[cider-nrepl]: https://github.com/clojure-emacs/cider-nrepl\n[refactor-nrepl]: https://github.com/clojure-emacs/refactor-nrepl\n[CCW]: https://github.com/ccw-ide/ccw\n","funding_links":[],"categories":["Clojure"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnrepl%2Fpiggieback","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnrepl%2Fpiggieback","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnrepl%2Fpiggieback/lists"}