This started as a discussion with David Nolen on Slack at https://clojurians.slack.com/archives/C07UQ678E/p1644422895167709
It seems to be a common pattern nowadays to rely on JS interop syntax along with ^js
at places where the compiler warns the user with "Cannot infer target type". At the same time, it doesn't seem to be uncommon to use built-in CLJS functions and macros like first
, seq
, when-first
, etc. on JS objects and arrays - after all, the support for them is explicitly there.
This leads to a problem that makes it incredibly easy to introduce issues to an optimized build that are impossible to guard against, unless you put ^js
everywhere you do JS interop.
It is especially true in really interop-heavy code, like this one, for example:
(defn replace-track-source! [^js playlist track-name src]
(loop [tracks (.-tracks playlist)]
(when-first [^js track tracks]
(if (= (.-name track) track-name)
(-> (to-audio-buffer src playlist)
(.then (fn [audio-buffer]
(let [audio-buffer (audio-util/normalize-volume audio-buffer)]
(set! track -src audio-buffer)
(.setBuffer track audio-buffer)
(.setCues track 0 (.-duration audio-buffer))
(.setPlayout track (doto (Playout. (.-ac playlist) audio-buffer)
(.setVolumeGainLevel (.-gain track))
(.setStereoPanValue (.-stereoPan track))))
(.calculatePeaks track (.-samplesPerPixel playlist) (.-sampleRate playlist))
(.adjustDuration playlist)
(.drawRequest playlist)))))
(recur (next tracks))))))
This case above is problematic because the compiler doesn't warn you if you omit the ^js
in front of the track
binding. But without that tag, names of all the functions called on track
will be minified.
One potential solution to this is to introduce an opt-in warning when a CLJS function that doesn't preserve the ^js
tag is used on a JS object/array.