clerk 2025-03-12

Hi all, after serving a notebook, refreshes don't work (404). How can I get some sort of permalink to the notebook? rather than having to go to index and server manually from the editor

The new support for (js/import) in notebooks and viewers greatly simplifies the development of viewers that depend on external libs. I've been working on some new Leaflet-based viewers for geographic data, but am having some trouble importing an extension to Leaflet. Here's the https://gist.github.com/respatialized/e2e791e6dc5f1addb98c5b121e955ab7 with the examples: the first works, the second encounters a SCI/JS error. The tl;dr: I can load base leaflet just fine, but I encounter an undefined instance method error when trying to use the leaflet-vega library which https://github.com/nyurik/leaflet-vega/blob/main/src/VegaLayer.js the base Leaflet L class with new layer methods. Does this have anything to do with how either SCI or myself loads these libraries?

đź‘€ 1

can you get this working with plain js (without clerk)? Not sure how https://raw.githubusercontent.com/nyurik/leaflet-vega/refs/heads/main/src/VegaLayer.js can work using imports? Would this need an import map wich we don’t support yet in clerk?

esm.sh is pretty good with this: https://esm.sh/leaflet@latest

but the second script is being loaded from a gist?

import Vsi from 'vega-spec-injector';
// eslint-disable-next-line import/extensions,import/no-unresolved
import L from 'leaflet';
import { version } from '../package.json';

what is "the second script"?

oh he already used esm.sh, I see. yeah don't know about this gist :)

first does (js/import "") and then (js/import "")

the second one doesn’t look it it would run without processing, right?

and wouldn’t they also run in isolation from each other by default?

getting cors errors when I try to import the second script on the squint playground

@afoltzm try (js/import "") I think this brings in vega as well

đź‘€ 1

an earlier version of this script attempted to import leaflet-vega from esm.sh but I forgot to change it back, sorry about that

if you can make an updated version of this to avoid all confusion, I can take a look at the "instance" problem tomorrow.

revised viewer:

(defn vega-lite
  [vl-spec]
  (when-let [leaflet-vega
             (hooks/use-promise
              (js/import
               "https://esm.sh/leaflet-vega@0.9.0/es2022/leaflet-vega.mjs"))
             vl-spec vl-spec]
    (let
      [map-div-id (str (gensym))
       m (atom nil)
       attribution
       "© OpenStreetMap"]
      [:div
       {:id    map-div-id
        :ref   (fn [el]
                 (when el
                   (let [vega-layer (.vega js/L (clj->js vl-spec) (clj->js {}))
                         tile-layer
                         (.tileLayer
                          js/L
                          "https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png"
                          (clj->js {:attribution attribution}))]
                     (reset! m (.map js/L
                                     map-div-id
                                     {:renderer (.canvas js/L)}))
                     (.setView @m (clj->js [40.690214 -73.986964]) 13)
                     (.addTo tile-layer @m)
                     #_(.addTo vega-layer @m))))
        :style {:height "500px"}}])))
it works to display a basemap when the call to create the vega layer is commented out, but uncommenting the line creating the layer results in a vega is undefined error. I will try to reproduce outside of Clerk to make sure the issue isn’t with the underlying JS library.

> revised viewer: can you please make one consistent repository / update your gist, it's easy to make mistakes when copy/pasting snippets back and forth > I will try to reproduce outside of Clerk ok, I'll wait for this then

https://gist.github.com/respatialized/e2e791e6dc5f1addb98c5b121e955ab7 Following up on this after a while - I updated the gist with a functional example. I also confirmed that the leaflet-vega https://github.com/nyurik/leaflet-vega/blob/main/demo/demo.html works locally (plain JS, outside of Clerk/SCI). I attempted to try porting it to use import instead of <script>, but I couldn't figure out how to do it because I don't understand how JS imports work well enough.

so the leaflet-vega value is the problem or also the L value? if you js/console.log those values you might be able to find out more. sometimes modules have a default export which you need to go through, so something like:

L.default
will get you the thing you're looking for, perhaps

I did include a js/console.log statement that lists the instance methods for both L and js/L in the leaflet-vega cljs viewer; the console output seems to indicate that the vega method isn't being added to L by leaflet-vega.

oh, that's probably expected, modules are immutable

shouldn't you be using it like this?

L.vegaLayer(vegaGrapSpec).addTo(map);

oh yes, I see

this library mutates L which is the default export from the Leaflet module:

import L from 'leaflet';

✔️ 1

the README for leaflet-vega is unfortunately https://github.com/nyurik/leaflet-vega/blob/612683768947e752a40afdb717543dffa96d28e5/src/VegaLayer.js#L6 - I may try to manually construct a`VegaLayer` object rather than using the L instance method

with dynamic import this would be (hooks/use-promise [L (-> (js/import "...") (.then (fn [module] (.-default module))))) (paren nesting may be incorrect)

and that value should be mutated after loading the vega lib. note that loading is async so there may be a point in time that you see the L object without the vega method (so you should probably be defensive about this)

also when-let doesn't support multiple bindings in clojure

SCI is a bit too sloppy here:

user=> (when-let [a 1 b 2] [a b])
clojure.lang.ExceptionInfo: Could not resolve symbol: b [at <repl>:2:1]

nesting the when-let s with 1 binding should work

another issue with the esm.sh approach is that the leaflet import may just load another version of Leaflet so it wouldn't mutate the Leaflet module you loaded

you could find out in the browser console if it's doing the right thing (to see if the leaflet module is only loaded once)

check here with "Specifying dependencies" https://esm.sh/#docs

it might just be better to avoid relying on the mutation that leaflet-vega does indeed

I don't think that's possible, unfortunately; even the layer class is defined within the scope of L for leaflet-vega https://github.com/nyurik/leaflet-vega/blob/612683768947e752a40afdb717543dffa96d28e5/src/VegaLayer.js#L10

https://leafletjs.com/examples/extending/extending-1-classes.html I don't know enough about JS modules to know whether this is considered a good practice according to modern standards, but Leaflet pretty much expects you to extend it in this way, which may make dynamic loading difficult for the reasons you describe

should I do an end-run around the dynamic import problem by including leaflet and leaflet-vega in the head of the Clerk doc in the call to alter-var-root! for #'nextjournal.clerk.view/include-css+js I do to add the Leaflet-specific CSS?

ok sure, I don't know enough about Leaflet, but there are at least two problems: • when-let should not contain multiple bindings • esm.sh loads dependencies and adds query parameters to them, which make them effectively different modules every time, they aren't global objects so you should look at how to properly combine dependencies in the link I linked earlier

✔️ 1

hacking in the script tags may also work of course

another hack may be this: do a js/fetch to the umd build (or whatever they call it in JS when they use globals) and call eval on it

for both libs

I updated the https://gist.github.com/respatialized/e2e791e6dc5f1addb98c5b121e955ab7 with your suggested revisions; using the ?deps syntax gets the instance method working, but I now run into a different error: i is undefined.

perhaps you can try to simplify the code first, to see if the bug is not in your own code

good news to hear that the instance method works

this just uses the globals stuff

you can see that L.vega is defined in the console

In the squint example, I can see it in the autocomplete suggestions but I still get an error about it being undefined when I attempt to call the methods:

probably the esm.sh + deps is most promising

( @afoltzm We are trying to solve a similar problem in "Kindly-render". Crude attempts: https://github.com/scicloj/kindly-render/blob/91c501f48febb9590d679e854ef4419d910da469/src/scicloj/kindly_render/note/to_hiccup_inline_js.cljc#L40 More refined attempt: https://github.com/scicloj/kindly-render/pull/46 We would be glad for any "solutions" if workable / adaptable outside of Clerk)