Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/mitranim/clojure-forge
Support library for Clojure servers built on the System/Component pattern
https://github.com/mitranim/clojure-forge
clojure devtools
Last synced: about 1 month ago
JSON representation
Support library for Clojure servers built on the System/Component pattern
- Host: GitHub
- URL: https://github.com/mitranim/clojure-forge
- Owner: mitranim
- Created: 2017-10-27T15:20:47.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2018-07-19T06:35:40.000Z (over 6 years ago)
- Last Synced: 2025-01-03T05:05:34.813Z (about 1 month ago)
- Topics: clojure, devtools
- Language: Clojure
- Size: 19.5 KB
- Stars: 8
- Watchers: 3
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: readme.md
Awesome Lists containing this project
README
## Overview
Support library for Clojure servers built on the [system/component
pattern](https://github.com/stuartsierra/component) and Ring.See example project templates:
* [Clojure with Datomic](https://github.com/Mitranim/clojure-datomic-starter)
* [Clojure with Auth0](https://github.com/Mitranim/clojure-auth0-starter)Provides plumbing:
* a place to store the system singleton, safe from namespace reloads (see below)
* shortcuts for system lifecycle management: recreate, restartProvides a smooth development experience:
* automatic code reload on change
* automatic system reset on code change
* automatic webpage refresh on system reset
* rendering of compile errors and runtime exceptionsInspired by:
* [`component.repl`](https://github.com/stuartsierra/component.repl): system
storage, lifecycle shortcuts
* [`lein-ring`](https://github.com/weavejester/lein-ring): automatic code
reload, webpage refresh, error renderingUses [`clojure.tools.namespace`](https://github.com/clojure/tools.namespace) for
code reload. Adapts it to work in background threads such as filesystem
watchers. Properly refreshes namespaces in nREPL sessions (assumes
`clojure.tools.nrepl`).Comparison with [`component.repl`](https://github.com/stuartsierra/component.repl):
* automatic code reload
* background code reload correctly refreshes the REPL
* integrated webserver goodies: auto refresh on system reset, error rendering
Comparison with [`lein-ring`](https://github.com/weavejester/lein-ring):
* not Ring-specific
* doesn't mess with your build, AOT compilation works properly
* code reload uses
[`clojure.tools.namespace`](https://github.com/clojure/tools.namespace),
avoiding limbo states* code reload involves a system reset, making it easy to redefine background
activities such as job queues* background code reload correctly refreshes the REPL
* code reload works on its own, you don't need a webpage open
* code reload is vastly more reliable
* page refresh is vastly more reliable
## Installation
Add to `project.clj`:
```clj
[com.mitranim/forge "0.1.0"]
```Require in code:
```clj
(:require [com.mitranim.forge :as forge])
```## Usage
```clj
(ns core
(:gen-class)
(:require
[com.mitranim.forge :as forge]
[com.stuartsierra.component :as component]))(defn create-system [prev-system]
(reify
component/Lifecycle
(start [this] (println "starting") this)
(stop [this] (println "stopping") this)))(defn -main []
(forge/reset-system! create-system))(defn -main-dev []
(forge/start-development! {:system-symbol `create-system})
(forge/reset-system! create-system))
```When using Ring, add the middleware that automatically refreshes webpages and
renders errors:```clj
(let [my-ring-handler (forge/wrap-development-features my-ring-handler)])
```Launch your REPL and run an equivalent of this:
```clj
(forge/start-development! {:system-symbol `create-system})
(forge/reset-system! create-system)
forge/sys
```Now, modifying source files or running `(forge/reset)` will trigger a code
reload and system reset. The current system is always stored in `forge/sys`.Enjoy your workflow!
The `template` folder in this repo provides the absolute smallest starting core.
Copy it to start playing around.## API
The most important stuff is listed here. To dig deeper, check the source. It's
simple and hackable.All functions here are thread-safe and idempotent.
#### `set-system-symbol!`
Tells Forge where to find your `create-system` function after a namespace
refresh. Needs to be called once before using `reset`. `start-development!` also
sets this.```clj
(forge/set-system-symbol! `create-system)
forge/system-symbol
```#### `sys`
Stores the current system. Gets modified by `reset` and `reset-system!`. Can be
used to avoid passing the system everywhere. Also convenient in the REPL.```clj
(forge/reset)
forge/sys
```#### `start-development!`
Starts auto-reload and other goodies. Run it once after launching the REPL. See
[Usage](#usage) for example code.#### `reset`
Reloads modified namespaces, recreates and restarts the system. Must be called
after `set-system-symbol!` or `start-development!`.After one `start-development!` call, `reset` runs automatically on every source
change.```clj
; once
(forge/start-development! {:system-symbol `create-system})
(forge/reset)
```#### `reset-system!`
Recreates and restarts the system, storing the result in `#'forge/sys`.
In development, use `reset` instead of this.Define your "create-system" function. It must take one argument, the previous
version of the system, and return the next version _without starting it_.Handles exceptions carefully:
* exception when stopping → store the partially stopped system so you can fix it
manually* exception when starting → stop the partially started system, store the
remainderThe latter can be convenient when debugging production failures. If any
component fails to start, the rest won't keep the JVM from shutting down.```clj
(defn create-system [prev-system]
(component/system-map))(defn -main []
(try (forge/reset-system! create-system)
(catch Throwable err
(shutdown-agents)
(binding [*out* *err*] (prn err))
(System/exit 1))))
```#### `wrap-development-features`
Optional Ring middleware for auto-refresh and error rendering. Add to your
middleware stack as an outer layer, typically just before the 500 handler:```clj
(def handler
(-> routes
... other middleware ...
forge/wrap-development-features
my-500-handler))
```Running `restart-system!` or `reset` will refresh any open webpages.
#### `refresh-namespaces`
Refreshes any modified namespaces. This is a version of
`clojure.tools.namespace.repl/refresh` that works in background threads, so it's
usable in filesystem watchers, HTTP handlers, etc. Used internally by `reset`.```clj
(forge/refresh-namespaces); works
(future (forge/refresh-namespaces))
```**Note:** unlike `(require 'my-ns :reload)`, this completely replaces namespace
objects, breaking `defonce`. To preserve state, keep it in your System and
migrate between system resets. If you have sufficiently good reasons, you can
opt a namespace out of "hard" reload into "soft" reload:```clj
(ns my-ns
(:require
[clojure.tools.namespace.repl :refer [disable-unload!]]))(disable-unload!)
(defonce blah blah)
```#### ...
For lower-level stuff, please run `(dir com.mitranim.forge)` and check the
source; it's annotated and self-explanatory.## Changelog
### 0.1.2
Avoid double status notification on `reset`.
### 0.1.1
More reliable webpage reloading. Now uses websockets to avoid a few edge cases
in long polling.### 0.1.0
init
## Misc
Feedback, criticism, suggestions, and pull requests are welcome!
Open an issue or reach me on skype:mitranim.web or [email protected].