Error when using :advance compiler optimization: createSVGPoint is not a function

clojurescript

#1

I’m new to Clojurescript but I have been playing around with Clojure for a while now. Recently I came across a Conway Game of Life example in the Clojure Programming book and I thought it would be fun to code it in Clojurescript and re-frame.

I managed to create one version which is working in dev mode. But when I compile it in production mode and run it, part of it stopped working (it is supposed to highlight the cell that the mouse pointer is on and to toggle the cell on mouse click).

Using pretty print and pseudo name compiler options, the error on the browser console is:

Uncaught TypeError: $cell_size$jscomp$1$$.$createSVGPoint$ is not a function
    at app.js:11480

I’m calling this function on a SVG element like this:

(defn- svg-pos [client-pos svg-elem]
  (let [[x y] client-pos
        svg-point (.createSVGPoint svg-elem)
        matrix (-> svg-elem .getScreenCTM .inverse)]
    (set! (.-x svg-point) x)
    (set! (.-y svg-point) y)
    (let [gpt (.matrixTransform svg-point matrix)]
      [(int (.-x gpt)) (int (.-y gpt))])))

When I switched to simple optimisation, I don’t get the error.
Here’s the demo using simple optimization.

Is there something that I need to do to use advance compilation?


#2

https://clojurescript.org/reference/advanced-compilation
https://clojurescript.org/reference/dependencies


#3

In advanced compilation mode, the Google Closure Compiler (GCC) shortens the names of properties and methods, to save space.

When you access properties using the (.-property o) notation and invoke methods using the (.method o) notation, you’re generating candidates for GCC to uglify.

Some tips:

  • the first thing to do is always to temporarily set the pseudo-names option. This will still mangle the strings, but in a less dramatic way so you can still guess at the original name
  • some common property names common in DOM operations, like .-value, are in an internal list and exempt from uglification. This can cause confusion ("Why does .-value work but .-myValue doesn’t?)
  • when accessing data, you’re safe if use (goog.object/get o "property") instead of (.-property o)
  • when invoking methods, you’re safe if use (.call (goog.object/get o "method") o arg1 arg2) instead of (.method o arg1 arg2) (note the extra argument o in the .call form)

The last two bullet points represents a quick fix for the issue, but they can be tedious to apply (cljs-oops makes this more pleasant). The official way to avoid uglification of names is to supply a special configuration file called “externs” to GCC. The CLJSJS project contains externs for many of the libraries it packages. It’s also possible to write your own externs file.

Finally, recent versions of the ClojureScript compiler will automatically generate externs for foreign libraries, if so configured.

In your case, it looks like the property name gets mangled before the svg-pos function is even called. So I’d look for a culprit further upstream.


#4

I see now that you had psuedo-names on already.


#5

i don’t know which cljs compiler you are using. shadow-cljs has lots of work on this aspect. Try this https://shadow-cljs.github.io/docs/UsersGuide.html#externs


#6

This issue is also solved by supplying an externs file to GCC, as suggested by @pesterhazy. I tried the extern inference option in Clojurescript, and I also tried using extern inference in Shadow CLJS as suggested by @jiyinyiyong.

I have to say the experience in Shadow CLJS is much better. At least for my simple program:

  • the warnings were spot on.
  • The type hint that has to be added for the Shadow CLJS to generate the extern file is just ^js. Shadow CLJS does not require the actual type to be specified.

Shadow CLJS also has other ‘conveniences’ spelt out in the user manual which I did not have the chance to use:

  • it accepts a simplified extern file
  • calls on globals using js/ do not require type hints
  • no hints required on :as and :refer bindings