Skip to content

user.clj tips and constraints #16

@practicalli-johnny

Description

@practicalli-johnny

#The user namespace is the default used by the REPL. Adding a custom user.clj file that defines a user namespace automatically loads (reads & evaluates) all the code in that user.clj file.

This is a common practice in Clojure and can be very effective way of loading in tools useful for improving the Clojure development experience

The user namespace should not be used as part of the application code or have anything related to run ing any code that goes into production

user namespace is not loaded at the very end of starting the Clojure runtime, so setting dynamic vars may not be possible (TODO investigate startup order of Clojure and what is not possible in a user.clj file)

A custom user namespace can cause problems and even prevent the REPL from starting

Tips and constraints for using a user.clj

Alternative: load a specific file

clojure -M:project/build -i build.clj -r to start a REPL in "build" mode.

-i loads build.clj as an "init script", -r signals that a REPL should be started.

REPL starts in the user ns with build.clj loaded, allowing use of (build/database-setup {}) or (build/run-tests {:projects '{api auth login]})

Keep user.clj seperate

The custome user namespace should never be part of the default classpath for a project and should not be packaged in a jar or uberjar

Never require the user namespace from other namespaces, consider it a top-level namespace that may refer to other development only namespaces (TODO: environment namespace example)

Create a dev/user.clj and include it only when doing local development

TODO investigate how to keep dev/user.clj separated in Leiningen

Packaging apps

TODO investigate packaging apps with Clojure CLI

Assuming user.clj is on a dev profile classpath, then ensure it doesn't get included by using the production profile, which excludes all other profile configuration

lein with-profile production uberjar

There should be only one user.clj

Clojure looks for the first user.clj file on the classpath during runtime initialization. So if there are multiple ones, which one it finds first is entirely dependent on the order of your classpath. With clj, you can see your classpath with clj -Spath. The local project paths are always placed first (followed by your dependencies). Generally, I think it would be bad for a library to publish with a user.clj, so you should only find them locally.

If you always want to find some special one first, you should ensure it’s in the first path in your source :paths.

require expressions

Limit the number of required libraries

  • more libraries (and there dependencies) increases startup time and increases risk of conflict. Use minimum amount of libraries and consider excluding transitory dependencies

  • requiring-resolve to load on demand (TODO find out more about this) and optionally load dependency namespaces only if they are on the classpath

  • https://blog.flowthing.me/clojure-repl-start/ require-resolve examples

Reload code

When reloading code with integrant, mount, etc ensure directories like dev are included so the custom dev/user.clj is found on the classpath

(clojure.tools.namespace.repl/set-refresh-dirs "src" "test" "dev")

Constraints due to Clojure startup order

Dynamic vars have to be bound at the root before you can rebind them dynamically. The repl binds a bunch of dyn vars for you but user.clj is loaded before the repl

You could alter-var-root, but the repl is going to rebind over you (the default is actually false - the repl rebinds it to true)

There is not currently an easy affordance to change this, but you could start your own repl and do whatever you want in it
https://insideclojure.org/2020/02/11/custom-replan
https://insideclojure.org/2020/02/11/custom-repl/

TODO some things to try to test startup order of Clojure and user.clj

(set! print-namespace-maps false)

https://clojurians.slack.com/archives/C03S1KBA2/p1655233677529089?thread_ts=1655233677.529089&cid=C03S1KBA2

 (defmethod print-method clojure.lang.IPersistentMap [m, ^java.io.Writer w]
   "From https://clojuredocs.org/clojure.core/*print-namespace-maps*"
   (#'clojure.core/print-meta m w)
   (#'clojure.core/print-map m #'clojure.core/pr-on w))

Lint and LSP

disable warning for unused imports? have user.clj with:

 (ns user
   (:require [integrant.repl :refer [clear go halt prep reset reset-all]]
             [integrant.repl.state :as state]))
, which causes (for all of the unused ones):
 ... warning: #'integrant.repl/clear is referred but never used

Clj-kondo config

 {:config-in-ns {user {:linters {:unused-referred-var {:level :off}}}}}

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Options

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions