Skip to content

Commit

Permalink
Add dynamic js/import for JavaScript Modules (nextjournal#304)
Browse files Browse the repository at this point in the history
* Extend js global namespace with dynamic `js/import`
* Add convenience react hook wrappers
  • Loading branch information
zampino committed Jan 3, 2023
1 parent 8ed0c35 commit 0a0f1e2
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 2 deletions.
58 changes: 58 additions & 0 deletions notebooks/js_import.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
;; # 📦 Dynamic JS Imports
(ns js-import
{:nextjournal.clerk/visibility {:code :hide :result :hide}}
(:require [nextjournal.clerk :as clerk]
[nextjournal.clerk.viewer :as clerk.viewer]
[clojure.data.csv :as csv]))

;; This example uses [Observable Plots](https://observablehq.com/plot) with data from https://allisonhorst.github.io/palmerpenguins/

(defn parse-float [^String s] (Float/parseFloat s))

^{::clerk/visibility {:code :show}}
(def observable-plot-viewer
{:transform-fn clerk/mark-presented
:render-fn
'(fn [data _]
[nextjournal.clerk.render/with-dynamic-import
{:module "https://cdn.skypack.dev/@observablehq/plot@0.5"}
(fn [Plot]
[:div {:ref (fn [el]
(when el
(let [dot-plot (.. Plot
(dot (clj->js data)
(j/obj :x "flipper_length_mm"
:y "body_mass_g"
:fill "species"))
(plot (j/obj :grid true)))]
(doto el
(.append (.legend dot-plot "color"))
(.append dot-plot)))))}])])})

^{::clerk/visibility {:code :show :result :show}
::clerk/viewer observable-plot-viewer}
(def palmer-penguins
(-> (slurp "https://nextjournal.com/data/Qmf6FJyJxBQnB6TUZ3J9pdzHSs8UoewoY6WfdZHu1XxkD8?filename=penguins.csv&content-type=text/csv")
(csv/read-csv)
clerk.viewer/use-headers
clerk.viewer/normalize-table-data
(as-> data
(let [{:keys [head rows]} data]
(map (fn [row] (zipmap head (reduce #(update %1 %2 parse-float) row [1 2 3 4])))
rows)))))

;; or use `js/import` directly:
^{::clerk/visibility {:result :show :code :show} ::clerk/no-cache true ::clerk/width :wide}
(nextjournal.clerk/with-viewer
'(fn [_]
(let [cc (nextjournal.clerk.render.hooks/use-promise
(js/import "https://cdn.skypack.dev/canvas-confetti"))
ref (nextjournal.clerk.render.hooks/use-ref)]
(when cc
[:div
[:button.bg-teal-500.hover:bg-teal-700.text-white.font-bold.py-2.px-4.rounded.rounded-full.font-sans
{:on-click #((.create cc @ref)
(j/lit {:spread 80 :angle 45 :startVelocity 20 :origin {:x 0.25 :y 0.5}}))} "Peng 🎉!"]
[:canvas
{:ref ref
:style {:width "100%" :height "500px"}}]]))) nil)
1 change: 1 addition & 0 deletions src/nextjournal/clerk/builder.clj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"eval_cljs"
"example"
"hiding_clerk_metadata"
"js_import"
"multiviewer"
"pagination"
"paren_soup"
Expand Down
6 changes: 6 additions & 0 deletions src/nextjournal/clerk/render.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,12 @@
(f package)
loading-view))

(defn with-dynamic-import [{:keys [module loading-view]
:or {loading-view default-loading-view}} f]
(if-let [package (hooks/use-dynamic-import module)]
(f package)
loading-view))

(defn render-vega-lite [value]
(let [handle-error (hooks/use-error-handler)
vega-embed (hooks/use-d3-require "vega-embed@6.11.1")
Expand Down
5 changes: 5 additions & 0 deletions src/nextjournal/clerk/render/hooks.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
(:require ["d3-require" :as d3-require]
["react" :as react]
[reagent.ratom]
[shadow.esm :as esm]
["use-sync-external-store/shim" :refer [useSyncExternalStore]]))

;; a type for wrapping react/useState to support reset! and swap!
Expand Down Expand Up @@ -152,3 +153,7 @@
list))
#js[(str package)])]
(use-promise p)))

(defn ^js use-dynamic-import [mod]
(let [p (use-memo #(esm/dynamic-import mod) [mod])]
(use-promise p)))
5 changes: 3 additions & 2 deletions src/nextjournal/clerk/sci_env.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
[sci.configs.applied-science.js-interop :as sci.configs.js-interop]
[sci.configs.reagent.reagent :as sci.configs.reagent]
[sci.core :as sci]
[sci.ctx-store]))
[sci.ctx-store]
[shadow.esm]))

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

0 comments on commit 0a0f1e2

Please sign in to comment.