clojurescriptgoogle-closure-compilerquillclojurescript-javascript-interop

How can I prevent the closure compiler from minifying certain methods in clojurescript?


I'm integrating quilljs with my clojurescript application. I'm including it in my project.cljs file like so: [cljsjs/quill "1.3.5-0"].

The compiler is minifying some methods and is causing an error:

function xA(a, b) {
        var c = t(a).getSelection(!0)
          , d = c.index
          , e = c.length
          , h = Quill.import("delta");
        c = function(b, c, d, e) {
            return function(b) {
                return t(a).updateContents((new e).rf(c).delete(d).nf({
                    image: b
                }))
            }
        }(c, d, e, h);
        return b.c ? b.c(c) : b.call(null, c)
    }

This is the error: Uncaught TypeError: (intermediate value).rf is not a function

The clojurescript code looks like this:

(defn file-recieve-handler [this cb]
  (let [range (.getSelection @this true)
        index (.-index range)
        length (.-length range)
        delta (.import js/Quill "delta")]

    (cb (fn [url]
            (.updateContents @this
                             (.insert
                               (.delete
                                 (.retain (new delta) index)
                                 length)
                               #js {:image url}))))))

The retain method and the insert method are getting minified - and they shouldn't be. (The delete is not for some reason, I'm guessing that's because it's a keyword in javascript.)

I found the externs file for quilljs: https://github.com/cljsjs/packages/blob/master/quill/resources/cljsjs/quill/common/quill.ext.js

Is there someway I need to supplement the extern file or another way I can write the code so those two methods don't get minified when advanced compilation is turned on for the compiler?

For some context below is the full file. It's based on this https://github.com/benhowell/reagent-quill/blob/master/quill.cljs

(ns quill.core
  (:require
   [reagent.core :as r]))


(defn quill-toolbar [id]
  [:div {:id (str "quill-toolbar-" id)}

   [:span {:class "ql-formats"}
    [:select {:class "ql-header"}
     [:option {:value "1"}]
     [:option {:value "2"}]
     [:option {:value "3"}]
     [:option {:value "4"}]
     [:option {:value "5"}]
     [:option]]]

   [:span {:class "ql-formats"}
    [:select {:class "ql-font"}
     [:option]
     [:option {:value "serif"}]
     [:option {:value "monospace"}]]]

   [:span {:class "ql-formats"}
    [:select {:class "ql-size"}
     [:option {:value "small"}]
     [:option]
     [:option {:value "large"}]
     [:option {:value "huge"}]]]

   [:span {:class "ql-formats"}
    [:button {:class "ql-bold"}]
    [:button {:class "ql-italic"}]
    [:button {:class "ql-underline"}]
    [:button {:class "ql-strike"}]
    [:button {:class "ql-blockquote"}]]

   [:span {:class "ql-formats"}
    [:select {:class "ql-align"}]]

   [:span {:class "ql-formats"}
    [:button {:class "ql-script" :value "sub"}]
    [:button {:class "ql-script" :value "super"}]]

   [:span {:class "ql-formats"}
    [:button {:class "ql-indent" :value "-1"}]
    [:button {:class "ql-indent" :value "+1"}]]

   [:span {:class "ql-formats"}
    [:button {:class "ql-image"}] ]

   [:span {:class "ql-formats"}
    [:select {:class "ql-color"}]
    [:select {:class "ql-background"}]]

   [:span {:class "ql-formats"}
    [:button {:class "ql-clean"}]]])


(defn file-recieve-handler [this cb]
  (let [range (.getSelection @this true)
        index (.-index range)
        length (.-length range)
        delta (.import js/Quill "delta")]

    (cb (fn [url]
            (.updateContents @this
                             (.insert
                               (.delete
                                 (.retain (new delta) index)
                                 length)
                               #js {:image url}))))))


(defn editor [{:keys [id value selection on-change image-handler]}]
  (let [this (r/atom nil)
        get-value #(aget @this "container" "firstChild" "innerHTML")
        string-id (if (keyword? id) (name id) id) ]
    (r/create-class
     {:component-did-mount
      (fn [component]
        (reset! this
                (js/Quill.
                 (aget (.-children (r/dom-node component)) 1)
                 #js {:modules #js {:toolbar (aget (.-children (r/dom-node component)) 0)}
                      :theme "snow"
                      :scrollingContainer (str "quill-wrapper-" string-id)
                      :placeholder "Compose an epic..."}))

        (.on @this "text-change"
             (fn [delta old-delta source]
                 (on-change source (get-value))))

        ; FYI this is another area I had trouble. I got around it using 
        ; get and set in the goog.object
        (let [toolbar (.getModule @this "toolbar")
              handlers (goog.object/get toolbar "handlers")]
          (goog.object/set handlers "image" #(file-recieve-handler this image-handler)))

        (if (= selection nil)
          (.setSelection @this nil)
          (.setSelection @this (first selection) (second selection) "api")))

      :component-will-receive-props
      (fn [component next-props]
        (if
            (or
             (not= (:value (second next-props)) (get-value))
             (not= (:id (r/props component)) (:id (second next-props))))
          (do
            (if (= selection nil)
              (.setSelection @this nil)
              (.setSelection @this (first selection) (second selection) "api"))
            (.pasteHTML @this (:value (second next-props))))))

      :display-name  (str "quill-editor-" string-id)

      :reagent-render
      (fn []
        [:div {:id (str "quill-wrapper-" string-id) :class "quill-wrapper"}
         [quill-toolbar string-id]
         [:div {:id (str "quill-editor-" string-id)
                :class "quill-editor"
                :dangerouslySetInnerHTML {:__html value}}]])})))


(defn display-area [{:keys [id content]}]
  (let [this (r/atom nil)]
    (r/create-class
     {:component-did-mount
      (fn [component]
        (reset! this (js/Quill. (r/dom-node component)
                                #js {:theme "snow"
                                     :modules #js {:toolbar false}
                                     :placeholder ""}))
        (.disable @this))

      :component-will-receive-props
      (fn [component next-props]
        (.pasteHTML @this (:content (second next-props))))

      :display-name  (str "quill-display-area-" id)

      :reagent-render
      (fn []
        [:div {:id (str "quill-display-area-" id)
               :class "quill-display-area"
               :dangerouslySetInnerHTML {:__html content}}])})))


Solution

  • You can turn on externs inference warnings and the compiler will tell you about things that are likely to rename.

    ;; in the actual ns
    (set! *warn-on-infer* true)
    
    ;; in the build config compiler options
    :infer-externs true
    

    See https://clojurescript.org/guides/externs#externs-inference

    To help debug issues with renaming you can turn on :pseudo-names true in the compiler options. That'll make it easier to figure out which methods get renamed and may need a ^js typehint or manual externs.