Fork me on GitHub

Hey, just wrote up a question on Clojureverse, and was wondering if any of you have tips on how to use Google’s <model-viewer> with Reagent. Thanks 🙂


As far as I'm aware, what you describe are one of the reasons why custom elements aren't really taking off. It might be easy to just write something with them, but it's far from easy to develop with them, especially if you have to change the code of such a component.


You also write: > without having to go with a custom solution via something like react-three-fiber But how is using a custom element while requiring a custom <script> better than using a library? Conceptually, it's exactly the same. You will end with a React element that does something.


Ah what I mean by that is that <model-viewer> provides all the key functionality that I need. If I went with writing a custom solution I would have to write all of the functionality that I am getting already from <model-viewer> (ie, poster images, sane interface, AR compatibility and a great deal of edge case checks) It is possible for me to write all of those features on top of threejs but I would end up with an inferior version of all of them with the time that I have.


It is sad that they decided to so tightly couple this to the web component. Rather than have the web component be one way to interface with the broader feature set.


Indeed. Also, just as an additional piece of info against using it - it doesn't support Safari and Firefox.


Only for web-xr features


Which is an issue with the larger ecosystem


I’ve been developing this in firefox. Safari doesn’t support webxr at all on the mobile side.


Ah, alright.


Are there any other ways to try to integrate this beyond the two I mentioned?


An <iframe>, unless that's what you mean by "a separate html element from the Reagent controlled application".


Maybe it's possible to tear apart the library and use it without having to rely on the custom elements machinery.


By the separate element I meant a manually created element outside of the “app” hierarchy mounted by reagent. Thanks for the tips.

👍 1
Pepijn de Vos12:03:11

Dumb question... when writing an async loop, do I have to use loop/recur or for/doseq is also fine? I found one random SO answer that claims doseq is fine but for is not because it's lazy, but my code did not work with doseq so I'm just making sure that I can rule that out as a problem


Anything should be fine if you use it correctly and in the right context. It's better to ask a more concrete question here. Like "why does my async code not work with doseq", while also providing the code itself. :)

Pepijn de Vos12:03:30

Yea I'll post it if there is anything I can't solve. For now it's probably simple bugs that are more work to provide the needed context than to debug. Just wanted to know what constructs are fine to use in theory.


As far as I'm aware, the only things that can definitely result in problems are interop-related. In particular, ^js disappears in go blocks. But there are workarounds.

Pepijn de Vos14:03:01

Works! Indeed turns out my bug was some destructuring, and then some thing that wasn't sorted, etc. Doseq works fine.

👍 1

for a reframe electron app, does any jvm/java code run, or is OS io done through node / cljs calls?


could the internal api be luminus and front end reframe I guess is what im asking


the 2nd one

thanks3 1

Are there any good tutorials out there for using clojurescript interop with javascript promises? I'm trying to use it with showOpenFilePicker() which returns a JS promise, but all i seem to be able to do with the return so far is to generate console errors.


Thank you. I had found that earlier, but had not been able to get it to work. I finally got it working with the code:

(defn ld-file
  (-> (. js/window showOpenFilePicker)
      (.then #(.getFile (first %)))
      (.then #(.text %))
      (.then #(println %))))
Thank you again.


I have a situation where some dead code isn't being eliminated with :optimizations :advanced. Specifically, a whole lot of code ends up getting pulled in because of a situation like D below:

(def A {"This is eliminated" "This is eliminated"})
(def B (f "This is eliminated"))
(def C {nil (f "This is eliminated")})
(def D {(f "This is _NOT_ eliminated") nil})
The resulting code doesn't contain any references to D, but it does contain a 'bare' statement that creates the map (`{(f "This is NOT eliminated") nil}`). It doesn't seem to matter what f is — I get the same result if I replace it with identity. Any ideas what might be causing this? Is there anything I can do to help the compiler get rid of code like this?


@wombawomba probably not, it might be a case that Closure Compiler can't handle


assuming f is free of side effects, you could probably create an example where this fails for plain JavaScript and request an enhancement


by free of side effects, also means no logging, printing etc.


Alright, thanks. Yeah it doesn't seem to matter if there are side effects or not (I'm assuming identity doesn't have any side effects even when compiled)


Let's say I want to boil this down to a minimal JS example. Is there a way to compile with advanced optimizations but without dead code elimination, and then run the code elimination pass separately?


I don't think so


but this seems like it should not be so hard to reproduce


var d = new CustomDataType(foo("bar"));


that's effectively what we generate


hmm... I wouldn't think it's that simple


it definitely is


I mean sure, the code generation is, but this issue might not be.


I'm very skeptical about that having tuned DCE for ClojureScript


it always trivial stuff


Right, yeah. I'm thinking more about that this issue only seems to arise in some cases (`{(identity 1) 2}` but not {1 (identity 2)}). The code gets compiled to:

PersistentArrayMap$createAsIfByAssoc(["This is _NOT_ eliminated", null]);
(which doesn't get eliminated), vs:
PersistentArrayMap$createAsIfByAssoc([null, "This is eliminated"]);
(which does get eliminated)


The problem seems to only (and consistently) occur when there's a key in a map that gets created via a (any) function call.

wombawomba16:03:35 I'm assuming that this is due to something specifically with how createAsIfByAssoc is defined

Ferdinand Beyer16:03:12

Is there some caching/interning involved for map keys maybe?

Ferdinand Beyer16:03:37

(i.e. a side effect)


that is true, for strings, keywords, symbols I think for hashing we may intern (if we can't figure out the hashes at compile time)

👍 1

Alright, so assuming that intering is what's causing this, could there be a way to work around it?


not that I can think of, it's a runtime hashing performance optimization


but I'm not 100% sure this is the problem - you'd have to audit


Hmm... audit how?


like look at what createAsIfByAssoc does in the standard library - I scanned it but I didn't see anything particular suspicious


yeah that's what I'm doing right now


I also can't see that it's doing anything especially weird.


yeah, so then I would try to make something minimal - it's not obvious to me why the values work but not the keys


you also might want to try master, a few things were changed to align w/ Clojure 1.11 in this code path


looking at the generated JS, the only thing that seems to differ between keys and values is that it calls cljs$core$array_index_of$ for the keys


...although that function is a bit more complicated, so perhaps it's what's causing problems for Closure


right I don't think that's in the path in anymore


Is there a nightly build or something I can use to test master, or do I need to build it myself?


if you use tools deps there nothing to do - it supports git deps


you can point at a sha and it will work


I'm not, but either way I went ahead and compiled it on my own


Unfortunately, the changes on master don't seem to help.


hrm that seems harder to explain then, since I don't think array-index-of is in the path


also if there are no dupes, then the code path is even more simple


yeah it doesn't seem to be there in the compiled code


yes probably dead


and the earlier theory is even less believable


because there is no difference between keys and values


the emitted code is literally new PAM([k0, v1, k1, v1, ...]) w/o further processing


hmm... afaict the emitted createAsIfByAssoc is actually pretty complex


it seems to walk through the code and call cljs.core/key-test to check for identical keys


key test is uninteresting and free of effects


the code is not complex in the normal path


it checks for dupes, if there are none - effectively what is done is the above


it maybe something about the key testing is thwarting Closure - but then this seems to point at a Closure issue


I'll try removing the key check and see if that fixes it.


Yup, replacing createAsIfByAssoc with a direct call to PersistentArrayMap. fixes the problem.


right only option is to request some kind of enhancement to Closure - there's really nothing else we could do


since we don't know what the keys are we resort to a dynamic construction


which is the correct thing to do


Makes sense. I'll try to see if I can trick Closure into doing the right thing, and if not, I guess I'll have to try to repro in plain JS and file a ticket with Closure


pretty sure this comes down to equality semantics that are too complex for closure to "understand". there is also a path that may throw so that alone probably stops the elimination


IIRC Closure marks everything that may throw as not pure


although this might warrant some investigation. the only path that may throw is the cljs.core/missing-protocol. but since it has a default implementation it will never reach that, so in theory could become somewhat more pure?


but I doubt it'll understand protocols such as IEquiv fully so it probably ends there


I'm trying to see if that could be it. Is there any way to do a regular js equality check in cljs to confirm?


you can just replace the key-test with identical?


Yeah that does the trick.


So I guess the conclusion then is that there's not really anything that can be done about this.


...if, in theory, I wanted to work around this for my project, would it be possible for me to alter-var-root! or set! createAsIfByAssoc before any of my code gets compiled? 😇


what are you trying to do in the first place? I mean what is this map that you are trying to get rid of and why?


seems like an easier strategy to eliminate that map before it even gets to closure?


@wombawomba you going to have be very careful - honestly I don't see how you wouldn't just break everything


eliminating these maps also doesn't seem interesting unless you're getting rid of more than 10% of your code by doing so


@thheller So I'm using — and I'm defining a whole bunch of schemas in different places (like (def MySchema {(schema.core/optional-key :foo) schema.core/Str})) — and I want to compile a release build that's completely stripped of all schema definitions except those I actually use (I do (schema.core/check MyOtherSchema x) in a few places).


@dnolen this stuff seems to account for ~30% of my build size


then you have to go back to the first point - I don't see how you won't break your deps


you cannot use identical?


(by "those I actually use", I mean in this specific build — the schemas are mostly there to check fn inputs/outputs in development)


@dnolen why not? 🙂 Duplicate keys normally cause an error, no? So if I'm not seeing any such errors, then shouldn't I be able to just get rid of the duplicate key check?


there are no checks for duplicate keys if you switch to identical?


you will just have broken maps


any 3rd party lib that assumes the usual behavior won't work


I'd only have broken maps if I actually try to create maps with duplicate keys though?


and any of your dependencies


...actually, I'm creating all these schemas using a macro anyway — would it be possible to disable the check just for the scope of that particular macro?


I think a better alternative might to use ObjMap


your macro could rewrite the schema emission to use that instead


or write your own super simple map implementation etc.


and emit that instead


more likely to avoid the dragons


Yeah I suppose


Although, I'd rather avoid having to do a 'deep' rewrite of the code in the macro. Would is be possible to use something like with-redefs for this purpose?


(also, if I wanted to go down that path, could I do something like that for createAsIfByAssoc?)


nothing like that will work


or rather I'm super skeptical that Closure will make sense of these redefinitions - effectively runtime mutation


but visible to Closure


just make a DCE friendly map - that is the best way


@wombawomba instead of (def MySchema {(schema.core/optional-key :foo) schema.core/Str}) you could (dev-only-def MySchema {(schema.core/optional-key :foo) schema.core/Str}) macro that just emits the code in a way that can be eliminated. eg (def MySchema (when ^boolean js/goog.DEBUG {(schema.core/optional-key :foo) schema.core/Str}))


yeah, that's even simpler if this is really about eliminating dev build stuff


@thheller yeah I've actually built something like already, but it ends up being kind of clunky having to keep track of which schemas to annotate


another option is moving all the dev only schemas to a dedicated namespace that is only included via preloads. ie. only in dev


depending on how you use all the schemas delay may also work. (def MySchema (delay {(schema.core/optional-key :foo) schema.core/Str})). just requires using @MySchema whereever you would actually use it


I can't really move the definitions — a lot of them are part of shared libraries used in multiple clj and cljs projects


delays are potentially interesting... are you saying that anything wrapped in a delay won't be included in the compilation output unless it's actually used?


I'm already defining my own versions of schema.core/def, schema.core/defn etc, so I suppose I could just make them work with delays transparently


last time I checked delay is eliminated if never deref'd regardless of what is in it


could there be some automated way to figure out which schemas I need to emit?


...actually, it seems like there's a Closure annotation ( that could be used to mark functions as side effect-free. Couldn't this be used to help the Closure compiler eliminate the problematic code?


it cannot - only supported in externs


I'm attempting to read and write files in clojurescript by transliterating the code I found in The translteration of the file read code was somewhat simple, and using the pointers in, I was able to get it working fairly easily. However, writing seems more involved. I am translating the following code:

async function writeFile(contents) {
  const handle = await window.showSaveFilePicker();
  // Create a FileSystemWritableFileStream to write to.
  const writable = await handle.createWritable();
  // Write the contents of the file to the stream.
  await writable.write(contents);
  // Close the file and write the contents to disk.
  await writable.close();
According to the advice in the promise-interop guide, this should translate to (something like):
(defn save-file
  (let [writeable (-> (. js/window showSaveFilePicker)
                      (.then #(.createWritable %)))]
    (.then (fn [writeable]
             (-> (.write writeable data)
                 (.then #(.close writeable)))))))
However, when I try to run this, I get the following error:
./cljs-runtime/atlas.complexes.js:472 Uncaught TypeError: (intermediate value).then is not a function
    at Object.atlas$complexes$save_file [as save_file]
I'm pretty sure that something in my translation of the close is messed up. Anyone have any ideas as to where my translation is going wrong and how to right it?


Your second .then is not called on a promise with a function as an argument. Instead, you call it on the (fn [writable] ...) function, with no arguments.


(-> (.showSaveFilePicker js/window)
    (.then (fn [^js handle]
             (.createWritable handle)))
    (.then (fn [^js writable]
             (-> (.write contents)
                 (.then (fn [_]
                          (.close writable)))))))
or something like that.


Thank you. I'll give it a try.


I messed about with it a little bit. It turns out that this is the magic formula:

(-> (.showSaveFilePicker js/window)
    (.then (fn [^js handle]
             (.createWritable handle)))
    (.then (fn [^js writable]
             (-> (.write writable contents)
                 (.then (fn [_]
                          (.close writable))))))))
Thanks for your help!


Ah, right, missed that writable in there. NP!