Skip to content

Commit d100c5f

Browse files
committed
improve error reporting
1 parent c3c32ed commit d100c5f

File tree

3 files changed

+118
-39
lines changed

3 files changed

+118
-39
lines changed

core/src/uix/core.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@
137137
rsc-id (str *ns* "/" fname)]
138138
`(def ~fname
139139
(with-meta
140-
(core/fn [& ~args-sym]
140+
(core/fn ~(symbol (str "comp-" fname)) [& ~args-sym]
141141
~(with-props-cond props-cond `(first ~args-sym))
142142
(let [~args ~args-sym
143143
~(or rest-sym `_#) (dissoc (first ~args-sym) ~@dissoc-ks)]

core/src/uix/rsc.cljc

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
#?(:clj (:refer-clojure :exclude [partial]))
33
#?(:cljs (:require-macros [uix.rsc]))
44
(:require #?@(:cljs [["@roman01la/react-server-dom-esm/client" :as rsd-client]
5+
[cljs-bean.core :as bean]
56
[clojure.edn :as edn]
67
[clojure.walk :as walk]
8+
[clojure.string :as str]
79
[reitit.core :as r]
810
[reitit.frontend :as rf]
911
[reitit.frontend.easy :as rfe]])
@@ -164,16 +166,49 @@
164166
:padding "32px 0 0"}}
165167
($ :div {:style {:max-width 800}}
166168
($ :div {:style {:color "rgb(206, 17, 38)"
167-
:font-size 21}}
169+
:font-size 21
170+
:margin-bottom 24}}
168171
(.-message error))
169-
($ :pre {:style {:font-size 12
170-
:max-height 360
171-
:overflow-y :auto
172-
:background-color "rgba(206, 17, 38, 0.05)"
173-
:margin "24px 0 16px"
174-
:padding 8
175-
:border-radius 5}}
176-
($ :code (.-stack error)))
172+
(let [{:keys [src line start-line frame-line name]} (bean/bean js/__ERROR_SRC)]
173+
($ :<>
174+
($ :div {:style {:font-size 12
175+
:color "#5a5a5a"}}
176+
name)
177+
($ :pre {:style {:font-size 12
178+
:max-height 360
179+
:overflow-y :auto
180+
:background-color "rgba(206, 17, 38, 0.05)"
181+
:margin "16px 0"
182+
:padding "8px 0"
183+
:border-radius 5}}
184+
($ :code
185+
(->> (str/split-lines src)
186+
(map-indexed (fn [idx ln]
187+
(if (= idx line)
188+
($ :div {:key idx
189+
:style {:background-color "#ff000047"
190+
:padding "0 8px"}}
191+
(str (+ start-line idx) " ")
192+
ln)
193+
($ :div {:key idx
194+
:style {:padding "0 8px"}}
195+
(str (+ start-line idx) " ")
196+
ln)))))))
197+
($ :pre {:style {:font-size 12
198+
:max-height 360
199+
:overflow-y :auto
200+
:background-color "rgba(206, 17, 38, 0.05)"
201+
:margin "24px 0 16px"
202+
:padding "8px 0"
203+
:border-radius 5}}
204+
($ :code
205+
(->> (str/split-lines (.-stack error))
206+
(map-indexed (fn [idx ln]
207+
($ :div
208+
{:key idx
209+
:style {:background-color (when (= idx frame-line) "#ff000047")
210+
:padding "0 8px"}}
211+
ln))))))))
177212
($ :div {:style {:font-size 12
178213
:color "#5a5a5a"}}
179214
"This screen is visible only in development. It will not appear if the app crashes in production. Open your browser’s developer console to further inspect this error.")))
@@ -376,10 +411,12 @@
376411
(let [done-count (atom 0)
377412
set-done #(when (== 2 (swap! done-count inc))
378413
(on-chunk :done))
414+
sb (server.flight/create-state)
415+
emit-error #(when-let [err (:error-component @sb)]
416+
(on-chunk (str "<script>window.__ERROR_SRC = " (json/generate-string err) ";</script>")))
379417
handle-chunk #(if (= :done %)
380-
(set-done)
418+
(do (emit-error) (set-done))
381419
(on-chunk (str "<script>window.__FLIGHT_DATA.push(" (json/generate-string %) ");</script>")))
382-
sb (server.flight/create-state)
383420
ast (loader/run-with-loader
384421
#(server.flight/-unwrap src sb))
385422
*state (volatile! :state/root)]

dom/src/uix/dom/server/flight.clj

Lines changed: 69 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
(ns uix.dom.server.flight
2-
(:require [clojure.string :as str]
2+
(:require [clojure.java.io :as io]
3+
[clojure.string :as str]
34
[clojure.walk :as walk]
45
[uix.compiler.attributes :as attrs]
56
[uix.core :refer [$ defui]]
67
[uix.dom.server :as dom.server]
78
[uix.rsc.loader :as loader]
89
[cheshire.core :as json]
910
[clojure.core.async :as async])
10-
(:import [clojure.lang IBlockingDeref IPersistentVector ISeq]))
11+
(:import [clojure.lang IBlockingDeref IPersistentVector ISeq RT]
12+
(java.io InputStreamReader LineNumberReader PushbackReader)))
1113

1214
(defprotocol FlightRenderer
1315
(-unwrap [this sb]
@@ -163,6 +165,8 @@
163165
(keyword? tag) (render-dom-element el sb)
164166
:else (throw (IllegalArgumentException. (str (type tag) " " tag " is not a valid element type")))))
165167

168+
(defrecord RenderingError [^Throwable error var])
169+
166170
(defn- unwrap-component-element [[tag :as el] sb]
167171
(let [props (normalize-props el)
168172
v (try
@@ -171,7 +175,8 @@
171175
(tag props)
172176
(tag)))
173177
(catch Throwable e
174-
e))]
178+
(let [v (-> tag meta :rsc/id symbol resolve)]
179+
(RenderingError. e v))))]
175180
(-unwrap v sb)))
176181

177182
(defn- unwrap-fragment-element [[tag attrs & children] sb]
@@ -214,6 +219,63 @@
214219
:fallback (-unwrap fallback sb)
215220
:children tags}]))
216221

222+
(defn- source-fn [v]
223+
(when-let [filepath (:file (meta v))]
224+
(with-open [rdr (LineNumberReader. (io/reader filepath))]
225+
(dotimes [_ (dec (:line (meta v)))] (.readLine rdr))
226+
(let [text (StringBuilder.)
227+
pbr (proxy [PushbackReader] [rdr]
228+
(read [] (let [i (proxy-super read)]
229+
(.append text (char i))
230+
i)))
231+
read-opts (if (.endsWith ^String filepath "cljc") {:read-cond :allow} {})]
232+
(if (= :unknown *read-eval*)
233+
(throw (IllegalStateException. "Unable to read source while *read-eval* is :unknown."))
234+
(read read-opts (PushbackReader. pbr)))
235+
(str text)))))
236+
237+
(defn- render-error [^RenderingError this sb]
238+
(let [{:keys [^Throwable error var]} this
239+
st (.getStackTrace error)
240+
stack (->> st
241+
(mapv (fn [^java.lang.StackTraceElement frame]
242+
(str/join " "
243+
[" at"
244+
(str (.getClassName frame)
245+
" "
246+
(.getMethodName frame))
247+
(str
248+
"("
249+
(.getFileName frame)
250+
":"
251+
(.getLineNumber frame)
252+
")")])))
253+
(into [(str (type error) ": " (ex-message error))])
254+
(str/join "\n"))
255+
src (str "E" (json/generate-string
256+
{:digest ""
257+
:name (str (type error))
258+
:message (str (type error) " " (ex-message error))
259+
:stack stack}))
260+
id (get-cached-id sb :errors src)
261+
component-src (source-fn var)
262+
{:keys [line ns name]} (meta var)
263+
component-ns (str (-> ns str munge) "$comp_")
264+
[frame-line error-line] (reduce
265+
(fn [[idx _] ^java.lang.StackTraceElement f]
266+
(if (str/starts-with? (.getClassName f) component-ns)
267+
(reduced [(inc idx) (.getLineNumber f)])
268+
[(inc idx) nil]))
269+
[0 nil]
270+
st)
271+
marker-line-offset (- (or error-line line) line)]
272+
(swap! sb assoc :error-component {:src component-src
273+
:line marker-line-offset
274+
:start-line line
275+
:frame-line frame-line
276+
:name (str ns "/" name)})
277+
(str "$L" id)))
278+
217279
(defn unwrap-element [[tag :as el] sb]
218280
(cond
219281
(client-component? tag) el
@@ -252,32 +314,11 @@
252314
(-render [this sb]
253315
(-render (str this) sb))
254316

255-
Throwable
317+
RenderingError
256318
(-unwrap [this sb]
257319
this)
258320
(-render [this sb]
259-
(let [stack (->> (.getStackTrace this)
260-
(mapv (fn [^java.lang.StackTraceElement frame]
261-
(str/join " "
262-
[" at"
263-
(str (.getClassName frame)
264-
" "
265-
(.getMethodName frame))
266-
(str
267-
"("
268-
(.getFileName frame)
269-
":"
270-
(.getLineNumber frame)
271-
")")])))
272-
(into [(str (type this) ": " (ex-message this))])
273-
(str/join "\n"))
274-
src (str "E" (json/generate-string
275-
{:digest ""
276-
:name (str (type this))
277-
:message (str (type this) " " (ex-message this))
278-
:stack stack}))
279-
id (get-cached-id sb :errors src)]
280-
(str "$L" id)))
321+
(render-error this sb))
281322

282323
IBlockingDeref
283324
;; async values serializer
@@ -314,7 +355,8 @@
314355
:suspended {}
315356
:imports {}
316357
:refs {}
317-
:errors {}}))
358+
:errors {}
359+
:error-component {}}))
318360

319361
(defn- render-result [src result sb]
320362
(let [id (get-id sb)]

0 commit comments

Comments
 (0)