3

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.

waechtertroll
  • 607
  • 3
  • 17

2 Answers2

12

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
                ))})
Thomas Heller
  • 3,842
  • 1
  • 9
  • 9
  • Thanks for your answer. It's a pity there is no built-in facility for that, but that will finally give me a reason to read deeper into macros. – waechtertroll Apr 06 '20 at 06:07
  • 1
    Is there any way to avoid the `Class constructor L cannot be invoked without 'new'` issue when using this method? My current workaround is creating stub JS files containing `export default class Foo extends Bar {}` for each class, importing them, and using `gobj/extend` to define the actual functionality in CLJS. It works, but the folder of JS stubs is clunky and a bit error prone. – Greg May 04 '20 at 12:45
  • Not sure I have seen that error message before. I know there are some issues when trying to extend native built-in classes (eg. `HTMLElement`) which require working with `Reflect` in some way but I can't remember the details. – Thomas Heller May 04 '20 at 14:33
  • The specific error is likely a result of `:output-feature-set :es6`, which I have to use in turn to avoid the Closure compiler error `ES6 transpilation of 'assigning to a super property' is not yet implemented` being caused by a library I'm using. I'll look further into using `Reflect` and see if that can help here - thanks very much for the pointer and the quick reply. Worst case the JS stub files still work, I just hope we don't start to see the ES6 ecosystem leaving us behind! – Greg May 04 '20 at 16:28
11

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.

Thomas Heller
  • 3,842
  • 1
  • 9
  • 9
  • This is quite interesting. I was going through the implementation [here](https://github.com/thheller/shadow-cljs/blob/master/src/main/shadow/cljs/modern.cljc) and was pleasantly surprised by how extensible the CLJS compiler is. Might be a while (or never) before this would be supported by the compiler natively. Have you consider publishing it as a separate library? – kofrasa Feb 25 '23 at 17:03
  • Not at this time, but feel free to take the two files and make your own library. Just change the namespace please. – Thomas Heller Feb 25 '23 at 22:30
  • How do I call super.method1() from overloaded method1 ? – royaltm Apr 28 '23 at 22:11