clojurescript 2024-12-24

It seems that the method createSyncAccessHandle from the FileSystemFileHandle interface of the https://developer.mozilla.org/en-US/docs/Web/API/File_System_API gets munged in Clojurescript advanced compilation. What course of action would you recommend?

Can you just wrap it then? If go messes with it.

(defn create-sync-access-handle
 [file-handle]
 (.createSyncAccessHandle ^js file-handle))

Then call create-sync-access-handle from inside the go block.

You can, yes. Every single thing that uses ^. You'll end up with 3 times as much CLJS code, and with probably around 20 times as much compiled JS code. For little to no gain.

If you don’t want to use the lib, it still includes a good explanation of the problem and solution.

Awesome. Thank you!

πŸ‘ 1

Calling the method via js-invoke fixed it.

The modern recommendation is to never use cljs-oops and instead rely on externs inference. When you use (.-xxx obj) and xxx gets minified while it shouldn't be, just use (.-xxx ^js obj), that's it.

The problem is with a method, not a property.

Doesn't matter.

Does this presuppose :infer-externs true?

I think so, yes. Can't tell for certain as I've been using shadow-cljs for years, and AFAICT it has expanded the externs inference functionality a bit.

Documentation says yes, only with that compiler flag, but it also says that this works only for property access.

Add a type hint to a local binding name to prevent advanced compilation from munging property names on . dot callsβ€” only if :infer-externs is enabled.

For my use case, js-invoke is the correct solution.

A member function is a property. It's just a callable property - that's it. js-invoke is specifically for situations where the name of the method is not a valid identifier.

This makes sense.

What are the semantics for calling the member function retrieved via property access?

If it doesn't use this then just as a regular function. If it does use this, then you have to bind it to the object. But (.xxx ^js obj) works just fine - you don't need to bind the value of xxx to something and then call it.

I would be delighted if you were right, but I did the experiment and (.createSyncAccessHandle ^js file-handle) didn't work while (js-invoke file-handle "createSyncAccessHandle") did.

What am I missing?

Maybe because the code is inside a go block?

And it gets rewritten

by core.async

You have suggested using core.async in another thread, and this is yet another reason not to use it in CLJS if you can. :) go blocks lose all type hints, including ^js.

I just tested it with the vanilla compiler, with advanced optimizations, without explicitly setting :infer-externs. This code

(defn ^:export f [file-handle]
  (.createSyncAccessHandle ^js file-handle))
became this
function u(a){return a.createSyncAccessHandle()}
So as you can see, the method name is preserved just fine, and :infer-externs doesn't seem to be required in this case.

That is an important caveat indeed. I might reconsider. But do you have an elegant way for promises interop? I was using the <p! sugar.

On the other hand, it worked for me even without ^js. CLJS version is 1.11.132.

> But do you have an elegant way for promises interop? I just use the regular JS interop for them. Alternatively, you can use the js-await macro: https://clojureverse.org/t/promise-handling-in-cljs-using-js-await/8998

Thank you for testing, and for your time. But then I should expose more context. I am running code in a web worker that does file system stuff.

(go (let [root-storage (<p! (.getDirectory js/navigator.storage)) sub-dir (<p! (.getDirectoryHandle root-storage dir #js {:create true})) file-handle (<p! (.getFileHandle sub-dir (.-name file) #js {:create true})) access-handle (<p! (js-invoke file-handle "createSyncAccessHandle")) ;; otherwise gets munged in Clojurescript advanced compilation file-reader-sync (js/FileReaderSync.) buffer (.readAsArrayBuffer file-reader-sync file)] (try (.write access-handle buffer) (finally (.flush access-handle) (.close access-handle) (.postMessage js/self (str (.-name file) " saved"))))))

js-await could certainly work. It would require a refactoring.

That context doesn't really affect things. This is how I'd write it, or something like that:

(let [file-reader-sync (js/FileReaderSync.)
      buffer (.readAsArrayBuffer file-reader-sync file)]
  (-> (.getDirectory js/navigator.storage)
      (.then #(.getDirectoryHandle % dir #js {:create true}))
      (.then #(.getFileHandle % (.-name file) #js {:create true}))
      (.then #(.createSyncAccessHandle %))
      (.then #(try
                (.write % buffer)
                (finally (.flush %)
                         (.close %)
                         (.postMessage js/self (str (.-name file) " saved")))))
      (.catch (fn [e]
                (somehow-handle-the-error e)))))

This is good, indeed.

Thank you.

πŸ‘ 1

BTW, just in case - you're posting a "success" message unconditionally, even if .write has actually failed to save the buffer.

πŸ‘Œ 1
πŸ™ 1

:infer-externs true is enabled by default just fyi

So you never need to specify it to use the ^js hint

(if you're using shadow that is)

@p-himik Spot on. Thanks!

@bhlieberman93 Thanks! Yes, unlike the vanilla compiler which defaults to false.