clojure.tools.namespace

repository·master·Indexed 20 days ago

https://github.com/clojure/tools.namespace

Utilities for managing Clojure namespaces, including parsing ns declarations, finding namespaces on the classpath, and implementing code-reloading mechanisms for REPL-driven development. Features the refresh function for smart reloading based on dependency graphs and file timestamps, with support for both Clojure and ClojureScript.

Tokens
1.4K
Snippets
5
Records
8
Agent score
21%

What's inside clojure.tools.namespace

  1. Overview of clojure.tools.namespace components

    master

    The library is divided into several functional namespaces:

    • clojure.tools.namespace.parse: A syntactic parser for ns declarations. It extracts :require and :use clauses without evaluating code.
    • clojure.tools.namespace.find: Utilities to locate Clojure namespaces on the filesystem, in directories, or within JAR files.
    • clojure.tools.namespace.repl: The primary entry point for reloading code. It manages a dependency graph and handles loading/unloading namespaces. It is composed of dependency, track, file, dir, and reload.
    • clojure.tools.namespace.move: (ALPHA) Utilities for renaming and moving namespaces. Warning: This modifies your source files.

    ClojureScript Support: Namespaces c.t.n.dependency, c.t.n.track, and c.t.n.parse are .cljc files usable in both Clojure and ClojureScript. Most functions in c.t.n.find and c.t.n.file can analyze both Clojure and ClojureScript source files if provided with a platform argument (clj or cljs).

  2. Design patterns for reload-safe applications

    master

    To use refresh effectively without breaking your application, follow these two design principles:

    1. No Global State

    Avoid storing state in global def or defonce Vars (e.g., (def state (atom nil))), as refresh will destroy them. Instead, encapsulate state in a local object (map, record, etc.) and use a constructor function.

    2. Managed Lifecycle

    Implement a consistent start and stop mechanism for your system:

    • start: Acquires resources (sockets, DB connections), starts background threads, and returns an object representing the application state.
    • stop: Takes the state object and releases all resources and background processes.

    Workflow Example:

    1. (def my-app (start-my-app))
    2. Modify code
    3. (stop-my-app my-app)
    4. (refresh)
    5. (def my-app (start-my-app))
  3. Install clojure.tools.namespace

    master

    Depending on your build tool, use the following dependency information to install clojure.tools.namespace version 1.5.1.

    CLI / deps.edn

    org.clojure/tools.namespace {:mvn/version "1.5.1"}

    Leiningen

    [org.clojure/tools.namespace "1.5.1"]

    Maven

    <dependency>
      <groupId>org.clojure</groupId>
      <artifactId>tools.namespace</artifactId>
      <version>1.5.1</version>
    </dependency>
    org.clojure/tools.namespace {:mvn/version "1.5.1"}
  4. Handle errors during refresh

    master

    If an exception occurs while loading a namespace, refresh stops and returns the exception. The namespace that caused the error is left in an unloaded state, and any namespaces depending on it will also not exist.

    To debug, use clojure.repl/pst to print the stacktrace of the exception bound to *e.

    Once the error is fixed, calling refresh again will resume the reloading process.

    ;; If refresh returns an error:
    (refresh)
    ;; :error-while-loading com.example.app
    ;; #<IllegalArgumentException ...>
    
    (clojure.repl/pst)
  5. Common pitfalls and warnings when using refresh

    master

    AOT-compilation

    Reloading does not work with AOT-compiled namespaces. Ensure AOT is disabled and delete any .class files before starting a development session.

    Protocols and Records

    If you reload a namespace containing a protocol, any existing instances of records implementing that protocol will become invalid and throw errors. Always create new instances of records after a refresh.

    Namespace Aliases

    Aliases created in your REPL (e.g., (require '[foo :as f])) will still point to the old version of the namespace after a refresh. To fix this, you must unalias and then re-alias:

    (ns-unalias *ns* 'foo)
    (alias 'foo 'com.example.foo)

    Multimethods

    prefer-method is a global side-effect. If you modify it and reload, you may get a preference conflict. Call remove-method before reloading the namespace containing the multimethod.

  6. Disable unloading or reloading for specific namespaces

    master

    If you have a 'scratch' or 'project REPL' namespace where you want to preserve state during development, you can prevent refresh from automatically unloading or reloading them using disable-unload! and disable-reload! in clojure.tools.namespace.repl.

    (require '[clojure.tools.namespace.repl :refer [disable-unload! disable-reload!]])
    
    (disable-unload! 'my.persistent.namespace)
  7. Reload code using refresh

    master

    The refresh function in clojure.tools.namespace.repl provides a smart way to reload code without restarting the JVM. It scans the classpath, builds a dependency graph, and reloads only the namespaces that have changed (based on file timestamps) in the correct dependency order.

    Crucially, refresh unloads (removes) the namespaces before reloading them. This clears out old definitions but will destroy any state stored in global Vars.

    (require '[clojure.tools.namespace.repl :refer [refresh]])
    
    ;; Initial refresh
    (refresh)
    
    ;; Subsequent refreshes reload only changed files
    (refresh)
  8. Configure refresh to run code after reload

    master

    Because refresh destroys the namespace containing your helper functions, you cannot safely call a local function to restart your app (e.g., (defn restart [] ...)).

    Instead, use the :after option in refresh. This option accepts a fully namespace-qualified symbol that will be executed only after a successful reload.

    ;; Assuming 'dev/start' is defined in your REPL namespace
    (refresh :after 'dev/start)