Fork me on GitHub
#shadow-cljs
<
2023-06-16
>
benji10:06:28

@thheller apologies in advance if this already has been discussed … I feel I’ve got a little lost in the woods reading the docs when working with esm modules :s … I’ve been playing with this old https://gist.github.com/borkdude/7e548f06fbefeb210f3fcf14eef019e0 as wanted to try using the https://github.com/vadimdemedes/ink library too which has been totally rewritten as an esm only module now i have a similar shadow-cljs config:- ;; shadow-cljs configuration - use lein to manage dependencies and the classpath etc {:lein true :builds {:cli {:target :esm :output-dir "out" :runtime :node :modules {:index {:init-fn cli.core/main}} :js-options {:keep-as-import #{"ink"}}}}} and my cli.core namespace requires the “ink” module referring to the render fn and Text component (:require ["ink" :as ink :refer [render Text]] ... this all works perfectly when I build a release, but when running a dev/watch I get the following error:-

file:///Users/benj/Development/fireadmin_2.0/out/cljs-runtime/shadow.esm.esm_import$ink.js:3
shadow.esm.esm_import$ink = esm_import$ink;
                            ^

ReferenceError: esm_import$ink is not defined
    at file:///Users/benj/Development/fireadmin_2.0/out/cljs-runtime/shadow.esm.esm_import$ink.js:3:29
    at ModuleJob.run (node:internal/modules/esm/module_job:194:25)
is this a result of the compiler renaming the “ink” symbol and is there anything I can do about it?

thheller11:06:28

what is the context here? does it happen when you first run the output? does it happen over the REPL?

benji11:06:10

it’s as result of compiling or watching a dev build - as soon as i run my script:-

$ node ./out/index.js
if I perform a release build, the same command works fine

benji11:06:55

since I’m using leiningen to manage the dependencies i start a dev build with the following:-

lein with-profile +dev run -m shadow.cljs.devtools.cli --npm watch cli
and building for release:-
lein with-profile +live run -m shadow.cljs.devtools.cli --npm release cli

benji11:06:00

i also appreciate this is a little vague so am happy to put a little github repro together … just from what I’ve been reading and trying to get through my thick skull is whether it’s due to the :keep-as-import bit which might exclude it for development builds but happily bundle it for release builds? i must admit i struggle with the complexities of esm vs common js etc … this is the first time I’ve tried to build a simple project using a dependency that’s esm only :s - i really do appreciate any help but also understand you’re insanely busy!

thheller16:06:57

since its node you can just run :js-options {:js-provider :import} maybe that fixes it?

thheller16:06:10

repro always helps

thheller16:06:20

which shadow-cljs version do you use?

benji16:06:51

latest version i believe - i'll give the js-options a try and if no joy i'll get a repro for you :) thanks for the continued help

benji16:06:20

yep .. pretty sure im using 2.23.3

thheller16:06:29

curious why you are doing the +live +dev switch? not related to esm, just curious

benji16:06:38

oh ... i might not actually need it but the project is eventually meant to connect to one of our various firebase projects and i was attempting to use different profiles to add additional src paths for the various google service json files

benji16:06:08

(and i needed to use leiningen as that supported connecting to our private maven repo on google cloud)

benji17:06:45

ok, just tried with :js-provider :import and it’s the same deal, if I build a release then my script runs fine, if I compile/watch a dev build i get the same ReferenceError: esm_import$ink is not defined error 😞 i’ll get a repro to you, if not over the weekend then definitely Monday!

thheller06:06:18

so I had some time to look over this and identified the issue

thheller06:06:15

per spec the module loading order is guaranteed as long as there is no async import

thheller06:06:35

the shadow-cljs output assumes this is always the case

thheller06:06:41

however, ink has a top level async import, which then apparently makes everything async and totally messes the loading order of shadow-cljs

thheller06:06:11

not sure what to do about that

benji08:06:48

ah fair enough @thheller - really appreciate the update especially over the weekend! i think for the time being I could at least go back to a pre 4.0 release of ink (prior to the esm module rewrite) - it’ll mean I can’t use react 18, but that’s not an issue at all really

thheller08:06:18

I had an idea how to at least ensure this sort of works

benji08:06:04

am all ears! if there’s anything I need to change/reconfigure in my project/code

thheller10:06:30

problem appears to be bigger than expected but I got some stuff working

benji10:06:34

oh nice - that’s encouraging! if there’s anything I can try and do to help? although the various ways commonJS and esm work and conflict with each other currently confuse me!

benji10:06:52

(even if it’s to send you some beer money!) 🙂

thheller10:06:43

just need to tweak the output thats generated so node is happy. just need to find a way to work around one last issue

benji10:06:48

wow! thanks for this!

thheller11:06:01

try version 2.23.4, should hopefully take care of it

benji11:06:21

legend! that’s dev and release builds working … am I right in that I shouldn’t be loading this module dynamically, I should be able to (:require [“ink” :as ink :refer [Text render]]) .. as normal?

benji11:06:22

actually ignore that … that’s what i’m already doing and it’s working fine thank you 🙂

benji11:06:04

just spotted one little issue which might be due to hot reloads in dev builds … if I change my code and run my script again I get:-

file:///Users/benj/Development/fireadmin_2.0/out/cljs-runtime/shadow.esm.esm_import$ink.js:4
import * as esm_import$ink from "ink";
^^^^^^^^^^^^^^^^^^^^^^^^^^

SyntaxError: Identifier 'esm_import$ink' has already been declared
    at ESMLoader.moduleStrategy (node:internal/modules/esm/translators:119:18)
    at ESMLoader.moduleProvider (node:internal/modules/esm/loader:468:14)

benji11:06:39

if I quit the watch process and restart it and then run the script everything is fine

thheller11:06:14

what exactly do you do to trigger this?

thheller11:06:51

you say hot reload and then "run my script again", so what is it? a hot-reload or node that-script.js?

benji11:06:05

Sorry … the script just renders a Text component to the terminal and then exits. If I simply change the color property on the text component, the watch process recompiles and produces new output - then I run the script again and get the error. If I restart the watch, and run my script it all works fine

thheller11:06:41

the change I made is not cache aware, so it just writes it every time and duplicates the imports

benji11:06:53

should I add ^:dev/once metadata to the namespaces that might require “ink”?

thheller11:06:42

no you should not. try 2.23.5

benji11:06:14

yep! all good 🙂

benji11:06:50

can I contribute to your beer fund in anyway for all the help?

benji12:06:55

ah! nvm … found your paypal link … 🙂

benji12:06:31

pleasure … and thank you for all your hard work on shadow - it’s been a game changer for me!

benji13:06:29

@thheller sorry, it’s me again … not a big issue - dev build is still happy etc, but I can’t get a REPL connection with a :target :esm build - previously it was :node-script and this was fine - now I just get repeated Waiting for Shadow CLJS runtimes, start your CLJS app even though my node script is running? Don’t want this to be a time sink so if the :esm target doesn’t support connecting via a REPL then fair enough

thheller13:06:01

yeah, :esm currently doesn't support a node REPL

benji13:06:32

ah … that’s an easy one then 🙂 thanks!

thheller13:06:36

REPL in general is very tricky with ESM, so have to figure some stuff out for that eventually

benji13:06:38

kk no worries … wish I hadn’t started playing around with this “ink” library now … will see how far I get without my REPL crutch 😄

thheller13:06:06

you could just stick with a regular :node-script and use a dynamic import to load the ink lib

benji13:06:15

oh! this?

(ns 
  (:require
    [shadow.esm :refer (dynamic-import)]
    [shadow.cljs.modern :refer (js-await)]))

(defn foo []
  (js-await [mod (dynamic-import "ink")]
    (js/console.log "loaded module" mod)))
? erm, do I just then require it in any of my namespaces that need it as normal afterward?

thheller13:06:13

no you need to expose mod somehow to the outside and make sure the import finishes before you run anything else

thheller13:06:30

so (def ink nil) as a normal def and then (set! ink mod) or so in the await body

benji13:06:55

ah gotcha … will give that a go thanks

thheller13:06:48

for shadow.esm to work you probably need :prepend "global.shadow_esm_import = function(x) { return import(x); }" in the build config

thheller13:06:06

still need to hide that from the closure compiler so it doesn't complain

benji13:06:04

does :prepend go in the :modules section and does the :node-script target support it?

thheller13:06:31

node-script doesn't have a modules section 😉

benji13:06:46

ah … 🙂

thheller13:06:23

just in the build config should be fine, not actually sure but I think so

benji13:06:39

cool thanks 🙂

benji15:06:39

well that all works a treat! back to :node-script target with REPL, using the pesky ESM ‘ink’ module … script waits until the dynamic import has completed at which point it (set!’s …) all the stuff I need to expose, then I just require my new ink namespace instead and :refer to everything as normal … brilliant!

benji15:06:25

(I’ll have to wait until next payday to send you some more beer money!) 🍻

thheller17:06:33

dont worry about it, happy to help 🙂

thheller17:06:11

I should probably make that a properly documented recommendation. going full ESM so full of so many problems 😛

benji09:06:51

😄 … the Javascript eco system (common Js and ESM) makes my brain hurt … if it wasn’t for shadow-cljs I’d be in a proper state :s

benji12:06:53

hey @thheller - one more quick question, if i needed to import another esm module what would i change my :prepend to in my build config? i currently have

"global.shadow_esm_import = function(x) { return import(\"ink\"); }"
which works well, would I need to create my own js object and merge the results of multiple imports together e.g.
var ink = import("ink");
var other = import("other");
return merge(ink, other) // whatever the js equivalent is :)

thheller12:06:44

uhm why did you hardcode "ink" there? thats supposed to use the x argument?

benji12:06:34

didn’t realise - thought ‘x’ was a placeholder for the module I wanted to import :s

thheller12:06:47

nah we just can't call the import directly because the closure compiler complains

thheller12:06:00

so we setup an alias function, and shadow.esm/dynamic-import calls that instead

benji12:06:47

cool! thanks for setting me straight 🙂

thheller12:06:57

:target :esm sets up that alias automatically, but :esm then just has other problems which you are aware of 😛

lilactown21:06:50

is there an easy way for me to profile the compiler? for some reason one ns takes nearly 30s to compile, and I want to figure out why

hifumi12321:06:33

i don't know of a flamegraph but the shadow cljs console usually reports how long it took to write out cljs files. i assume this ia your bottleneck and not closure's optimizations misread your post, oops

thheller07:06:09

does it just take long or does it also produce excessive amount of JS code?

thheller07:06:11

otherwise profiling is just regular profiling. nothing special in regards to the compiler. if you suspect macros just record time spent in anything but cljs.* or shadow.*. that should reveal macro issues

thheller14:06:39

did you try to just eval form for form in the REPL? should be noticable if something is slow

lilactown21:06:32

hmm yeah I guess I'll try and hook in clj-async-profiler or something

lilactown21:06:13

I haven't gone form for form. the curious thing is that in another build, a change to the file takes 5s. so my hypothesis that it's a form in the file might be off.

lilactown21:06:55

it could be a difference in the build type too. one build is an :npm-module the other is a regular :browser build

thheller09:06:03

build targets really only control how the output is organized. they are not a factor in actual compilation. in fact they aren't involved in that code.

thheller09:06:47

so, more than likely it is the macros. what is done within those I cannot say. if you have something looking at the target setting that is certainly possible

lilactown21:06:11

I'm guessing it's macro BS but it would be nice to be able to easily confirm that with a flamegraph

Roman Liutikov13:06:11

I had moderate success at profiling Clojure using YourKit https://www.yourkit.com/java/profiler/features/