Clojurscript: extend a Javascript class

1.7k Views Asked by At

I'm trying to use a particular JavaScript framework which requires extending a base class to use it for application.

Basically I want to do the following as idiomatic ClojureScript.

class Foo extends Bar {
  constructor() { super("data") }
  method1(args) { /* do stuff */ }
}

I tried

(defn foo
  []
  (reify
    js/Bar
    (constructor [this] (super this "data"))
    (method1 [this args] )))

Which would work if I'd create a new class from Object, but as shadow-cljs correctly complains, "Symbol js/Bar is not a protocol". Also, I don't want to add methods but create a subclass that inherits somemethods and overloads others.

I thought about using proxy, but "core/proxy is not defined".

Of course I could create an instance of Bar and set! new methods, but that feels like giving up and using an inferior language.

2

There are 2 best solutions below

4
On BEST ANSWER

Please see answer below for more current solution!

CLJS has no built-in support for class ... extends ....

You can hack it together yourself with a bit of boilerplate, which you could generate via a macro to make it look pretty.

(ns your.app
  (:require
    [goog.object :as gobj]
    ["something" :refer (Bar)]))

(defn Foo
  {:jsdoc ["@constructor"]}
  []
  (this-as this
    (.call Bar this "data")
    ;; other constructor work
    this))

(gobj/extend
  (.-prototype Foo)
  (.-prototype Bar)
  ;; defining x.foo(arg) method
  #js {:foo (fn [arg]
              (this-as this
                ;; this is Foo instance
                ))})
3
On

CLJS (still) has no built-in support for class ... extends ....

However in recent shadow-cljs versions I added support for class as well as extends. This will emit a standard JS class and does not require any hacky workarounds to get it working.

Translating this example

class Foo extends Bar {
  constructor() { super("data") }
  method1(args) { /* do stuff */ }
}

would be

(ns your.app
  (:require [shadow.cljs.modern :refer (defclass)]))

(defclass Foo
  ;; extends takes a symbol argument, referencing the super class
  ;; could be a local symbol such as Bar
  ;; a namespaced symbol such as alias/Bar
  ;; or just a global via js/Bar
  (extends Bar)

  (constructor [this]
    (super "data"))

  ;; adds regular method, protocols similar to deftype/defrecord also supported
  Object
  (method1 [this args]
    ;; do stuff
    ))

More complex examples of defclass can be found here and here.

Currently this only ships with shadow-cljs but technically you can take the modern.cljc and modern.cljs files from here and put them into your project. It should work with all build tools then.