Fork me on GitHub
#clojurescript
<
2023-10-03
>
pez08:10:27

I get a Use of undeclared Var cljs.core/destructure that I don’t understand how to get rid off. I’m updating a macro library, https://github.com/PEZ/taplet, that uses cljs.core/destructure. When bumping/cleaning up the library dependencies, I notice that it used to work in ClojureScript only because of som transient dependencies. I can require the namespace without getting warnings, and it works. shadow-cljs runs the tests fine, and I can run the tests in the repl. All without any warnings. But evaluating the namespace file I get that warning. As long as I can trust that the library will work for any users, I am good to deploy it, but it worries me that the user might get warnings. Here’s the new branch I’m working on, in case someone wants to take a look at what I might be doing wrong. https://github.com/PEZ/taplet/tree/next

pez08:10:29

A thing I noticed is that to avoid warnings from when requiring the namespace, I need to keep the reference to cljs.core/destructure in a macro.

(let [taps (->taps label bindings #?(:cljs cljs.core/destructure
                                       :clj destructure))]
  ...)
If I reference from a utility function (`->taps`) that the macro uses, the require also emits warnings.

pez08:10:13

I only get the warnings when evaluating the namespace using the ClojureScript REPL. With the Clojure REPL, no warnings.

p-himik09:10:29

Why is it in a .cljc file in the first place? Why not .clj given that its whole API is just macros?

pez09:10:45

It’s mostly because of Calva making it easy to evaluate things in either the Clojure or the ClojureScript REPL if the code is in a .cljc file. Means I can exercise the API from Rich comment forms in either REPL.

p-himik09:10:16

And how does it work for regular projects that define macros in CLJ files that are intended to be used from CLJS?

pez09:10:32

Not sure what the question is, can you elaborate?

p-himik09:10:41

Well, you're saying that Calva makes some things easy when everything is in a .cljc file. But people that write macros for CLJS usually use two kinds of files - .cljs where just the CLJS code resides and .clj where just the macros reside (since they're written in CLJ). In the latter case, are there any issues with Calva, are any things less easy than in the former case?

pez09:10:10

Calva uses the Clojure REPL for .clj files and the ClojureScript REPL for .cljs files, so that is convenient.

p-himik09:10:30

Alright, so what exactly drives the need to have Taplet be implemented in .cljc, given that it's only macros, that are CLJ anyway (unless you're targeting self-hosted CLJS, but that's a beast on its own)?

pez09:10:29

I meant that for someone structuring things with .clj and .cljs files, Calva doesn’t get in the way. But for me, I like the convenience of using the .cljc Calva support. It’s also convenience in other aspects. In the case of the taplet.cljc file, it makes it straightforward to select different destructure implementations for the different environments. Is this choice causing the warning, you mean? To me it seems I would get the same warning even if I split it up in two files?

pez09:10:35

(I’m not targeting self-hosted CLJS, btw.)

p-himik09:10:12

> it makes it straightforward to select different destructure implementations for the different environments. AFAICT, the implementation is the same. The only reason CLJS has its own destructure is exactly for the self-hosted ability. You can place the whole Taplet into a CLJ file and it'll work flawlessly. The only thing you should add then is a CLJS file that only does :require-macros on itself.

pez09:10:27

Maybe I forgot in the OP to mention that I want Clojure users of the macro to not have to include a dependency to ClojureScript. I think that happens transitively with the current version of the library. (But I’m not sure, this is not things I know very much about.)

p-himik09:10:57

You wouldn't have to do that.

pez09:10:55

When I tried using just destructure for both, things blew up. But maybe that was something else. I’ll try that now.

p-himik09:10:12

All you need is two files, one CLJ with all the code you have right now, written only with CLJ in mind, and one CLJS with only (ns ... (:require-macros [...])), that's it.

pez09:10:46

That’s the same as using a .cljc file and a reader conditional, right?

pez09:10:26

Since I would lose the Rich comment convenience in Calva (or have to duplicate the Rich comment in the two files), I rather keep it as .cljc.

pez09:10:44

However, > When I tried using just destructure for both, things blew up. This was not really so. Now trying it again I get reminded what happened. I get different warning from the ClojureScript REPL: > Use of undeclared Var pez.taplet/destructure Both versions of the warning worries me. But this one worries me more. 😃

p-himik10:10:41

> That’s the same as using a .cljc file and a reader conditional, right? In the end - yes. But CLJC gives you much more hassle with reader conditionals and a chance to accidentally use any vars in CLJS outside of macros. Not relevant for Taplet, but anything that can't be DCE'ed from the CLJC file will increase the size of the release JS bundle.

p-himik10:10:07

> Use of undeclared Var pez.taplet/destructure Wrap the defn in a reader conditional.

p-himik10:10:49

This whole thread is 10% of the reasons for why writing macros in CLJC is not a good idea. :)

pez10:10:44

Haha, point taken. But wrapping the defn in a reader conditional helped. To summarize the answer to my question in OP: > Evaluating destructure in the ClojureScript REPL causes the warning. And it is unnecessary to evaluate it in ClojureScript anyway. Something like that?

p-himik10:10:07

It's not evaluating that, it's defining a function that refers to destructure. The analyzer reads the source code of the defined function and tries to understand where destructure comes from. It can't find it, so it complains.

pez10:10:35

Thanks! 🙏 And why did it not happen when requiring the namespace? I think you have left enough clues for me to understand this, but I’m a bit too stupid about this to quite understand.

p-himik10:10:34

Are you sure you required that namespace?

$ echo "(ns x) (defn f [] (g))" > x.cljs
$ clj -Sdeps '{:paths ["."] :deps {org.clojure/clojurescript {:mvn/version "RELEASE"}}}' -M -m cljs.repl.node
ClojureScript 1.11.121
cljs.user=> (require 'x)
WARNING: x is a single segment namespace at line 1 /home/p-himik/Downloads/x.cljs
WARNING: Use of undeclared Var x/g at line 1 /home/p-himik/Downloads/x.cljs
nil
cljs.user=>

thheller11:10:57

uhm these reader conditionals do nothing, it'll always take the :clj branch when loading macros. as they are loaded in clojure. it would only have an effect for self-host, which you said you don't target.

thheller11:10:52

thats why I agree that this should not be a .cljc file, it just makes things unnecessarily complicated

p-himik11:10:45

The reader conditionals are only needed here because pez prefers to load that file in a CLJS REPL to use rich comments.

thheller11:10:47

but regardless, when expanding for CLJS you should really be using the cljs.core/destructure not the CLJ one

👀 1
p-himik11:10:41

Huh? So macros that are intended to be used from CLJS should use cljs.core/destructure? What if a macro targets both CLJ and CLJS? What are the relevant differences between the two versions of destructure that make this necessary?

thheller11:10:15

cljs.core/destructure generates slightly different code better targetted for CLJS. clojure.core/destructure also generates code that CLJS won't understand?

🙏 1
pez11:10:21

How unfortunate. 😃

p-himik11:10:46

Oh crap, you're right. Unfortunate indeed, especially given that there's no standard way to determine where a macro is being used.

thheller11:10:12

sure there is. just check (:ns &env), in CLJ this is never true

p-himik11:10:47

Yeah, but I've always seen it a happenstance and not something deliberate. So not "standard". :)

p-himik11:10:03

Pretty sure there's a ticket about it somewhere, an old one.

thheller11:10:08

I very much doubt this is ever going to change, too many things use it at this point

p-himik11:10:42

Of course. It's just one of those know-how things and I simply don't like it, that's all.

thheller11:10:49

(type &env) would also work I guess

thheller11:10:11

ah no nvm, that would work in self-host only 😛

pez11:10:34

The current version of taplet uses (:ns &env), so it’s going back to that, I guess.

pez11:10:17

Thanks, both of you. ❤️ 🙏 gratitude Now I’ve updated the next version and have no warnings anywhere, while still using destructure from cljs.core in the ClojureScript environment. It does get a bit ridiculous with all reader conditionals, but now at least I know more about the trade-offs, and also about your strong opinions on the matter.

👍 1
Ben Lieberman16:10:54

I've read up on the web components story in CLJS. I found https://github.com/WICG/webcomponents/issues/587 that describes an ES5-compatible workaround for web components. So I created https://gist.github.com/bhlieberman/3ad68a9c0ad939c7e70f410323a52af1 that does work, and I am curious what other pitfalls there would be to doing this, apart from the really gross boilerplate involved (and the hot reload thing)?

thheller17:10:03

if you want access to class, shadow-cljs has it

👀 1