I have written a facade using JSImport, and it works. Unfortunately, I arrived at the solution through trial and error, and I don't fully understand why this particular solution works but others I tried did not.
Background: I'm starting with a working project, built with sbt, which is a single page application that implements the client side code with scala.js and the server side with scala and the Play framework. The javascript libraries were packaged with web jars and bundled into the client js file using the sbt jsDependencies variable. I wanted to implement some new features which required a library up rev, which then required an up rev of some javascript libs which were only available in npm format. So now I am including all the javascript dependencies for the client app using npmDependencies with the scalajs-bundler plugin. This broke some of the scalajs facades leading to my question.
I'll use the facade to log4javascript as an example for this question.
The variable log4javascript
is the top level object used to access the rest of the api.
When the js libs were included as web jars, this is how the facade to log4javascript
was implemented:
@js.native
@js.annotation.JSGlobalScope
object Log4JavaScript extends js.Object {
val log4javascript:Log4JavaScript = js.native
}
After the change to npm:
import scala.scalajs.js.annotation.JSImport.Namespace
@JSImport("log4javascript", Namespace)
@js.native
object Log4JavaScript extends js.Object {
def resetConfiguration(): Unit = js.native
def getLogger(name:js.UndefOr[String]): JSLogger = js.native
...
}
Following the scala.js docs for writing importing modules I expected the object name (Log4JavaScript in this case) would have to match the exported symbol name in order for the binding to work. However, the top level symbol in log4javascript.js is log4javascript
. After experimenting, it seems the scala object name makes no difference for the binding. It binds correctly no matter what I name the scala top level object.
Can someone explain what relationship exists, if any, between the scala object/class/def/val names and the names in the javascript module when using the 'Namespace' arg to JSImport?
According to the scala.js docs, it seems I should be able to provide the actual name of the js object (I also tried "Log4JavaScript")
@JSImport("log4javascript", "log4javascript")
@js.native
object SomeOtherName extends js.Object {
def resetConfiguration(): Unit = js.native
def getLogger(name:js.UndefOr[String]): JSLogger = js.native
...
}
However, this fails to bind. I will get a runtime error when I try to access any of the member functions.
Log4JavaScript.resetConfiguration()
Uncaught TypeError: Cannot read property 'resetConfiguration' of undefined
Can someone explain why this doesn't work?
log4javascript also defines some classes inside the scope of log4javascript
. When the lib was included as a web jar the definition looked like:
@js.native
@JSGlobal("log4javascript.AjaxAppender")
class AjaxAppender(url:String) extends Appender {
def addHeader(header:String, value:String):Unit = js.native
}
After switching to npm I had to put the class definition inside the top level object:
@js.native
trait Appender extends js.Object {
...
}
@JSImport("log4javascript", "log4javascript")
@js.native
object Log4JavaScript extends js.Object {
...
class AjaxAppender(url: String) extends Appender {
def addHeader(name: String, value: String): Unit = js.native
}
...
}
This seems sensible, but from the scala.js docs it seems like it should have been possible to define it this way outside of the top level object
@JSImport("log4javascript", "log4javascript.AjaxAppender")
@js.native
class AjaxAppender(url: String) extends Appender {
def addHeader(name: String, value: String): Unit = js.native
}
However, this also fails to bind. Could someone explain the correct way to define the class as above? Or is the definition nested inside the Log4JavaScript
object the only correct way to do it?