Skip to content

Commit cb19394

Browse files
zampinomk
andauthored
Unify static build and interactive modes (#463)
This unifies the code paths between the static and interactive modes, dropping the static-app namespace and doing the routing in a plain function instead of bringing in the reitit-frontend dep. Co-authored-by: Martin Kavalar <[email protected]>
1 parent 6440baa commit cb19394

File tree

13 files changed

+118
-156
lines changed

13 files changed

+118
-156
lines changed

.github/workflows/main.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ jobs:
196196
run: echo $(yarn global dir)/node_modules
197197

198198
- name: 🧪 Build Rule 30 Notebook with SSR
199-
run: NODE_PATH=$(yarn global dir)/node_modules clojure -J-Dclojure.main.report=stdout -X:demo:nextjournal/clerk :git/sha '"${{ github.sha }}"' :git/url '"https://github.com/nextjournal/clerk"' :index '"notebooks/rule_30.clj"' :paths [] :ssr true :compile-css true
199+
run: NODE_PATH=$(yarn global dir)/node_modules clojure -J-Dclojure.main.report=stdout -X:demo:nextjournal/clerk :git/sha '"${{ github.sha }}"' :git/url '"https://github.com/nextjournal/clerk"' :index '"notebooks/rule_30.clj"' :paths [] :ssr true :compile-css true :exclude-js true
200200

201201
- name: 🔐 Google Auth
202202
uses: google-github-actions/auth@v0

dev/nextjournal/clerk/ssr.clj

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444

4545
(defn render [edn-string]
4646
(force !eval-viewer-source)
47-
(execute-fn context "nextjournal.clerk.static_app.ssr" edn-string))
47+
(execute-fn context "nextjournal.clerk.sci_env.ssr" edn-string))
4848

4949

5050
(comment

notebooks/viewers/html.clj

+4-3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
" notebook."]) nil)
2323

2424
(clerk/html
25-
[:ol (list [:li [:a {:href (clerk/doc-url "notebooks/document_linking.clj")} "Cross Document Linking"]]
26-
[:li [:a {:href (clerk/doc-url "notebooks/rule_30.clj")} "Rule 30"]]
27-
[:li [:a {:href (clerk/doc-url "notebooks/markdown.md")} "Appendix"]])])
25+
[:ol
26+
[:li [:a {:href (clerk/doc-url "notebooks/document_linking.clj")} "Cross Document Linking"]]
27+
[:li [:a {:href (clerk/doc-url "notebooks/rule_30.clj")} "Rule 30"]]
28+
[:li [:a {:href (clerk/doc-url "notebooks/markdown.md")} "Appendix"]]])

render/deps.edn

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
reagent/reagent {:mvn/version "1.2.0"}
77
io.github.babashka/sci.configs {:git/sha "0702ea5a21ad92e6d7cca6d36de84271083ea68f"}
88
io.github.nextjournal/clojure-mode {:git/sha "ac038ebf6e5da09dd2b8a31609e9ff4a65e36852"}
9-
metosin/reitit-frontend {:mvn/version "0.5.15"}
109
thheller/shadow-cljs {:mvn/version "2.23.1"}
1110
io.github.squint-cljs/cherry {;; :local/root "/Users/borkdude/dev/cherry"
1211
:git/sha "ac27030016e5ae8f5280a62eaf81dfe0dc83924b"}}}

shadow-cljs.edn

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
:compiler-options {:source-map true}
99
:dev {:modules {:viewer {:entries [devtools]}}}
1010
:modules {:viewer {:entries [nextjournal.clerk.sci-env
11-
nextjournal.clerk.static-app
1211
nextjournal.clerk.trim-image]}}
1312
:js-options {:output-feature-set :es8}
1413
:build-hooks [(shadow.cljs.build-report/hook

src/nextjournal/clerk.clj

+1-1
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@
402402

403403
(defn ^:private normalize-opts [opts]
404404
(set/rename-keys opts #_(into {} (map (juxt identity #(keyword (str (name %) "?")))) [:bundle :browse :dashboard])
405-
{:bundle :bundle?, :browse :browse?, :dashboard :dashboard? :compile-css :compile-css? :ssr :ssr?}))
405+
{:bundle :bundle?, :browse :browse?, :dashboard :dashboard? :compile-css :compile-css? :ssr :ssr? :exclude-js :exclude-js?}))
406406

407407
(defn ^:private started-via-bb-cli? [opts]
408408
(contains? (meta opts) :org.babashka/cli))

src/nextjournal/clerk/builder.clj

+34-20
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@
110110
(defn build-ui-reporter [{:as build-event :keys [stage]}]
111111
(when (= stage :init)
112112
(builder-ui/reset-build-state!)
113-
((resolve 'nextjournal.clerk/show!) (clojure.java.io/resource "nextjournal/clerk/builder_ui.clj"))
113+
((resolve 'nextjournal.clerk/show!) (find-ns 'nextjournal.clerk.builder-ui))
114114
(when-let [{:keys [port]} (and (get-in build-event [:build-opts :browse]) @webserver/!server)]
115115
(browse/browse-url (str "http://localhost:" port))))
116116
(stdout-reporter build-event)
@@ -225,7 +225,7 @@
225225
(report-fn {:stage :ssr})
226226
(let [{duration :time-ms :keys [result]}
227227
(eval/time-ms (sh {:in (str "import '" (resource->url "/js/viewer.js") "';"
228-
"console.log(nextjournal.clerk.static_app.ssr(" (pr-str (pr-str static-app-opts)) "))")}
228+
"console.log(nextjournal.clerk.sci_env.ssr(" (pr-str (pr-str static-app-opts)) "))")}
229229
"node"
230230
"--abort-on-uncaught-exception"
231231
"--experimental-network-imports"
@@ -238,6 +238,10 @@
238238
(assoc static-app-opts :html out))
239239
(throw (ex-info (str "Clerk ssr! failed\n" out "\n" err) result)))))
240240

241+
(defn cleanup [build-opts]
242+
(select-keys build-opts
243+
[:bundle? :path->doc :path->url :current-path :resource->url :exclude-js? :index :html]))
244+
241245
(defn write-static-app!
242246
[opts docs]
243247
(let [{:as opts :keys [bundle? out-path browse? ssr?]} (process-build-opts opts)
@@ -248,12 +252,14 @@
248252
(when-not (fs/exists? (fs/parent index-html))
249253
(fs/create-dirs (fs/parent index-html)))
250254
(if bundle?
251-
(spit index-html (view/->static-app static-app-opts))
255+
(spit index-html (view/->html (cleanup static-app-opts)))
252256
(doseq [[path doc] path->doc]
253257
(let [out-html (str out-path fs/file-separator (->> path (viewer/map-index opts) ->html-extension))]
254258
(fs/create-dirs (fs/parent out-html))
255-
(spit out-html (view/->static-app (cond-> (assoc static-app-opts :path->doc (hash-map path doc) :current-path path)
256-
ssr? ssr!))))))
259+
(spit out-html (view/->html (-> static-app-opts
260+
(assoc :path->doc (hash-map path doc) :current-path path)
261+
(cond-> ssr? ssr!)
262+
cleanup))))))
257263
(when browse?
258264
(browse/browse-url (-> index-html fs/absolutize .toString path-to-url-canonicalize)))
259265
{:docs docs
@@ -379,30 +385,38 @@
379385
(report-fn {:stage :done :duration duration})))
380386
(report-fn {:stage :finished :state state :duration duration :total-duration (eval/elapsed-ms start)})))
381387

382-
#_(build-static-app! {:paths clerk-docs :bundle? true})
383-
#_(build-static-app! {:paths ["notebooks/index.clj" "notebooks/rule_30.clj" "notebooks/viewer_api.md"] :index "notebooks/index.clj"})
384-
#_(build-static-app! {:paths ["index.clj" "notebooks/rule_30.clj" "notebooks/markdown.md"] :bundle? false :browse? false})
385-
#_(build-static-app! {:paths ["notebooks/viewers/**"]})
386-
#_(build-static-app! {:index "notebooks/rule_30.clj" :git/sha "bd85a3de12d34a0622eb5b94d82c9e73b95412d1" :git/url "https://github.com/nextjournal/clerk"})
387-
#_ (reset! config/!resource->url @config/!asset-map)
388-
#_(swap! config/!resource->url dissoc "/css/viewer.css")
389-
#_(build-static-app! {:ssr? true
388+
(comment
389+
(build-static-app! {:paths clerk-docs :bundle? true})
390+
(build-static-app! {:paths ["notebooks/index.clj" "notebooks/rule_30.clj" "notebooks/viewer_api.md"] :index "notebooks/index.clj"})
391+
(build-static-app! {:paths ["index.clj" "notebooks/rule_30.clj" "notebooks/markdown.md"] :bundle? false :browse? false})
392+
(build-static-app! {:paths ["notebooks/viewers/**"]})
393+
(build-static-app! {:index "notebooks/rule_30.clj" :git/sha "bd85a3de12d34a0622eb5b94d82c9e73b95412d1" :git/url "https://github.com/nextjournal/clerk"})
394+
(reset! config/!resource->url @config/!asset-map)
395+
(swap! config/!resource->url dissoc "/css/viewer.css")
396+
397+
(build-static-app! {:ssr? true
398+
:exclude-js? true
399+
;; test against cljs release `bb build:js`
400+
:resource->url {"/js/viewer.js" "./build/viewer.js"}
401+
:index "notebooks/rule_30.clj"})
402+
403+
(build-static-app! {:ssr? true
390404
:compile-css? true
391405
;; test against cljs release `bb build:js`
392406
:resource->url {"/js/viewer.js" "./build/viewer.js"}
393407
:index "notebooks/rule_30.clj"})
394-
#_(fs/delete-tree "public/build")
395-
#_(build-static-app! {:compile-css? true
408+
(fs/delete-tree "public/build")
409+
(build-static-app! {:compile-css? true
396410
:index "notebooks/rule_30.clj"
397411
:paths ["notebooks/hello.clj"
398412
"notebooks/markdown.md"]})
399-
#_(build-static-app! {;; test against cljs release `bb build:js`
413+
(build-static-app! {;; test against cljs release `bb build:js`
400414
:resource->url {"/js/viewer.js" "/viewer.js"}
401415
:paths ["notebooks/cherry.clj"]
402416
:out-path "build"})
403-
#_(build-static-app! {:paths ["CHANGELOG.md"
417+
(build-static-app! {:paths ["CHANGELOG.md"
404418
"notebooks/markdown.md"
405-
"notebooks/viewers/image.clj"
406-
"notebooks/viewers/html.cj"]
419+
"notebooks/viewers/html.clj"]
420+
:bundle? true
407421
:git/sha "d60f5417"
408-
:git/url "https://github.com/nextjournal/clerk"})
422+
:git/url "https://github.com/nextjournal/clerk"}))

src/nextjournal/clerk/render.cljs

+46-13
Original file line numberDiff line numberDiff line change
@@ -748,29 +748,62 @@
748748
#_(js/console.log :<= type := msg)
749749
(dispatch-fn msg)))
750750

751-
(defonce react-root
752-
(when-let [el (and (exists? js/document) (js/document.getElementById "clerk"))]
753-
(react-client/createRoot el)))
751+
(defonce container-el
752+
(and (exists? js/document) (js/document.getElementById "clerk")))
753+
754+
(defonce hydrate?
755+
(and container-el
756+
(pos? (.-childElementCount container-el))))
754757

758+
(defonce react-root
759+
(when container-el
760+
(if hydrate?
761+
(react-client/hydrateRoot container-el (r/as-element [root]))
762+
(react-client/createRoot container-el))))
755763

756-
(defonce !listeners (atom #{}))
764+
(defonce !router (atom nil))
757765

758766
(defn handle-initial-load [_]
759767
(history-push-state {:path (subs js/location.pathname 1) :replace? true}))
760768

761-
(defn setup-router! []
762-
;; TODO: unify with static app
769+
(defn handle-hashchange [{:keys [url->path path->doc]} ^js e]
770+
(let [url (some-> e .-event_ .-newURL ->URL .-hash (subs 2))]
771+
(when-some [doc (get path->doc (get url->path url))]
772+
(set-state! {:doc doc}))))
773+
774+
(defn listeners [{:as state :keys [mode]}]
775+
(case mode
776+
:path
777+
#{(gevents/listen js/document gevents/EventType.CLICK handle-anchor-click false)
778+
(gevents/listen js/window gevents/EventType.POPSTATE handle-history-popstate false)
779+
(gevents/listen js/window gevents/EventType.LOAD handle-initial-load false)}
780+
781+
:fragment
782+
#{(gevents/listen js/window gevents/EventType.HASHCHANGE (partial handle-hashchange state) false)}))
783+
784+
(defn setup-router! [{:as state :keys [mode]}]
763785
(when (and (exists? js/document) (exists? js/window))
764-
(doseq [listener @!listeners]
786+
(doseq [listener (:listeners @!router)]
765787
(gevents/unlistenByKey listener))
766-
(reset! !listeners #{(gevents/listen js/document gevents/EventType.CLICK handle-anchor-click false)
767-
(gevents/listen js/window gevents/EventType.POPSTATE handle-history-popstate false)
768-
(gevents/listen js/window gevents/EventType.LOAD handle-initial-load false)})))
788+
(reset! !router (assoc state :listeners (listeners state)))))
789+
769790

770791
(defn ^:export ^:dev/after-load mount []
771-
(when react-root
772-
(.render react-root (r/as-element [root]))
773-
(setup-router!)))
792+
(when (and react-root (not hydrate?))
793+
(.render react-root (r/as-element [root]))))
794+
795+
(defn ^:export init [{:as state :keys [bundle? path->doc path->url current-path]}]
796+
(let [static-app? (contains? state :path->doc)] ;; TODO: better check
797+
(if static-app?
798+
(let [url->path (set/map-invert path->url)]
799+
(when bundle? (setup-router! (assoc state :mode :fragment :url->path url->path)))
800+
(set-state! {:doc (get path->doc (or current-path (url->path "")))})
801+
(mount))
802+
(do
803+
(setup-router! {:mode :path})
804+
(set-state! state)
805+
(mount)))))
806+
774807

775808
(defn html-render [markup]
776809
(r/as-element

src/nextjournal/clerk/sci_env.cljs

+5-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
[nextjournal.clojure-mode.commands]
2727
[nextjournal.clojure-mode.extensions.eval-region]
2828
[nextjournal.clojure-mode.keymap]
29+
[reagent.dom.server :as dom-server]
2930
[sci.configs.applied-science.js-interop :as sci.configs.js-interop]
3031
[sci.configs.reagent.reagent :as sci.configs.reagent]
3132
[sci.core :as sci]
@@ -167,10 +168,11 @@
167168
(defn ^:export eval-form [f]
168169
(sci/eval-form (sci.ctx-store/get-ctx) f))
169170

170-
(defn ^:export set-state [state]
171-
(render/set-state! state))
171+
(def ^:export init render/init)
172172

173-
(def ^:export mount render/mount)
173+
(defn ^:export ssr [state-str]
174+
(init (read-string state-str))
175+
(dom-server/render-to-string [render/root]))
174176

175177
(defn reconnect-timeout [failed-connection-attempts]
176178
(get [0 0 100 500 5000] failed-connection-attempts 10000))

src/nextjournal/clerk/static_app.cljs

-76
This file was deleted.

src/nextjournal/clerk/view.clj

+12-29
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
(ns nextjournal.clerk.view
2-
(:require [nextjournal.clerk.config :as config]
3-
[nextjournal.clerk.viewer :as v]
2+
(:require [nextjournal.clerk.viewer :as v]
43
[hiccup.page :as hiccup]
54
[clojure.string :as str]
65
[clojure.java.io :as io])
@@ -51,36 +50,20 @@
5150
;; https://html.spec.whatwg.org/multipage/syntax.html#cdata-rcdata-restrictions
5251
(str/replace s "</script>" "</nextjournal.clerk.view/escape-closing-script-tag>"))
5352

54-
(defn ->html [{:as state :keys [conn-ws?] :or {conn-ws? true}}]
53+
(defn ->html [{:as state :keys [conn-ws? current-path html exclude-js?]}]
5554
(hiccup/html5
5655
[:head
5756
[:meta {:charset "UTF-8"}]
5857
[:meta {:name "viewport" :content "width=device-width, initial-scale=1"}]
59-
(include-css+js state)]
58+
(when current-path (v/open-graph-metas (-> state :path->doc (get current-path) v/->value :open-graph)))
59+
(if exclude-js?
60+
(include-viewer-css state)
61+
(include-css+js state))]
6062
[:body.dark:bg-gray-900
61-
[:div#clerk]
62-
[:script {:type "module"} "let viewer = nextjournal.clerk.sci_env
63+
[:div#clerk html]
64+
(when-not exclude-js?
65+
[:script {:type "module"} "let viewer = nextjournal.clerk.sci_env
6366
let state = " (-> state v/->edn escape-closing-script-tag pr-str) ".replaceAll('nextjournal.clerk.view/escape-closing-script-tag', 'script')
64-
viewer.set_state(viewer.read_string(state));
65-
viewer.mount(document.getElementById('clerk'))\n"
66-
(when conn-ws?
67-
"viewer.connect(document.location.origin.replace(/^http/, 'ws') + '/_ws')")]]))
68-
69-
(defn ->static-app [{:as state :keys [current-path html]}]
70-
(hiccup/html5
71-
[:head
72-
[:title (or (and current-path (-> state :path->doc (get current-path) v/->value :title)) "Clerk")]
73-
[:meta {:charset "UTF-8"}]
74-
[:meta {:name "viewport" :content "width=device-width, initial-scale=1"}]
75-
(when current-path (v/open-graph-metas (-> state :path->doc (get current-path) v/->value :open-graph)))
76-
(include-css+js state)]
77-
[:body
78-
[:div#clerk-static-app html]
79-
[:script {:type "module"} "let state = " (-> state v/->edn escape-closing-script-tag pr-str) ".replaceAll('nextjournal.clerk.view/escape-closing-script-tag', 'script')
80-
let opts = nextjournal.clerk.sci_env.read_string(state)
81-
nextjournal.clerk.static_app.init(opts)\n"]]))
82-
83-
(defn doc->html [opts]
84-
(->html (-> opts
85-
(update :doc doc->viewer)
86-
(assoc :resource->url @config/!resource->url))))
67+
viewer.init(viewer.read_string(state))\n"
68+
(when conn-ws?
69+
"viewer.connect(document.location.origin.replace(/^http/, 'ws') + '/_ws')")])]))

0 commit comments

Comments
 (0)