Search code examples
scala.jsscalajs-bundler

In Scala.js facades, why does the @js.native annotation preclude the @JSExport one?


Please consider a Scala.js library with a native dependency implemented as pure JavaScript CommonJS module.

The library includes a facade for the JavaScript dependency. As expected, the facade includes a lot of code like:

@JSImport("com", "Foo") @js.native
class Foo extends js.Object { ... }

Unfortunately, the ScalaJS-Bundler bundles Foo in a way that hides it from the global scope. The obvious fix involves adding the @JSExport annotation to the other two, but that results in a compiler error.

Why isn't js.native compatible with JSExport? What would it take to add support for @JSExport on facades?

Is any work-around available now?


Solution

  • @JSExport on top-level classes and objects was deprecated in Scala.js 0.6.15. What you are after is actually @JSExportTopLevel.

    There is no fundamental reason that @JSExportTopLevel is not compatible with @JSImport/@JSGlobal. It isn't because of the 3 following things:

    • supporting it means more work in the entire compiler toolchain to support it,
    • it felt like a rare use case, and
    • there is another way to achieve the same result.

    The other way to achieve the result is simply to export a val storing the result of the import, as follows:

    @js.native
    @JSImport("com", "Foo")
    class Foo extends js.Object { ... }
    
    // 'private' not to pollute the Scala API with this object
    private object Reexports {
      @JSExportTopLevel("Foo") // or another name
      val Foo = js.constructorOf[Foo]
    }
    

    It sure is a bit more verbose if you only re-export one such import, but you can bundle as many as you want in the unique object Reexports.