Skip to content

Commit 0a0f1e2

Browse files
authored
Add dynamic js/import for JavaScript Modules (#304)
* Extend js global namespace with dynamic `js/import` * Add convenience react hook wrappers
1 parent 8ed0c35 commit 0a0f1e2

File tree

5 files changed

+73
-2
lines changed

5 files changed

+73
-2
lines changed

notebooks/js_import.clj

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
;; # 📦 Dynamic JS Imports
2+
(ns js-import
3+
{:nextjournal.clerk/visibility {:code :hide :result :hide}}
4+
(:require [nextjournal.clerk :as clerk]
5+
[nextjournal.clerk.viewer :as clerk.viewer]
6+
[clojure.data.csv :as csv]))
7+
8+
;; This example uses [Observable Plots](https://observablehq.com/plot) with data from https://allisonhorst.github.io/palmerpenguins/
9+
10+
(defn parse-float [^String s] (Float/parseFloat s))
11+
12+
^{::clerk/visibility {:code :show}}
13+
(def observable-plot-viewer
14+
{:transform-fn clerk/mark-presented
15+
:render-fn
16+
'(fn [data _]
17+
[nextjournal.clerk.render/with-dynamic-import
18+
{:module "https://cdn.skypack.dev/@observablehq/[email protected]"}
19+
(fn [Plot]
20+
[:div {:ref (fn [el]
21+
(when el
22+
(let [dot-plot (.. Plot
23+
(dot (clj->js data)
24+
(j/obj :x "flipper_length_mm"
25+
:y "body_mass_g"
26+
:fill "species"))
27+
(plot (j/obj :grid true)))]
28+
(doto el
29+
(.append (.legend dot-plot "color"))
30+
(.append dot-plot)))))}])])})
31+
32+
^{::clerk/visibility {:code :show :result :show}
33+
::clerk/viewer observable-plot-viewer}
34+
(def palmer-penguins
35+
(-> (slurp "https://nextjournal.com/data/Qmf6FJyJxBQnB6TUZ3J9pdzHSs8UoewoY6WfdZHu1XxkD8?filename=penguins.csv&content-type=text/csv")
36+
(csv/read-csv)
37+
clerk.viewer/use-headers
38+
clerk.viewer/normalize-table-data
39+
(as-> data
40+
(let [{:keys [head rows]} data]
41+
(map (fn [row] (zipmap head (reduce #(update %1 %2 parse-float) row [1 2 3 4])))
42+
rows)))))
43+
44+
;; or use `js/import` directly:
45+
^{::clerk/visibility {:result :show :code :show} ::clerk/no-cache true ::clerk/width :wide}
46+
(nextjournal.clerk/with-viewer
47+
'(fn [_]
48+
(let [cc (nextjournal.clerk.render.hooks/use-promise
49+
(js/import "https://cdn.skypack.dev/canvas-confetti"))
50+
ref (nextjournal.clerk.render.hooks/use-ref)]
51+
(when cc
52+
[:div
53+
[:button.bg-teal-500.hover:bg-teal-700.text-white.font-bold.py-2.px-4.rounded.rounded-full.font-sans
54+
{:on-click #((.create cc @ref)
55+
(j/lit {:spread 80 :angle 45 :startVelocity 20 :origin {:x 0.25 :y 0.5}}))} "Peng 🎉!"]
56+
[:canvas
57+
{:ref ref
58+
:style {:width "100%" :height "500px"}}]]))) nil)

src/nextjournal/clerk/builder.clj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"eval_cljs"
2727
"example"
2828
"hiding_clerk_metadata"
29+
"js_import"
2930
"multiviewer"
3031
"pagination"
3132
"paren_soup"

src/nextjournal/clerk/render.cljs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,12 @@
718718
(f package)
719719
loading-view))
720720

721+
(defn with-dynamic-import [{:keys [module loading-view]
722+
:or {loading-view default-loading-view}} f]
723+
(if-let [package (hooks/use-dynamic-import module)]
724+
(f package)
725+
loading-view))
726+
721727
(defn render-vega-lite [value]
722728
(let [handle-error (hooks/use-error-handler)
723729
vega-embed (hooks/use-d3-require "[email protected]")

src/nextjournal/clerk/render/hooks.cljs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
(:require ["d3-require" :as d3-require]
33
["react" :as react]
44
[reagent.ratom]
5+
[shadow.esm :as esm]
56
["use-sync-external-store/shim" :refer [useSyncExternalStore]]))
67

78
;; a type for wrapping react/useState to support reset! and swap!
@@ -152,3 +153,7 @@
152153
list))
153154
#js[(str package)])]
154155
(use-promise p)))
156+
157+
(defn ^js use-dynamic-import [mod]
158+
(let [p (use-memo #(esm/dynamic-import mod) [mod])]
159+
(use-promise p)))

src/nextjournal/clerk/sci_env.cljs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
[sci.configs.applied-science.js-interop :as sci.configs.js-interop]
2525
[sci.configs.reagent.reagent :as sci.configs.reagent]
2626
[sci.core :as sci]
27-
[sci.ctx-store]))
27+
[sci.ctx-store]
28+
[shadow.esm]))
2829

2930
(defn ->viewer-fn-with-error [form]
3031
(try (viewer/->viewer-fn form)
@@ -120,7 +121,7 @@
120121
{:async? true
121122
:load-fn load-fn
122123
:disable-arity-checks true
123-
:classes {'js goog/global
124+
:classes {'js (j/assoc! goog/global "import" shadow.esm/dynamic-import)
124125
'framer-motion framer-motion
125126
:allow :all}
126127
:aliases {'j 'applied-science.js-interop

0 commit comments

Comments
 (0)