Fork me on GitHub
#clojurescript
<
2024-04-17
>
exp7l06:04:05

Hi, i have a npm package with a top level await and i have a shadow cljs project setup i am getting {:line 2, :column 0, :message "await must be inside asynchronous function"}, the top level await is on line 2 [here](https://www.npmjs.com/package/state-machine-cat?activeTab=code) wonder if you know how to import such a package to cljs

thheller06:04:12

this is currently not supported. unfortunately it basically requires rewriting the entire npm support handling and loading in shadow-cljs. so anything but trivial

exp7l06:04:00

ah ok : (, thanks for quick response here

exp7l06:04:06

would you recommend a different approach to work with npm packages or maybe i should just fork the package and remove the top level await?

thheller06:04:06

unfortunately there is no easy answer. https://shadow-cljs.github.io/docs/UsersGuide.html#js-provider-external works to some extend but introduces a nasty race condition that is not easily worked arround

thheller06:04:42

basically the problem is that loading is assumed to be sync currently. everything is supplied and ready to go. but the top level await turns it into async code

thheller06:04:55

since you cannot mix sync and async it all has to go async. which is not how things are implemented at all šŸ˜›

thheller06:04:36

with :external only the JS part turns async, which unfortunately still has the same end effect

thheller06:04:02

you can try random things like delaying the loading of the JS and stuff, but its all very hacky and ugly

thheller06:04:25

especially if the await it does actually takes some time or so, then everything pretty much goes to shit

exp7l06:04:31

got it, doesn't seem there's a straightforward way out

thheller07:04:31

yeah I have been thinking about this for quite some time. I do have some sort of solution, but basically that means deprecating :target :browser and moving everyhing to :target :esm

thheller07:04:00

(which to be honest is about time anyway, so will probably do it sometime soon)

exp7l07:04:05

ok thanks a lot for these responses: )

thheller07:04:56

but I also cannot make sense of what this await is even for

1
exp7l19:04:09

Wouldn't first compiling away the await (babel) then consume the js from cljs work?

exp7l23:04:42

"yeah I have been thinking about this for quite some time. I do have some sort of solution, but basically that means deprecating :target :browser and moving everyhing to :target :esm" @U05224H0W Wonder if and when roughly you would work on it. One reason I'm asking is I'd love to try building a product full time on the CLJS+node+browser stack and NPM packages are important to me. For now, I just need the top level async await support for state-machine-cat library. I enjoyed so much using Clojure at prev job, and investing in JS+Node stack is less appealing to me. Depending on how hard it is, I am willing to offer help to get the PR across the finish line. I'm just pretty new to Node and CLJS. Did do some CLJ before though. In any case, thanks so much for shawdow cljs!

thheller06:04:57

It is a fairly significant task, for which I currently do not have enough time to even start. Also not something someone unfamiliar with the shadow-cljs internals could just start doing, unless you want to do a deep dive there šŸ˜›

thheller06:04:17

So can't give you a timeline. At least a couple months out.

thheller06:04:35

Also so far the only package I have heard of doing this is yours. It is not a common thing packages do, so doesn't seem super high priority to me.

thheller06:04:59

you could try doing :target :esm build with :js-provider :import and post processing the shadow-cljs output via webpack or so

šŸ‘ 1
thheller06:04:02

that should work

thheller06:04:21

bit annoying and limiting running two different tools, but if you must use that package you are going to make sacrifices either way šŸ˜›

šŸ‘€ 1
f2wHTttf21:04:15

Is there a nice way to generate .css and .js files in the same directory as a Clojure file?

f2wHTttf21:04:46

I'm trying to write a UI library that uses Clojure/Script to generate CSS (with https://github.com/noprompt/garden) and JS for use in React, Vue, Svelte, Solid, etc. components. The project structure would ideally look something like this:

f2wHTttf21:04:11

The hope is that using a single programming language to define both styles and scripts will: ā€¢ Let us re-use values and functions for both static (i.e. static .css files) and dynamic (i.e. runtime CSS-in-JS) styling. ā—¦ No need to implement, for example, a type scale function in both CSS (or some limited CSS pre-processing language like SCSS/SASS) and JavaScript. ā€¢ Simplify pruning unused values. For example, suppose we have a type scale function in ui_library/src/lib/type_scale/script.cljc that returns a font size given an integer (0, 1, ..., 6 corresponds to

,

, ...,

). We'd be able to use this function in the h1 component's style.cljc to generate a static .css file or the button component's script.cljc whose generated script.js is imported into its component.vue to do runtime CSS-in-JS styling. A Vue/Svelte component (just for readability. React, Solid, etc. would look similar) using both generated CSS and JS would look like this:

f2wHTttf21:04:40

I tried using shadow-cljs with its default build targets like :browser, :node-library, and :npm-module and the output directory set to src/lib but it seems to just output a bunch of JS files directly in src/lib for both my own Clojure files and any libraries I'm using (e.g. garden). The shadow-cljs.edn looks like this in all cases but with the target options swapped accordingly:

f2wHTttf21:04:46

shadow-cljs isn't a hard requirement. It just seemed like the easiest way to integrate Clojure/Script with an npm package. If this is easier to do with tools.build or Leiningen, that works as well.

thheller06:04:02

no clue about the css garden parts, but for shadow-cljs yes everything only goes into the output-dir, there is no supported option to move them somewhere else

thheller06:04:00

but since its in a predictable directory why not just import * from "./../../script.js"; or whatever instead of import * from "./script.js";?

thheller06:04:24

:target :esm might be the best fit, but that will currently be a bit manual since it doesn't have a per-namespace file, rather only the ones configured

thheller06:04:37

and most importantly never mix output files with source files IMHO that gets way too messy in the long run šŸ˜›

f2wHTttf06:04:03

I'd like to keep them separate too. Unfortunately, npm doesn't seem to make it easy to have generated source closures that you can just reference with a namespace path regardless of where the actual files are (i.e. the JVM land way with meta-programming).

f2wHTttf06:04:08

At least for the scripts, I can make use of Vite's alias feature to map something like $generated to the shadow-cljs output directory. Guess the last question is how to evaluate the Clojure code to emit CSS files somewhere as a build step.

thheller06:04:38

do yourself a favor and don't try to do it as part of the cljs compilation

thheller06:04:43

just write a clojure function to do that

thheller06:04:40

fwiw npm doesn't care about .cljs files either. so no point in keeping the src folder at all, only need the output files

f2wHTttf07:04:05

That will probably have to stick around just for the framework-specific stuff. I can probably organize the Clojure source under its own folder in src though (e.g. use the normal src/main).

thheller07:04:10

cljs doesn't care where it lives, so just move it into cljs/src or something. just way less of a headache to put it somewhere svelte/npm can't see

šŸ‘ 1
thheller07:04:50

(of course need to adjust the source paths accordingly)

f2wHTttf07:04:15

Probably a layout like this:

f2wHTttf07:04:37

With Vue/Svelte components like this:

f2wHTttf07:04:19

And a shadow-cljs configuration like this (need to see if the ESM target is nicer):

thheller07:04:43

esm will definitely be a lot more verbose šŸ˜›

f2wHTttf00:04:44

Is there something I'm missing that's causing :target :esm with module splitting to not produce any build outputs? Tried following the https://shadow-cljs.github.io/docs/UsersGuide.html#_module_splitting closely but it seems like I messed something up. Not using module splitting works fine. Minimal reproduction repo: https://github.com/Aokp82ES/test-cljs-emmy-garden/tree/c0ae9229685c9c81b2b0451d2c7fb942b11647a4

thheller06:04:45

[:main] Compiling ...
Module Entry "util.scale" was moved out of module ":util".
It was moved to ":base" and used by #{:css :util}.

thheller06:04:26

currently the way this works is that :exports turns into :entries. and :entries tell the compiler "this namespace must be in this module"

thheller06:04:59

but since the :css module also wants to use that namespace it breaks since a namespace can only be in one module

thheller06:04:45

so this will not work. it also sort of looks like this split is only done because of this JS cancer of "one function per file"

thheller06:04:28

so IMHO this setup makes no sense and should only be one module with the exports

thheller06:04:06

modules are intented to split out functionality that other modules do not need. if they need it they need to :depends-on it

šŸ‘ 1
f2wHTttf16:04:48

This is just a minimal setup right now so it unintentionally has 1 function per file, but I'll be adding more later.

f2wHTttf16:04:57

I probably should have checked the exit code since I misinterpreted that message as an info log about an optimization instead of an error.

f2wHTttf16:04:46

Alright adding the missing :depends-on worked.