clojurescript

Schmoho 2025-03-20T11:03:21.986549Z

I am losing my mind over this. Accessing an attribute via interop fails when I build the project, but not during development. How is that even possible?

(prn atom)
;; #js {:resn "MET", :x -122.012, :y 27.877, :z 65.953, :elem "C", :hetflag false, :altLoc " ", :chain "A", :resi 1, :icode " ", :rescode "1", :serial 2, :atom "CA", :bonds #js [0 2 3], :ss "c", :bondOrder #js [1 1 1], :properties #js {}, :b 50.91, :pdbline "ATOM      2  CA  MET A   1    -122.012  27.877  65.953  1.00 50.91           C  ", :index 1, :reschain 0, :style #js {:cartoon #js {:colorfunc #object[Function]}}, :color 13158600, :model 0, :capDrawn false}

(prn (js->clj atom))
;; {"bonds" [0 2 3], "properties" {}, "z" 65.953, "pdbline" "ATOM      2  CA  MET A   1    -122.012  27.877  65.953  1.00 50.91           C  ", "model" 0, "index" 1, "serial" 2, "ss" "c", "x" -122.012, "rescode" "1", "bondOrder" [1 1 1], "reschain" 0, "resi" 1, "style" {"cartoon" {"colorfunc" #object[Function]}}, "atom" "CA", "b" 50.91, "y" 27.877, "icode" " ", "resn" "MET", "hetflag" false, "altLoc" " ", "elem" "C", "color" 13158600, "capDrawn" false, "chain" "A"}

(prn "atom resi" (.-resi atom))
;; nil

p-himik 2025-03-20T11:05:12.273459Z

It's probably a release build, and as such it probably uses advanced optimizations that rename some attributes. Try (.-resi ^js atom).

p-himik 2025-03-20T11:06:18.006739Z

If you use shadow-cljs, you should see a warning in the build output saying something like "the type of target in (.-resi atom) could not be determined". But there are rare gaps where the warning doesn't appear. And of course you don't have to slap ^js everywhere. Just in front of the introduction of the symbol is enough.

Schmoho 2025-03-20T11:09:45.396489Z

Now that I think about it it seems almost obvious the problem had to be something like that. Thanks!

p-himik 2025-03-20T11:13:01.057299Z

Alternatively, if atom (BTW shadowing built-in names should be avoided) is considered "data" and not "code", then you might want to use things like unchecked-get or goog.object/get.

Schmoho 2025-03-20T11:18:14.103419Z

I am having another issue where something works in dev but not prod where weirdly all the method calls work, accept the .removeAllShapes one. It does exist alright (https://3dmol.org/doc/GLViewer.html#removeAllShapes) Probably this is also made worse because I am using an external bundler? But in any case, no annotation did the trick here: (I am aware this might be better done with a ref and all

:reagent-render
(fn [& {:keys [objects style]}]
        (when-let [^GLViewer viewer @viewer-state]
          (let [{:keys [spheres boxes]} objects]
            (.removeAllShapes viewer) ;; <-- this is broken
            (doseq [sphere (filter some? spheres)]
              (.addSphere viewer (clj->js sphere)))
            (doseq [box (filter some? boxes)]
              (.addBox viewer (clj->js box)))
            (doto viewer
              (.setStyle (clj->js {}) (clj->js style))
              (.render))))
        [:div {:class "mol-container"
               :style {:width    "450px"
                       :height   "450px"
                       :position "relative"
                       :border   "solid grey 1px"}
               :ref   ref}
         "Loading viewer..."])

p-himik 2025-03-20T11:21:25.172389Z

Try adding (js-debugger) before the form that doesn't work and see what the code actually is when execution gets to that point in a build that doesn't work. Alternatively, use shadow-cljs release app --debug (`app` is your build ID) - it will pretty-print the code and use pseudo-names instead of full renames, so you will be able to trace what's going on. That's just to confirm whether .removeAllShapes gets renamed or not.

Schmoho 2025-03-20T11:26:21.339009Z

Thanks! I will look into that in a bit

Schmoho 2025-03-20T13:22:46.557159Z

When debugging in the --debug build it throws errors on all methods. When not using --debug it only throws on the .removeAllShapes This is very spooky really

p-himik 2025-03-20T13:23:40.661069Z

No clue. Can you create an MRE?

thheller 2025-03-20T13:23:44.710699Z

[^GLViewer viewer @viewer-state] this is wrong and the cause of all your troubles ๐Ÿ˜› the only tag you should ever consider using is the ^js tag. everything not using ^js or ^js/GLViewer will be treated as "safe to rename"

Schmoho 2025-03-20T13:24:32.801559Z

I wish it was. It makes no difference if its there or not - I did also try with ^js too

thheller 2025-03-20T13:25:53.262979Z

well, I can guarantee that it will definitely not work with ^GLViewer. ^js we could debug further ๐Ÿ˜‰

๐Ÿ‘ 1
thheller 2025-03-20T13:28:56.948649Z

this is also incorrect use of reagent and non-react DOM things. modern react should be using useEffect for that interop thing. dunno about that old reagent style

p-himik 2025-03-20T13:29:06.761789Z

> everything not using ^js or ^js/GLViewer will be treated as "safe to rename" Huh. Even when GLViewer is something that has been imported?

Schmoho 2025-03-20T13:29:28.732889Z

fml

Schmoho 2025-03-20T13:29:35.420709Z

I was so sure I had tried it with ^js too

thheller 2025-03-20T13:30:34.177619Z

@p-himik there is no such thing as "imported" as far as type hints are concerned, thats why the only type hint that makes any sense is ^js or if you are really obsessed ^js/GLViewer. the js part is the only relevant bit though.

thheller 2025-03-20T13:32:04.330139Z

basically if you ever get a inference warning use ^js, don't complicate it further. not worth worrying about finer details unless you want to tweak the final build size by 0.05% or so ๐Ÿ˜›

Schmoho 2025-03-20T13:34:25.307159Z

Okay that helped with my immediate problem. About the incorrect usage part - I am a bit out of my waters doing this whole frontend stuff rn, as in it feels like I am trying to cross the pacific in a kayak. Since the component for now "does what its supposed to" I guess I'll revisit that when I'm feature-complete

thheller 2025-03-20T13:34:37.253019Z

@d.eltzner I made an example using https://github.com/thheller/reagent-pdfjs/blob/master/src/main/demo/app.cljs a long while ago. basically you can adapt this to whatever this GLViewer does, moving the render bits into the useEffect. basically create a ref and use that to get the DOM node you want to embed something else in.

thheller 2025-03-20T13:35:34.233359Z

I'm sure there are better docs for this in the reagent docs somewhere

p-himik 2025-03-20T13:36:41.475199Z

@thheller I have a vague recollection of you writing somewhere that tagging symbols with actual "types" is useless right now (well, except for the js bit, yes), but might be useful in some potential future that could never come - if GCC starts supporting some required parts. If I'm not hallucinating, wouldn't that also require tags being aware of imports/requires? So that ^GLViewer is the GLViewer and not some plain symbol.

thheller 2025-03-20T13:41:18.951589Z

not saying inference can't be improved. just referring to the current state, which I don't expect changing much. certainly not impossible to resolve tags, just not done currently.

๐Ÿ‘ 1
Mark S 2025-03-20T13:57:28.143079Z

I'm new to clojurescript. I'm using nbb. This code works and logs output to console but how do I wait/return the JSON in the promise?

(let [client (new s3/S3Client)
        req (new s3/ListBucketsCommand #js{})]
    (.then (.send client req)
           #(js/console.log %)))

p-himik 2025-03-20T14:01:14.718269Z

I don't know about nbb but in general - you don't. You pass the next callback that does what you want. If you want the value to be available in the REPL, you can use an atom. If you want to access the value from some user of the above code, that user has to provide a function. Or use some sugar like Promesa or one of the variants of js-await macros you can find online to make it look like there's no function.

borkdude 2025-03-20T14:02:07.306589Z

Promesa is built into nbb

Mark S 2025-03-20T14:12:06.727399Z

I'm trying to use promesa and can print the result but not sure how to return it:

(let [client (new s3/S3Client)
        req (new s3/ListBucketsCommand #js{})]
    (p/let [foo (.send client req)]
      (prn foo)))

borkdude 2025-03-20T14:13:04.537229Z

See the nbb channel for a similar discussion. You canโ€™t break out of promises, you can only chain them

borkdude 2025-03-20T14:13:21.840869Z

Learning about how promises work fundamentally may help

Mark S 2025-03-20T14:15:54.847409Z

ok, thanks!

markaddleman 2025-03-20T15:51:34.695299Z

Iโ€™m trying to include https://github.com/maxGraph/maxGraph as a dependency in my clojurescript app. When shadow-cljs compiles, I get the error

Errors encountered while trying to parse file
  /Users/markaddleman/IdeaProjects/maxgraph-demo/node_modules/@maxgraph/core/lib/index.js
  {:line 113, :column 9, :message "'from' expected"}
Details in thread

markaddleman 2025-03-20T15:52:34.432429Z

Line 113 is

export * as constants from './util/Constants';
which grok tells me is valid for ecmascript 2020

markaddleman 2025-03-20T15:52:51.230359Z

My shadow-cljs is

{:source-paths ["src"]
 :dependencies []
 :builds
 {:app
  {:target           :browser
   :output-dir       "target/public/js"
   :asset-path       "js"
   :modules          {:main {:entries []}}
   :compiler-options {:language-in :ecmascript-2020}}}}

markaddleman 2025-03-20T15:53:51.418979Z

Is this error coming from the clojurescript compiler or something else? If from the cljs compiler, are there other config knobs to get it to understand this syntax?

markaddleman 2025-03-20T15:54:30.002189Z

If itโ€™s relevant, Iโ€™m including maxgraph in my code using

(ns 
  (:require ["@maxgraph/core" :as maxgraph]))

thheller 2025-03-20T15:56:08.968789Z

https://github.com/google/closure-compiler/issues/4177

markaddleman 2025-03-20T15:56:47.699759Z

ah. Thanks ๐Ÿ˜•

thheller 2025-03-20T15:56:51.646589Z

if you must use that package you'll need to switch to :js-provider :external https://shadow-cljs.github.io/docs/UsersGuide.html#js-provider-external

markaddleman 2025-03-20T15:57:13.987769Z

Oh, cool. Thereโ€™s a workaround.

markaddleman 2025-03-20T15:57:21.689929Z

Iโ€™ll check it out. Very much appreciated!

Josh 2025-03-20T21:46:12.802989Z

Is there anyway to "override" clojurescript's do in a namespace with my own custom fn/macro and then still use regular do in that namespace? With a regular function like mapv I would just do this

(ns my-ns
  (:refer-clojure :exclude [mapv])
  (:require
   [clojure.core :as c]))

(defn mapv ...)
Then I could still use mapv by doing c/mapv But with do the docs say it's special form and c/do isn't defined Is there some way to get around this?

p-himik 2025-03-20T21:49:24.284279Z

You can potentially do it but that would require changing how CLJS works, AFAICT. Why would you want something like this?

Josh 2025-03-20T21:52:42.914879Z

I'm writing some code to help with promises and wanted to have a custom macro for it named do, I can just name it do!but I was curious if it was possible

Josh 2025-03-20T21:53:07.348119Z

I see that promesa is able to have a custom do but they don't use clojure's do in the same namespace

p-himik 2025-03-20T21:55:22.538869Z

You can mimic the built-in do with e.g. (let [] ...).

Josh 2025-03-20T21:57:18.185989Z

thanks! I might do that

p-himik 2025-03-20T22:01:10.849359Z

Alternatively, shift the declaration of your own do to the very end of the namespace. But you'd have to be careful and document that it should not become higher.

Josh 2025-03-20T22:02:28.121149Z

oh interesting, I'll try that, I would think that I would get compiler warnings from it

Josh 2025-03-20T22:03:34.605099Z

it does seem to work!

Josh 2025-03-20T22:04:07.549449Z

oh wait, I need to reuse it in this namespace lol

Josh 2025-03-20T22:04:28.194169Z

but, I could just have two macros

p-himik 2025-03-20T22:11:54.471659Z

Or move the usages below the definition. Unless the same top-level form uses both your custom do and the built-in one. And maybe (declare do) would work as well, I actually don't know.

2025-03-21T00:17:42.544059Z

A symbol like do is often referred to as a special form, but more pedantically the special form is (do ...) meaning any seq starting with the symbol do, and often lisps will ignore any other bindings for those names that start their special forms, it depends if they check for the name being bound first, or check for special forms first in their implementation. I am not sure what clojurescript does, but it would not surprise me if you can define a macro with a name that is the same as a special form, but it doesn't let you use it

๐Ÿ’ก 1
2025-03-21T00:23:26.294699Z

https://github.com/clojure/clojurescript/blob/master/src/main/clojure/cljs/analyzer.cljc#L4118 looks it checks for a special form and does that before falling back to looking up stuff in the environment

2025-03-21T00:24:47.986009Z

So you can never have a (do ...) where do is not the built in special form, stuff like (somens/do ..) seems like it would work

Josh 2025-03-21T00:46:55.062529Z

For the most part my special do will be used outside of the namespace it's defined in, something like mp/do. I settled with defining a do! macro at the top of the file and then do (which just calls do! at the bottom of the file

Josh 2025-03-21T00:47:48.739019Z

This makes me wonder then, in promesa, they define a special do macro and then also refer clojure exclude do, but they use do in 1 place https://github.com/funcool/promesa/blob/11.0.671/src/promesa/core.cljc#L658

Josh 2025-03-21T00:47:58.225499Z

so which do is being used?

Josh 2025-03-21T00:49:08.141829Z

Seems like from the code you posted @hiredman, that even though do is excluded, that cljs.core/do is being used

2025-03-21T00:55:08.180569Z

There are layers, because it is in syntax quote the symbol gets namespace qualified by the reader

2025-03-21T00:56:36.514839Z

To me there are enough edge cases around using the same name as special forms that I would just avoid it

2025-03-21T00:59:45.025469Z

(although after saying that, syntax quote may also treat special forms specially)

Josh 2025-03-21T01:03:34.171819Z

normally I would avoid this problem as well but I thought I would ask and see if I can learn something.

Josh 2025-03-21T01:04:14.026299Z

plus as an API which someone might consume if I release this as a library do is a little more clear than do!

Josh 2025-03-21T01:05:52.436469Z

I think you're right that it's the clojure do, looks like promesa fully qualifies their do inside of macros https://github.com/funcool/promesa/blob/11.0.671/src/promesa/core.cljc#L586 (I'm also a macro noob, maybe this is totally normal to do anyway)

p-himik 2025-03-21T09:44:14.807929Z

> it checks for a special form and does that before falling back to looking up stuff in the environment @hiredman It's the opposite. The linked function, analyze-seq*, is used in this context:

(let [mform (macroexpand-1 env form)]
  (if (identical? form mform)
    (analyze-seq*-wrap op env form name opts)
    (analyze env mform name opts)))
So macros specifically are expanded before special symbols are checked.