Skip to content

Commit

Permalink
Support dev mode for better debugging of components
Browse files Browse the repository at this point in the history
When a component throws an exception in dev-mode, its output is
replaced with a component error info panel that makes debuggin
easier. This also prevents half written output from messing up the
page markup completely.
  • Loading branch information
tatut committed Mar 6, 2024
1 parent fc70f94 commit d62636e
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 7 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,22 @@ This can be used to avoid passing in every piece of context to all components (l
or db connection pools). The set of vars to capture must be configured when calling
`ripley.html/render-response`.

### Dev mode

In development mode, Ripley can be made to replace any `html` macro with an error description panel
that shows exception information and the body source of the form.
This has some performance penalty as all components will first be output into an in-memory `StringWriter`
instead of directly to the response.

Dev mode can be enabled with the system property argument `-Dripley.dev-mode=true` or by setting the
`ripley.html/dev-mode?` atom to true before any `ripley.html/html` macroexpansions take place.


## Changes

### 2024-03-06
- Dev mode: replace component with an error display when an exception is thrown

### 2023-12-27
- Bugfix: also cleanup source that is only used via other computed sources

Expand Down
50 changes: 43 additions & 7 deletions src/ripley/html.clj
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,23 @@
[ripley.live.source :as source]
[ripley.impl.output :refer [*html-out*]]
[ripley.live.patch :as patch]
[ripley.impl.dynamic :as dynamic])
[ripley.impl.dynamic :as dynamic]
[clojure.tools.logging :as log])
(:import (org.apache.commons.lang3 StringEscapeUtils)))

(set! *warn-on-reflection* true)

(def ^:dynamic *raw-text-content* false)

;; If dev-mode? is true, all html expansions will include
;; an error handler that first writes to string and only
;; outputs the component if it didn't throw an exception.
;;
;; If the component throws an exception, the exception
;; info is output instead of the component.
(defonce dev-mode?
(atom (= "true" (System/getProperty "ripley.dev-mode"))))

(defn out! [& things]
(doseq [thing things]
(.write ^java.io.Writer *html-out* (str thing))))
Expand Down Expand Up @@ -582,19 +592,45 @@
combine-adjacent-out)))
form))

(defn- wrap-try [form]
`(try
~form
(catch Throwable t#
(println "Exception in HTML rendering: " t#))))
(defn component-error [ex body]
(let [pretty #(dyn! (with-out-str ((requiring-resolve 'clojure.pprint/pprint) %)))]
(out! "<div style=\"border: dotted 2px red; padding: 0.5rem;\" class=\"ripley-error\"> ")
(out! "<details><summary>Render exception: ")
(dyn! (ex-message ex))
(out! "</summary><pre style=\"white-space: pre-line;\" class=\"ripley-exception\">")
(dyn! (pretty ex))
(out! "</pre></details>")
(out! "<details>")
(out! "<summary>Component body</summary>")
(out! "<pre style=\"white-space: pre-line;\" class=\"ripley-source\">") (pretty body) (out! "</pre>")
(out! "</details>")
(out! "</div>")))

(defn- wrap-try [original-body form]
(if @dev-mode?
`(let [[err# out#] (try
(binding [*html-out* (java.io.StringWriter.)]
~form
[nil (str *html-out*)])
(catch Throwable t#
[t# nil]))]
(if err#
(component-error err# ~(pr-str original-body))
(out! out#)))

;; If not in dev-mode, just catch and log error
`(try
~form
(catch Throwable t#
(log/warn "Exception in HTML rendering: " t#)))))

(defmacro html
"Compile hiccup to HTML output."
[body]
(->> body
compile-html
optimize
wrap-try))
(wrap-try body)))



Expand Down

0 comments on commit d62636e

Please sign in to comment.