scittle 2023-08-21

Hi, would it be possible to configure scittle to resolve external libraries imported via <script> tags, like so?

<script src="" type="application/javascript"></script>
    <script src="" type="application/javascript"></script>
    <script type="application/x-scittle">
      (ns my-ns
        (:require [clojure.string :as str]
                  ["@codemirror/state" :refer [EditorState]]))

      ...
    </script> 

would that require a custom build of scittle, like you do with plugins? I mean to tell scittle to use the :async-load-fn option...

yeah, it would require some (breaking) changes, but I wonder if I can accomodate both ways in one build, both have their trade-offs. Perhaps it's possible to include scittle in a second way that does it the ESM + async way

amazing this already works:

(ns scittle-build
  (:require [sci.core :as sci]
            [sci.async :as scia]
            [scittle.core :as scit]
            [shadow.esm :refer [dynamic-import]]
            [scittle.impl.error :as error]))

(swap! scit/!sci-ctx
       sci/merge-opts {:async-load-fn (fn [{:keys [ctx libname]}]
                                        (.then (dynamic-import libname)
                                               (fn [mod]
                                                 (js/console.log :require libname
                                                                 :mod mod)))
                                        {:handled true})})

(set! scit/eval-string (fn [txt]
                         (try
                           (scia/eval-string* @scit/!sci-ctx txt)
                           (catch :default e
                             (error/error-handler e (:src @scit/!sci-ctx))
                             (throw e)))))

with the import mapping eg. “@codemirror/state”: “https://cdn.jsdelivr.net/npm/@codemirror/state/dist/index.js”, then it can eval

(ns foo
  (:require ["@codemirror/state"])

note that scia/eval-string* returns a promise, so you should handle the promise error probably

👍 2

now I guess I’ll go find the 1000 implementation of the async-load-fn we did already 🙂

well not 1000 probably...

1000 + 1 Promise pending

what are you working on?

extracting with Philippa the stuff behind the split-view editor from Clerk

trying to write code once but target more build envs like shadow/scittle/squint possibly 🤞

I’m a bit confused now, it seems forms in the namespace gets evaluated before the requires are resolved (via async-load-fn), this kind of makes sense in an async setting, but how can forms refer to the required names?

you should return handled true from the promise

then the code will run after the promise, not parallel

ah, ok, I was returning it from the the load-fn body

I now have two tags:

<script type="application/x-scittle" src="folio.cljs"></script>
    <script type="application/x-scittle" src="demo.cljs"></script>
first loads just fine, the second however doesn’t seem able to require the namespace defined by the first

because async

the code that loads the script tags must handle the promises in order

ok, then I also need to change eval-script-tags*

or I also copy the ns defined in the first into the sci ctx

it’s not clear at this point what goes where

it's not clear to me what you mean with "or I also copy the ns defined in the first into the sci ctx"

if you change scittle.core/eval_string to be async, you also need to account for that in other places

I need to swap a bunch of nss into scittle.core/!sci-ctx in order for the example above to work at all

btw I wouldn't merge this since that is a breaking change, but for experimentation it's ok

the order will work once you fix the eval-script-tags to work with promises

sure, I can experiment with our build so far

once you're finished, please let me know, perhaps we can find a way to make scittle1 work with scittle async so we don't have breakages and we can have both behaviors at once

I think this will mean a new var scittle.core/eval-string-async and perhaps a new attribute on the script tags to indicate that it's ES6 code

but we can figure that out later

I think we can make eval-script-tags work with both sync and async code by just using Promise.resolve on the return value and always work in a promise-like fashion

then we'll just leave eval-string synchronous for people who already used it and introduce a new var for async evaluation

Got the sequential eval working 🙂

but still cannot demo the editor because of a react mismatch error when using hooks

oh yeah, that again

perhaps you can pin the react version using import-maps

could it be react version shipped with reagent the problem

I do pin them to 18.2.0

no, react isn't shipped with reagent, with scittle you provide your own react version

in the script tag

I’m adding reagent to the sci context

and reagent requires react from cljcljs

scittle already has a reagent module

this means that react isn't packaged along with scittle, you need to provide your own (global) version

hmm, but I’m using my own build

are you using target esm?

in our code I expect requires ["react" :as react] to be picked by the import map now

ok, you can check nbb which also packages reagent without react with esm

I've made a specific version of reagent there to work around some of the issues

yeah, I was thinking of forking it with the string requires

I already did that :)

cool, under which repo?

but it’s based on reagent 1.1.1 which expects react 17 I think?

I didn't even use string requires there, I think I ran into specific issues so I just made it depend on a global being defined

could be, it's been a while. I don't follow the react trends

yeah, I’m seeing that, I’ll try with the string require I’m not super keen on this global stuff

Is there any specific reason to prefer sci.configs for reagent than to use sci.core/copy-ns? I see e.g. set-default-compiler! is missing in sci.configs ?

just try it and you will find out

the string require didn’t fix my issue, and also if I’m providing reagent through :namespaces in the sci context, it will pick the one compiled by shadow right? so string requires will be sourced from npm_modules right?

yes, but just look at nbb for how to configure so that it won't pull in react from your project

then I have to setup the goog/global thing

no, I mean the shadow config

or wait, :keep-as-import as we did before

you need both the string require + keep-as-import

yeah, trying that

I don't think this is possible since "@codemirror/state" refers to something external which is not known by scittle or the JS environment, right?

should work with cherry though if you stick the complete CDN url in there: https://dev.livecodes.io/?template=clojurescript

but usually when you use a script tag in this way, the script defines a global which you can then use

oh, would squint-js + an import map, more appropriate?

oh, your example has na import map

scittle will work, but you need to access the codemirror stuff via the globals it defines (see example)

or if it only defines modules, you even need to define globals yourself:

<script type="importmap">
      {
        "imports": {
          "codemirror": "",
          "@codemirror/commands": "",
          "@codemirror/search": "",
          "@codemirror/autocomplete": "",
          "@codemirror/lint": "",
          "crelt": "",
          "@nextjournal/lang-clojure": "",
          "@nextjournal/lezer-clojure": "",
          "@lezer/highlight": "",
          "@lezer/lr": "",
          "@lezer/common": "",
          "@codemirror/language": "",
          "@codemirror/state": "",
          "@codemirror/view": "",
          "style-mod": "",
          "w3c-keyname": ""
        }
      }
    </script>
    <script type="module" type="application/javascript">
      import * as cm from 'codemirror';
      import * as lc from '@nextjournal/lang-clojure'
      import * as cv from '@codemirror/view';
      import * as cs from '@codemirror/state';
       = cm;
       = lc;
       = cv;
      globalThis.cs = cs;
      scittle.core.eval_script_tags();
    </script>

yeah I saw that... I was thinking more of keeping the cljs code as is to suit different targets (e.g. shadow)

squint or cherry should work I think

this is a minimal example of how to compile and run: https://squint-cljs.github.io/squint/

nice thank you!

I see, the eval part is done via an import of url encoded data

I think you could theoretically use eval if you would wrap it in a self-executing async function

hmm, but then import doesn't work as is

so yeah, that's why it's done this way

I’ll check it out

I'm contemplating an output that is more suited for hot-reloading / nREPL, then I'd have to emit import() instead and some global stuff where (defn foo []) becomes the_namespace.foo = function and then you could likely also also use eval

could sci async also support require of js modules using import maps or does it already maybe?

yes, sci async can

if you insert the right import maps in that page, dynamic import using those aliases will automatically work, this is just browser behavior