clojurescript

xificurC 2025-05-21T11:17:45.191819Z

How does cljs read namespaces wrt read-eval? E.g. https://github.com/taoensso/encore/blob/v2.122.0/src/taoensso/encore.cljx#L2709 there is read-eval usage that requires the previous defn to be already compiled and available for execution, but I'd expect that to be impossible during cljs analysis, as cljs->js compilation happens at a later stage

xificurC 2025-05-23T11:17:48.566769Z

> so the ns is created properly and can use all core things In the old encore version it's not just clojure.core but the vars from the namespace itself as well, as the initial link shows. Thanks @p-himik and @thheller for the investigative work. I thought someone will know the answer off-hand, guess this is a dark alley for everyone

p-himik 2025-05-21T11:51:36.152119Z

It's a cljx file, I don't even remember anymore how those are supposed to be loaded. :) But if you try #=(...) in a cljs file, you'll be disappointed because it's evaluated at read time, so within the CLJ context (unless you use self-hosted - no clue how it would work there). And yeah, it wouldn't work. You can't even do (+ 1 2) there, it has to be (clojure.core/+ 1 2).

p-himik 2025-05-21T12:00:18.520569Z

Just tried out of curiosity. Couldn't require the namespace due to some other opaque error: Not supported: class clojure.core$print. But if I remove everything unrelated, an error that I would expect pops up: Unable to resolve symbol: ms in this context.

p-himik 2025-05-21T12:02:34.293689Z

Maybe it used to be different for some old version of CLJS, no idea.

xificurC 2025-05-21T13:12:18.475209Z

I'm confused, encore promotes itself as a clojure and clojurescript library, are you claiming the linked version (1.122.0) shouldn't load in clojurescript?

xificurC 2025-05-21T13:26:27.424199Z

I tried loading that version and it compiled, so I'm not sure what is it that you tried

p-himik 2025-05-21T13:29:26.651379Z

I was loading it from sources. I'll try to see what's going on once I get home.

p-himik 2025-05-21T15:57:22.486979Z

Wow, OK. I have absolutely no clue how that works. Trying with shadow-cljs just because it's easier for me to use. #=(...) in my own sources, regardless whether it's in cljs or cljc, leads to a syntax error. #=(...) in taoensso/encore.cljc is compiled just fine, no issues whatsoever. Hey @thheller, maybe you know?

thheller 2025-05-21T16:12:53.109489Z

I wouldn't expect this to work for CLJS compilation no

thheller 2025-05-21T16:22:15.846129Z

I suspect that its just read as a normal tagged literal, so with the = tag. and since its in comment it never makes it to actual compilation

thheller 2025-05-21T16:23:02.971669Z

definitely no read-eval happening for CLJS

p-himik 2025-05-21T16:24:53.774569Z

Yeah, but in the most recent Encore build, it's not under comment.

p-himik 2025-05-21T16:25:12.159129Z

And even if I write my own (comment #=(...)) - it doesn't work at all, still a syntax error.

p-himik 2025-05-21T16:26:08.910449Z

This is how it looks in Encore:

(defn ms
  "Returns ~number of milliseconds in period defined by given args."
  {:arglists '([opts] [& {:as opts :keys [years months weeks days hours mins secs msecs ms]}])}
  (^long [{:keys [years months weeks days hours mins secs msecs ms]}]
   (round0
     (+
       (if years  (* (double years)  #=(* 1000 60 60 24 365))    0.0)
       (if months (* (double months) #=(* 1000 60 60 24 29.53))  0.0)
       (if weeks  (* (double weeks)  #=(* 1000 60 60 24 7))      0.0)
       (if days   (* (double days)   #=(* 1000 60 60 24))        0.0)
       (if hours  (* (double hours)  #=(* 1000 60 60))           0.0)
       (if mins   (* (double mins)   #=(* 1000 60))              0.0)
       (if secs   (* (double secs)   1000)                       0.0)
       (if msecs     (double msecs)                              0.0)
       (if ms        (double ms)                                 0.0))))
It compiles, the ms function works in CLJS. When I copy this function to my own ns and use round0 from Encore, it fails with a syntax error.

thheller 2025-05-21T16:30:27.886329Z

hmm that surprises me. (js/console.log #=(* 1000 60 60 24 365)) just works

thheller 2025-05-21T16:30:42.179219Z

guess tools.reader does indeed handle it

p-himik 2025-05-21T16:31:22.542949Z

Does not work for me:

ClojureScript 1.12.42
cljs.user=> (js/console.log  #=(* 1000 60 60 24 365))
Syntax error reading source at (REPL:1).
Unable to resolve symbol: * in this context
Syntax error reading source at (REPL:1).
<NO_SOURCE_FILE> [line 1, col 2] Unmatched delimiter ).

thheller 2025-05-21T16:32:09.038639Z

doesn't work in the REPL no, but source file it does

thheller 2025-05-21T16:33:21.688559Z

bit weird since that is supposed to be using the same reader

thheller 2025-05-21T16:36:07.092479Z

didn't know that defaults to true and at least shadow-cljs never sets this anywhere at all

p-himik 2025-05-21T16:38:01.874559Z

It doesn't work for me in a source file as well:

------ ERROR -------------------------------------------------------------------
 File: /home/p-himik/tmp/cljs-playground/src/proj/read_eval.cljc:4:41
--------------------------------------------------------------------------------
   1 | (ns proj.read-eval
   2 |   (:require [taoensso.encore :as encore]))
   3 | 
   4 | (js/console.log  #=(* 1000 60 60 24 365))
-----------------------------------------------^--------------------------------
Syntax error compiling at (4:20).

thheller 2025-05-21T16:40:20.056499Z

🤷

p-himik 2025-05-21T16:41:52.769469Z

I think I'll continue not using #=(...).

thheller 2025-05-21T16:42:37.624289Z

if you setup a repro I can take a look, but works fine in my source file

thheller 2025-05-21T16:43:04.639439Z

but yeah I never use it either

p-himik 2025-05-21T16:50:57.446179Z

Sure: https://github.com/p-himik/read-eval-in-cljs

thheller 2025-05-21T16:55:19.719129Z

that is indeed weird. doesn't work for me either

p-himik 2025-05-21T16:56:16.739569Z

So at least there's no evil spirit. But what's different with your setup where it does work?

thheller 2025-05-21T17:05:40.279359Z

I'm trying to figure that out 😛

thheller 2025-05-21T17:21:10.539599Z

absolutely no clue. works in shadow-cljs itself just fine, but not other projects

thheller 2025-05-21T17:21:30.127779Z

maybe lein/nrepl related

p-himik 2025-05-21T17:29:24.016799Z

Huh. But that [not] working in different contexts notwithstanding - how could there possibly be a difference between compiling the #=(...) inside my own proj.core and inside taoensso.encore required by proj.core? The former doesn't work for me, the latter does. As if .cljc files from jars go through some different reader.

thheller 2025-05-21T17:29:29.915249Z

ah found it

thheller 2025-05-21T17:30:45.639079Z

ok, so the actual error is

Execution error at clojure.tools.reader/read-eval (reader.clj:596).
Unable to resolve symbol: * in this context
shadow.user=> *e
#error {
 :cause "Unable to resolve symbol: * in this context"
 :via
 [{:type clojure.lang.ExceptionInfo
   :message "failed to compile resource: [:shadow.build.classpath/resource \"proj/core.cljs\"]"
   :data {:source-excerpt {:start-idx 0, :before ["(ns proj.core)" ""], :line "(js/console.log #=(* 1000 60 60 24 365))", :after ["" "(defn -main [])" ""]}, :file #object[java.io.File 0x714a513d "/Users/thheller/code/tmp/read-eval-in-cljs/src/proj/core.cljs"], :resource-id [:shadow.build.classpath/resource "proj/core.cljs"], :ex-type :reader-exception, :column 40, :line 3, :url #object[java.net.URL 0x226c0457 "file:/Users/thheller/code/tmp/read-eval-in-cljs/src/proj/core.cljs"], :source-id [:shadow.build.classpath/resource "proj/core.cljs"], :tag :shadow.build.compiler/compile-cljs, :ex-data {:type :reader-exception, :line 3, :column 40, :file "proj/core.cljs"}}
   :at [shadow.build.compiler$maybe_compile_cljs$fn__15639 invoke "compiler.clj" 1176]}
  {:type clojure.lang.ExceptionInfo
   :message "Syntax error compiling at (3:19)."
   :data {:type :reader-exception, :line 3, :column 40, :file "proj/core.cljs"}
   :at [clojure.tools.reader$read_STAR_ invokeStatic "reader.clj" 955]}
  {:type clojure.lang.Compiler$CompilerException
   :message "Syntax error compiling at (3:19)."
   :data #:clojure.error{:phase :compile-syntax-check, :line 3, :column 19, :source "NO_SOURCE_PATH"}
   :at [clojure.lang.Compiler analyze "Compiler.java" 7340]}
  {:type java.lang.RuntimeException
   :message "Unable to resolve symbol: * in this context"
   :at [clojure.lang.Util runtimeException "Util.java" 221]}]
 :trace
 [[clojure.lang.Util runtimeException "Util.java" 221]
  [clojure.lang.Compiler resolveIn "Compiler.java" 7944]
  [clojure.lang.Compiler resolve "Compiler.java" 7883]
  [clojure.lang.Compiler analyzeSymbol "Compiler.java" 7844]
  [clojure.lang.Compiler analyze "Compiler.java" 7300]

thheller 2025-05-21T17:31:13.367249Z

so it works if the namespace in question is also loaded in its CLJ variant, so for a .cljc file if you add (:require-macros [proj.core]) it works just fine

thheller 2025-05-21T17:31:58.688659Z

but if it only exists on the CLJS side then the read-eval fails because it doesn't know any of the symbols

thheller 2025-05-21T17:32:10.197769Z

kinda makes sense I guess

p-himik 2025-05-21T17:32:29.196569Z

Ewww. Where's my 20 foot stick. Thanks for figuring it out!

thheller 2025-05-21T17:33:17.539079Z

once again proof that shortening error messages and stack traces is a mistake 😛 figured it out instantly with the actual non-pretty-printed error 😛

thheller 2025-05-21T17:34:44.852509Z

kinda undefined behavior I guess. no reason to break encore and forcing *read-eval* to false though

p-himik 2025-05-21T17:35:29.005259Z

Because of the "Unable to resolve symbol: * in this context" part? I saw it when I tried figuring it out myself, but it didn't make it obvious to me that :require-macros is to blame. I guess the main issue for me was that "unable to resolve" became "syntax error" - definitely not an intuitive conversion for me. But what "syntax" is by itself is debatable, of course.

thheller 2025-05-21T17:36:42.867109Z

read-eval evals in CLJ of course. during compilation *ns* is bound to whatever the current ns is, so say proj.core. cljs during compilation calls (create-ns that-sym) to ensure the namespace exists

thheller 2025-05-21T17:37:11.806149Z

but create-ns creates only an empty ns. without any actual refers/requires like the ns macro would

thheller 2025-05-21T17:37:32.675309Z

you can try this in the REPL with simply (in-ns 'i.dont.exist)

thheller 2025-05-21T17:37:57.250899Z

it'll do the ns switch just fine, but none of the clojure.core things are available in it, so no other eval works, e.g. no (* 1 2)

p-himik 2025-05-21T17:38:50.606899Z

Ah yeah, once you mentioned :require-macros on self I immediately got it.

thheller 2025-05-21T17:40:23.846629Z

yeah once this :require-macros is added the ns is actually required on the CLJ side before the CLJS compilation starts. that require ensures the ns macro does its thing. so the ns is created properly and can use all core things.

thheller 2025-05-21T17:41:22.459799Z

yeah bit of a head scratcher

Noel Rivas 2025-05-21T17:20:05.903469Z

I'm writing https://github.com/noelrivasc/remolino-clj, a library / article on decoupling Tailwind styles from Hiccup structure. There's more README than there's code. The gist:Follow BEM conventions to name classes in hiccup.Make a map that links BEM classes to vectors of classes.Use a macro to pre-process components, applying the classes. Me being newish to clj[s], there's a good chance I'm reinventing the wheel or doing something strange. I appreciate any feedback / constructive criticism gratitude Edit: This turned out, in fact, to be a case of reinventing the wheel. Feel free to have a look, but I don't suggest you do 🙂

cjohansen 2025-05-21T17:45:35.064419Z

My first thought is that tailwind already supports this out of the box no? You can define new classes that expand to known tailwind classes.

Noel Rivas 2025-05-21T18:53:16.111059Z

🤔 I'm no Tailwind expert, but https://tailwindcss.com/docs/styling-with-utility-classes#using-components I didn't find anything to do that kind of expansion. If that's possible, then maybe it's the way to go! Do you have a link to that? Also, something similar could be done with Sass @extend but it can lead to massive lists of class names.

cjohansen 2025-05-21T18:55:55.899569Z

https://v3.tailwindcss.com/docs/reusing-styles

👀 2
Noel Rivas 2025-05-21T19:12:05.523779Z

Interesting. The docs for 3.x https://v3.tailwindcss.com/docs/reusing-styles#extracting-classes-with-apply using @apply to compose utility classes. The docs for 4.x omit that and https://tailwindcss.com/docs/styling-with-utility-classes#using-custom-css. Digging a little, I found that using apply https://github.com/tailwindlabs/tailwindcss/discussions/16349#discussioncomment-12097590 in 4.x. So, that's right! Tailwind's own tools are enough to decouple structure from styles. I'll change the README to suggest that as the first choice; not sure if there's any point for the clj macro now. Thanks @christian767 for pointing that out.

👍 1