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.
https://github.com/binaryage/cljs-oops will help you with this
If you donβt want to use the lib, it still includes a good explanation of the problem and solution.
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
Yes.
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.
BTW, just in case - you're posting a "success" message unconditionally, even if .write has actually failed to save the buffer.
: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.