{"id":15010376,"url":"https://github.com/tonsky/clj-reload","last_synced_at":"2025-05-15T12:06:03.394Z","repository":{"id":219811545,"uuid":"749983863","full_name":"tonsky/clj-reload","owner":"tonsky","description":"Smarter way to reload Clojure code","archived":false,"fork":false,"pushed_at":"2025-05-04T16:47:38.000Z","size":284,"stargazers_count":245,"open_issues_count":3,"forks_count":7,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-05-04T16:50:53.577Z","etag":null,"topics":["clojure","interactive-development","repl"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tonsky.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","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},"funding":{"github":["tonsky"],"patreon":"tonsky"}},"created_at":"2024-01-29T19:12:48.000Z","updated_at":"2025-05-04T16:47:06.000Z","dependencies_parsed_at":"2024-10-12T10:21:01.131Z","dependency_job_id":"24035a67-2b19-4c57-9d86-f506c38e64a1","html_url":"https://github.com/tonsky/clj-reload","commit_stats":null,"previous_names":["tonsky/clj-reload"],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonsky%2Fclj-reload","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonsky%2Fclj-reload/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonsky%2Fclj-reload/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonsky%2Fclj-reload/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tonsky","download_url":"https://codeload.github.com/tonsky/clj-reload/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253153273,"owners_count":21862334,"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","interactive-development","repl"],"created_at":"2024-09-24T19:33:46.047Z","updated_at":"2025-05-15T12:05:58.376Z","avatar_url":"https://github.com/tonsky.png","language":"Clojure","funding_links":["https://github.com/sponsors/tonsky","https://patreon.com/tonsky"],"categories":[],"sub_categories":[],"readme":"![](./.extras/logo.png)\n\nSmarter way to reload Clojure code. Clj-Reload tracks namespace dependencies, unloads namespaces, and then loads them in the correct topological order.\n\nThis is only about namespace dependencies within a single project. It has nothing to do with Leiningen, Maven, JAR files, or repositories.\n\n## Dependency\n\n```clojure\nio.github.tonsky/clj-reload {:mvn/version \"0.9.4\"}\n```\n\n## The problem\n\nDo you love interactive development? Although Clojure is set up perfectly for that, evaluating buffers one at a time can only get you so far.\n\nOnce you start dealing with the state, you get data dependencies, and with them, evaluation order starts to matter, and now you change one line but have to re-eval half of your application to see the change.\n\nBut how do you know which half?\n\n## The solution\n\nClj-reload to the rescue!\n\nClj-reload scans your source dir, figures out the dependencies, tracks file modification times, and when you are finally ready to reload, it carefully unloads and loads back only the namespaces that you touched and the ones that depend on those. In the correct dependency order, too.\n\nLet’s do a simple example.\n\na.clj:\n\n```clojure\n(ns a\n  (:require b))\n```\n\nb.clj:\n\n```clojure\n(ns b\n  (:require c))\n```\n\nc.clj:\n\n```clojure\n(ns c)\n```\n\nImagine you change something in `b.clj` and want to see these changes in your current REPL. What do you do?\n\nIf you call\n\n```clojure\n(clj-reload.core/reload)\n```\n\nit will notice that\n\n- `b.clj` was changed,\n- `a.clj` depends on `b.clj`,\n- there’s `c.clj` but it doesn’t depend on `a.clj` or `b.clj` and wasn’t changed.\n\nThen the following will happen:\n\n```\nUnloading a\nUnloading b\nLoading b\nLoading a\n```\n\nSo:\n\n- `c` wasn’t touched — no reason to,\n- `b` was reloaded because it was changed,\n- `a` was loaded _after_ the new version of `b` was in place. Any dependencies `a` had will now point to the new versions of `b`.\n\nThat’s the core proposition of `clj-reload`.\n\n## Usage\n\nInitialize:\n\n```clojure\n(require '[clj-reload.core :as reload])\n\n(reload/init\n  {:dirs [\"src\" \"dev\" \"test\"]})\n```\n\n`:dirs` are relative to the working directory.\n\nUse:\n\n```clojure\n(reload/reload)\n; =\u003e {:unloaded [a b c], :loaded [c b a]}\n```\n\nWorks best if assigned to a shortcut in your editor.\n\n## Usage: recovering from errors\n\n`reload` can be called multiple times. If reload fails, fix the error and call `reload` again.\n\nAlternatively, you can call `unload` which will unload all changed code and will not try loading it back. Fix the code and call `reload` again to load it back.\n\n## Usage: Return value\n\n`reload` returns a map of namespaces that were reloaded:\n   \n```clojure\n{:unloaded [\u003csymbol\u003e ...]\n :loaded   [\u003csymbol\u003e ...]}\n```\n\nBy default, `reload` throws if it can’t load a namespace. You can change it to return exception instead:\n\n```clojure\n(reload/reload {:throw false})\n\n; =\u003e {:unloaded  [a b c]\n;     :loaded    [c b]\n;     :failed    b\n;     :exception \u003cThrowable\u003e}\n```\n\n## Usage: Choose what to reload\n\nBy default, clj-reload will only reload namespaces that were both:\n\n- Already loaded\n- Changed on disk\n\nIf you pass `:only :loaded` option to `reload`, it will reload all currently loaded namespaces, no matter if they were changed or not.\n\nIf you pass regexp to `:only`, clj-reload will search for matching namespaces and load them. This is useful, for example, if you want to find all tests namespaces:\n\n```\n(reload/reload {:only #\".*\\.-test\"})\n```\n\nThis will reload all changed namespaces, then find and load all unloaded namespaces that match the pattern.\n\nFinally, if you pass `:only :all` option to `reload`, it will reload all namespaces it can find in the specified `:dirs`, no matter whether loaded or changed.\n\n## Usage: Skipping reload\n\nSome namespaces contain state you always want to persist between reloads. E.g. running web-server, UI window, etc. To prevent these namespaces from reloading, add them to `:no-reload` during `init`:\n\n```clojure\n(reload/init\n  {:dirs ...\n   :no-reload '#{user myapp.state ...}})\n```\n\nAlternatively, if you want to never unload some namespace, but still reload it (e.g. it contains important state, but only in `defonce`-s), use `:no-unload`:\n\n```clojure\n(reload/init\n  {:dirs ...\n   :no-unload '#{app.main ...}})\n```\n\n`:no-reload` implies `:no-unload`.\n\n## Usage: Unload hooks\n\nSometimes your namespace contains stateful resource that requires proper shutdown before unloading. For example, if you have a running web server defined in a namespace and you unload that namespace, it will just keep running in the background.\n\nTo work around that, define an unload hook:\n\n```clojure\n(def my-server\n  (server/start app {:port 8080}))\n\n(defn before-ns-unload []\n  (server/stop my-server))\n```\n\n`before-ns-unload` is the default name for the unload hook. If a function with that name exists in a namespace, it will be called before unloading.\n\nYou can change the name (or set it to `nil`) during `init`:\n\n```clojure\n(reload/init\n  {:dirs [...]\n   :unload-hook 'my-unload})\n```\n\nFor symmetry, there’s also `:reload-hook 'after-ns-reload` that triggers after reload.\n\n## Usage: Keeping vars between reloads\n\nOne of the main innovations of `clj-reload` is that it can keep selected variables between reloads.\n\nTo do so, just add `^:clj-reload/keep` to the form:\n\n```clojure\n(ns test)\n\n(defonce x\n  (rand-int 1000))\n\n^:clj-reload/keep\n(def y\n  (rand-int 1000))\n\n^:clj-reload/keep\n(defrecord Z [])\n```\n\nand then reload:\n\n```clojure\n(let [x test/x\n      y test/y\n      z (test/-\u003eZ)]\n  \n  (reload/reload)\n  \n  (let [x' test/x\n        y' test/y\n        z' (test/-\u003eZ)]\n    (is (= x x'))\n    (is (= y y'))\n    (is (identical? (class z) (class z')))))\n```\n\nHere’s how it works:\n\n- `defonce` works out of the box. No need to do anything.\n- `def`/`defn`/`deftype`/`defrecord`/`defprotocol` can be annotated with `^:clj-reload/keep` and can be persisted too.\n- Project-specific forms can be added by extending `clj-reload.core/keep-methods` multimethod.\n\nWhy is this important? With `tools.namespace` you will structure your code in a way that will work with its reload implementation. For example, you’d probably move persistent state and protocols into separate namespaces, not because logic dictates it, but because reload library will not work otherwise.\n\n`clj-reload` allows you to structure the code the way business logic dictates it, without the need to adapt to developer workflow.\n\nSimply put: the fact that you use `clj-reload` during development does not spill into your production code.\n\n## Usage: init-less workflow\n\nSometimes it might be useful to integrate `clj-reload` into your system-wide profile or into tool like CIDER to be available in all your projects without explicitly adding it as a dependency.\n\nTo support that, `clj-reload`:\n\n- Lets you skip `init`, in which case it’ll initialize with every directory it can find on classpath,\n- Supports `:clj-reload/no-reload` and `:clj-reload/no-unload` meta on namespace symbol, like this:\n\n```clojure\n(ns ^:clj-reload/no-reload no-reload\n  (:require ...))\n```\n\nIn that case, you can just call `clj-reload.core/reload` and it should work with default settings.\n\nTo disable initial `init`, set environment variable:\n\n```sh\nCLJ_RELOAD_AUTO_INIT=false clj -M -m ...\n```\n\nor system property:\n\n```sh\nclj -J-Dclj-reload.auto-init=false -M -m ...\n```\n\n## Usage: Reloading custom file types\n\nIf you have custom file types, like `*.repl`, you can specify that `clj-reload` should scan and reload them too:\n\n```clojure\n(reload/init\n  {:dirs  [...]\n   :files #\".*[.](clj|cljc|repl)\"})\n```\n\n## Usage: Finding namespaces\n\nSometimes you just want to know what namespaces are there. Since `clj-reload` does this work already anyways, you can do it, too:\n\n```clojure\n(reload/find-namespaces #\".*-test\")\n;; =\u003e #{lib.core-test lib.impl-test ...}\n```\n\n## Usage: Controlling output\n\nIn init, you can set how much `clj-reload` will log during reload. Verbose (default):\n\n```\n=\u003e (reload/init {:output :verbose})\n=\u003e (reload/reload)\n\nUnloading clojure+.print-test\nUnloading clojure+.print\nUnloading clojure+.error\nLoading clojure+.error\nLoading clojure+.print\nLoading clojure+.print-test\n```\n\nQuieter:\n\n```\n=\u003e (reload/init {:output :quieter})\n=\u003e (reload/reload)\n\nReloaded 4 namespaces\n```\n\nQuiet (no output):\n\n```\n=\u003e (reload/init {:output :quiet})\n=\u003e (reload/reload)\n```\n\n## Comparison: Evaluating buffer\n\nThe simplest way to reload Clojure code is just re-evaluating an entire buffer.\n\nIt works for simple cases but fails to account for dependencies. If something depends on your buffer, it won’t see these changes.\n\nThe second pitfall is removing/renaming vars or functions. If you had:\n\n```clojure\n(def a 1)\n\n(def b (+ a 1))\n```\n\nand then change it to just\n\n```clojure\n(def b (+ a 1))\n```\n\nit will still compile! New code is evaluated “on top” of the old one, without unloading the old one first. The definition of `a` will persist in the namespace and let `b` compile.\n\nIt might be really hard to spot these errors during long development sessions.\n\n## Comparison: `(require ... :reload-all)`\n\nClojure has `:reload` and `:reload-all` options for `require`. They do track upstream dependencies, but that’s about it.\n\nIn our original example, if we do\n\n```clojure\n(require 'a :reload-all)\n```\n\nit will load both `b` and `c`. This is excessive (`b` or `c` might not have changed), doesn’t keep track of downstream dependencies (if we reload `b`, it will not trigger `a`, only `c`) and it also “evals on top”, same as with buffer eval.\n\n## Comparison: tools.namespace\n\n[tools.namespace](https://github.com/clojure/tools.namespace) is a tool originally written by Stuart Sierra to work around the same problems. It’s a fantastic tool and the main inspiration for `clj-reload`. I’ve been using it for years and loving it, until I realized I wanted more.\n\nSo the main proposition of both `tools.namespace` and `clj-reload` is the same: they will track file modification times and reload namespaces in the correct topological order.\n\nThis is how `clj-reload` is different:\n\n- `tools.namespace` reloads every namespace it can find. `clj-reload` only reloads the ones that were already loaded. This allows you to have broken/experimental/auxiliary files lie around without breaking your workflow [TNS-65](https://clojure.atlassian.net/browse/TNS-65)\n\n- First reload in `tools.namespace` always reloads everything. In `clj-reload`, even the very first reload only reloads files that were actually changed [TNS-62](https://clojure.atlassian.net/browse/TNS-62)\n\n- `clj-reload` supports namespaces split across multiple files (like `core_deftype.clj`, `core_defprint.clj` in Clojure) [TNS-64](https://clojure.atlassian.net/browse/TNS-64)\n\n- `clj-reload` can see dependencies in top-level standalone `require` and `use` forms [TNS-64](https://clojure.atlassian.net/browse/TNS-64)\n\n- `clj-reload` supports load and unload hooks per namespace. Since `tools.namespace` doesn’t report which namespaces it’s going to reload, you always have to rebuild the entire state [TNS-63](https://clojure.atlassian.net/browse/TNS-63)\n\n- `clj-reload` can specify exclusions during configuration, without polluting the source code of those namespaces.\n\n- `clj-reload` can keep individual vars around and restore previous values after reload. E.g. `defonce` doesn’t really work with `tools.namespace`, but it does with `clj-reload`.\n\n- `clj-reload` has 2× smaller codebase and 0 runtime dependencies.\n\n- `clj-reload` doesn’t support ClojureScript. Patches welcome.\n\n## A word of caution\n\nClj-reload works by removing whole namespaces. Everything in them including vars is gone. This is different from how Clojure REPL usually works, in which calling `def` twice will just override its _value_, keeping the var itself the same.\n\nSo if you store a link to a var somewhere, it’ll be pointing to the old version after reload. If you required/aliased a namespace, it’ll be pointing to the old version after reload.\n\nFor your code to see new changes, use `(resolve 'full.ns/sym)` instead of just `full.ns/sym` or even `@#'full.ns/sym`, and do not put namespaces you’ll be reloading in `:require` portion of your REPL namespace.\n\n## ClojureScript?\n\n`clj-reload` doesn’t support ClojureScript. Patches are welcome.\n\n## License\n\nCopyright © 2024 Nikita Prokopov\n\nLicensed under [MIT](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftonsky%2Fclj-reload","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftonsky%2Fclj-reload","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftonsky%2Fclj-reload/lists"}