Fork me on GitHub
#shadow-cljs
<
2023-12-02
>
mauricio.szabo17:12:29

Hi, @thheller. I was playing with running Babel in macro-expansion time (more info here: https://clojurians.slack.com/archives/C06MAR553/p1701480380276509) and while things worked, I wanted to try a different approach - so I tried to use Shadow-CLJS hooks (specifically, the {:shadow.build/stage :compile-finish} hook) to capture what Shadow compiled, run Babel on it, then emit the already transformed JS. My test case was to emit JSX with (js* "<div>Hello</div>") and send it to Babel using the Solid.JS preset. So... it worked - kinda. When I opened the browser (I was using the ESM target) I could see the div, and the files were correct and everything; the problem was when I saved the file again- it tried to send the JSX, before Babel, version to the browser, so I could not hot-reload stuff. So I've been thinking if it's possible to send to the browser, in the hot-reload workflow, the version I emitted on my hook - either by adding another hook on the line, or by some other trick. Any ideas, or even, is this desirable in any way?

thheller18:12:27

can I see the hook code? I mean the hook should run fine for hot-reload stuff

thheller18:12:31

whats the potential gain here? I mean solid is never gonna map quite right to CLJS, so you are just going to fight one footgun after the other?

mauricio.szabo18:12:51

Sure - the hook code is this one:

(defn babel-compile
  {:shadow.build/stage :compile-finish}
  [build-state]
  (update build-state :output
          (fn [output]
            (->> output
                 (map (fn [[k v]]
                        (if-let [babel (-> v :ns-info :meta :babble/config)]
                          [k (update v :js transform* babel)]
                          [k v])))
                 (into {})))))

mauricio.szabo19:12:20

Basically, I get the ns metadata to see if I should run babel on it, then I run transform* to transform the source - transform changed a lot, but in a nutshell is a node runtime (provided by Javet) that calls Babel with the shadow compile source code and returns a string with the transformed code

mauricio.szabo19:12:31

> whats the potential gain here? To explore if there's a way to run Babel in the "middle" of the process - basically, to compile ClojureScript to Javascript+JSX and run Babel to transform that to JS, and that be the final result. Maybe even give some support for this ticket: https://github.com/thheller/shadow-cljs/issues/190

mauricio.szabo19:12:30

And I'm sure I'll have other footguns, just thinking if there's a way around this - I really wish there was a better way for CLJS to integrate better in the JS world, even though I am well aware how crazy things are with bundlers and compilers and everything else.

mauricio.szabo19:12:24

BTW, the code that the hook was being applied to is:

(ns babble.core-test
  {:babble/config {:presets ["babel-preset-solid"]}})

(js* "<div>Hello, world</div>")

thheller19:12:22

here I'm trying to get away from the JS world as much as possible and you want to get right back in 😛

thheller19:12:58

hook seems like it should be fine, although you'd probably want to maintain some flag to not run babel when it already ran

thheller19:12:39

:compile-finish is compiled every cycle, so :js will contain the already converted code unless the file itself was recompiled

thheller19:12:04

but regardless, the client should be getting the transformed code

thheller19:12:51

the client ends up calling this to get the source for the recompiled files

thheller19:12:06

so it just gets the :js basically, which should be the babel'd thing?

thheller19:12:22

btw I have no interest in adding babel support in shadow-cljs itself, so don't plan on working on any PRs or so on that front

thheller19:12:02

happy to add stuff so you can do it in hooks, but no chance of direct integration. babel is an absolute nightmare to work with and maintain

thheller19:12:10

I also don't quite see the point. I mean the transformations done by the JSX translation things are probably not rocket science and could easily be done in a macro, without any babel. just pure CLJ.

thheller19:12:12

javet looks interesting though, never seen that before

mauricio.szabo19:12:51

Ok, I imagined you didn't want to add Babel support - I'm completely fine with it, and I'm glad you consider adding stuff on hooks so that I can try something 👍. I'll try to do stuff in hooks, make a branch, and if things don't work out I'll update here

mauricio.szabo19:12:46

BTW, the point is that some tools transform JSX in a different way. Solid claims that it detects "static things" and "dynamic things" in JSX and compile everything to optimize the end Javascript code. These claims seems to be backed by third-part benchmarks too, so I'm curious if I can, let's say, have my (solid) cake and eat it too...

thheller19:12:33

FWIW these benchmark numbers are with extremely basic optimizations https://github.com/thheller/js-framework-shadow-grove#light

thheller19:12:00

I'm sure with some time invested it is possible to be even more on par with solid, with just pure CLJS

thheller19:12:30

so I never saw the draw to adopt the JS-framework-of-the-day if we can do things just as well in CLJS ourselves 😛

thheller19:12:58

> Solid claims that it detects "static things" and "dynamic things" in JSX

thheller19:12:09

is absolutely trivial in CLJS and grove does it too 😉

thheller07:12:04

FWIW DOM libs are all fast enough, even react. what matters more is how you handle data and how precise updates can be, so nothing any of the raw DOM libs cover. unfortunately something most benchmarks skip entirely.

thheller07:12:30

yeah import isn't allowed to be added dynamically by hooks and really can't be made to work since at the stage it is at :compile-finish the set of compiled sources is final