Viktor Hedefalk
Viktor Hedefalk

Reputation: 3914

How to provide and consume require.js modules in scala.js (and extending classes)

I'm doing this Ensime package for Atom.io https://github.com/ensime/ensime-atom and I've been thinking about the possibility to use scala.js instead of writing Coffeescript.

Atom is a web based editor which is scripted with js and is node.js based. A plugin/package defines it's main entry point by pointing out a javascript object with a few specific.

I figured I should start out simple and try using scala.js replacing the simplest coffeescript file I have:

{View} = require 'atom-space-pen-views'
# View for the little status messages down there where messages from Ensime server can be shown
module.exports =
  class StatusbarView extends View
    @content: ->
      @div class: 'ensime-status inline-block'

    initialize: ->

    serialize: ->

    init: ->
      @attach()

    attach: =>
      statusbar = document.querySelector('status-bar')
      statusbar?.addLeftTile {item: this}

    setText: (text) =>
      @text("Ensime: #{text}").show()

    destroy: ->
      @detach()

As you can see this exports a require.js module and is a class extending a class fetched with require as well.

Sooo.

I'm thinking I'd just use Dynamic for the require dep as I've seen on SO How to invoke nodejs modules from scala.js?:

import js.Dynamic.{global => g}
import js.DynamicImplicits._

private[views] object SpacePen {
  private val spacePenViews = require("atom-space-pen-views")
  val view = spacePenViews.view
}

But if I wanted to type the super-class, could I just make a facade-trait and do asInstanceOf?

Secondly, I wonder how I can export my class as a node module. I found this:

https://github.com/rockymadden/scala-node/blob/master/main/src/main/coffeescript/example.coffee

Is this the right way? Do I need to do the sandboxing? Couldn't I just get moduleimported from global and write module.exports = _some_scala_object_?

I'm also wondering how I could extend existing js classes. The same problem as asked here, but I don't really understand the answer:

https://groups.google.com/forum/#!topic/scala-js/l0gSOSiqubs

My code so far:
private[views] object SpacePen {
  private val spacePenViews = js.Dynamic.global.require("atom-space-pen-views")
  type View = spacePenViews.view
}

class StatusBarView extends SpacePen.View  {
  override def content =
    super.div()

}

gives me compile errors that I can't extend sealed trait Dynamic. Of course.

Any pointers highly appreciated!

Upvotes: 4

Views: 856

Answers (1)

Justin du Coeur
Justin du Coeur

Reputation: 2659

I'm not particularly expert in Node per se, but to answer your first question, yes -- if you have a pointer to a JS object, and you know the details of its type, you can pretty much always define a facade trait and asInstanceOf to use it. That ought to work.

As for the last bit, you basically can't extend JS classes in Scala.js -- it just doesn't work. The way most of us get around that is by defining implicit classes, or using implicit def's, to get the appearance of extending without actually doing so.

For example, given JS class Foo, I can write implicit class RichFoo(foo:Foo) { def method1() = { ... } } This is actually a wrapper around Foo, but calling code can simply call foo.method1() without worrying about that detail.

You can see this approach in action very heavily in jquery-facade, particularly in the relationship between JQuery (the pure facade), JQueryTyped (some tweaked methods over JQuery to make them work better in Scala), and JQueryExtensions (some higher-level functions built around JQuery). These are held together using implicit def's in package.scala. As far as calling code is concerned, all of these simply look like methods on JQuery.

Upvotes: 2

Related Questions