diff --git a/notebooks/js_import.clj b/notebooks/js_import.clj new file mode 100644 index 000000000..800c68277 --- /dev/null +++ b/notebooks/js_import.clj @@ -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) diff --git a/src/nextjournal/clerk/builder.clj b/src/nextjournal/clerk/builder.clj index e9fbe5b69..b1e48a373 100644 --- a/src/nextjournal/clerk/builder.clj +++ b/src/nextjournal/clerk/builder.clj @@ -26,6 +26,7 @@ "eval_cljs" "example" "hiding_clerk_metadata" + "js_import" "multiviewer" "pagination" "paren_soup" diff --git a/src/nextjournal/clerk/render.cljs b/src/nextjournal/clerk/render.cljs index 377ec5407..b39d10516 100644 --- a/src/nextjournal/clerk/render.cljs +++ b/src/nextjournal/clerk/render.cljs @@ -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") diff --git a/src/nextjournal/clerk/render/hooks.cljs b/src/nextjournal/clerk/render/hooks.cljs index 93eaf4c4a..4c17905ef 100644 --- a/src/nextjournal/clerk/render/hooks.cljs +++ b/src/nextjournal/clerk/render/hooks.cljs @@ -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! @@ -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))) diff --git a/src/nextjournal/clerk/sci_env.cljs b/src/nextjournal/clerk/sci_env.cljs index 6892f0b90..7f68eac26 100644 --- a/src/nextjournal/clerk/sci_env.cljs +++ b/src/nextjournal/clerk/sci_env.cljs @@ -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) @@ -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