Search code examples
three.jsscala.jsfacadetranspilerscalajs-bundler

New Scala.js facade for Three.js -> "Cannot find module "THREE""


As https://github.com/antonkulaga/threejs-facade is heavily outdated I tried an approach like: https://github.com/Katrix-/threejs-facade and would like to create a facade for the new three.js library.

I am by no means a JS expert, nor am I a Scala.js expert, so odds are I am doing something really dumb.

After another question I am using this sbt-scalajs-bundler and sbt-web-scalajs-bundler

My build.sbt looks like this:

lazy val client = (project in file("modules/client"))
  .enablePlugins(ScalaJSBundlerPlugin, ScalaJSWeb) // ScalaJSBundlerPlugin automatically enables ScalaJSPlugin
  .settings(generalSettings: _*)
  .settings(
    name := "client"
    //, scalaJSModuleKind := ModuleKind.CommonJSModule // ScalaJSBundlerPlugin implicitly sets moduleKind to CommonJSModule enables ScalaJSPlugin
   ,jsDependencies += ProvidedJS / "three.min.js"
  )

lazy val server = (project in file("modules/server"))
  .enablePlugins(PlayScala, WebScalaJSBundlerPlugin)
  .settings(generalSettings: _*)
  .settings(
    name := "server"
    ,scalaJSProjects := Seq(client)
    ,pipelineStages in Assets := Seq(scalaJSPipeline)
    //,pipelineStages := Seq(digest, gzip)
    ,compile in Compile := ((compile in Compile) dependsOn scalaJSPipeline).value
  )

three.min.js is in the resources-folder of my client project.

One part of the Facade is e.g.

@js.native
@JSImport("THREE", "Scene")
class Scene extends Object3D {

and I want to use it like this: val scene = new Scene. On scala.js side this actually compiles just fine, but when I run it I get:

Error: Cannot find module "THREE"

in the browser and I wonder why. It's called like this in three.min.js after all.

Now I tried providing and serving the three.min.js file from the server side as well, because I thought that maybe it was just missing at runtime, but no, that does not seem to be the cause.

So now I wonder what am I doing wrong here?

Just to clarify: Rest of transpiled js works just fine, if I do not export any usage of the Facade!


Solution

  • As explained in this part of Scala.js documentation, @JSImport is interpreted by the compiler as a JavaScript module import.

    When you use the CommonJSModule module kind (which is the case when you enable the ScalaJSBundlerPlugin), this import is translated into the following CommonJS import:

    var Scene = require("THREE").Scene;
    

    This annotation only tells how your Scala code will be interfaced with the JS world, but it tells nothing about how to resolve the dependency that provides the THREE module.


    With scalajs-bundler you can define how to resolve JS dependencies from the NPM registry by adding the following setting to your client project:

    npmDependencies += "three" -> "0.84.0"
    

    (And note that you can’t use jsDependencies to resolve these modules with @JSImport)

    Also, note that the correct CommonJS import to use three.js is "three" instead of "THREE", so your @JSImport annotation should look like the following:

    @JSImport("three", "Scene")
    

    Alternatively, if you don’t want to resolve your dependencies from the NPM registry, you can supply your CommonJS module as a resource file. Just put it under the src/main/resources/Scene.js and refer to it in the @JSImport as follows:

    @JSImport("./Scene", "Scene")
    

    You can see a working example here.