By default, the -fastopt.js and -fullopt.js files produced by Scala.js are top-level scripts, and their @JSExported stuff are sent to the global scope.With modern JavaScript toolchains, we typically write modules instead, which import and export things from other modules.You can configure Scala.js to emit a JavaScript module instead of a top-level script.

Two kinds of modules are supported: CommonJS modules (traditional module system of Node.js) and ECMAScript modules.They are enabled with the following sbt settings:

  1. // ECMAScript
  2. scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.ESModule) }
  3. // CommonJS
  4. scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }

Important: Using this setting is incompatible with the (deprecated) setting persistLauncher := true.It is also basically incompatible with jsDependencies; use scalajs-bundler instead.

When emitting a module, @JSExportTopLevels are really exported from the Scala.js module.Moreover, you can use top-level @JSImport to import native JavaScript stuff from other JavaScript module.

For example, consider the following definitions:

  1. import scala.scalajs.js
  2. import scala.scalajs.js.annotation._
  3. @js.native
  4. @JSImport("bar.js", "Foo")
  5. class JSFoo(val x: Int) extends js.Object
  6. // @ScalaJSDefined // required for Scala.js 0.6.x, unless using -P:scalajs:sjsDefinedByDefault
  7. @JSExportTopLevel("Babar")
  8. class Foobaz(x: String) extends js.Object {
  9. val inner = new JSFoo(x.length)
  10. def method(y: String): Int = x + y
  11. }

Once compiled under ModuleKind.ESModule, the resulting module would be equivalent to the following JavaScript module:

  1. import { Foo as JSFoo } from "bar.js";
  2. class Foobaz {
  3. constructor(x) {
  4. this.x = x;
  5. this.inner = new JSFoo(x.length);
  6. }
  7. method(y) {
  8. return this.x + y;
  9. }
  10. }
  11. export { Foobaz as Babar };

With ModuleKind.CommonJSModule, it would instead be equivalent to:

  1. var bar = require("bar.js");
  2. class Foobaz {
  3. constructor(x) {
  4. this.x = x;
  5. this.inner = new bar.Foo(x.length);
  6. }
  7. method(y) {
  8. return this.x + y;
  9. }
  10. }
  11. exports.Babar = Foobaz;

ES modules and Node.js

Support for ECMAScript modules is still experimental in Node.js.To run and test a Scala.js application or library using ES modules with Node.js, you will need the following additional settings:

  1. jsEnv := {
  2. new org.scalajs.jsenv.NodeJSEnv(
  3. org.scalajs.jsenv.NODEJSEnv.Config()
  4. .withArguments(List("--experimental-modules"))
  5. )
  6. }
  7. artifactPath in (proj, Compile, fastOptJS) :=
  8. (crossTarget in (proj, Compile)).value / "myproject.mjs"
  9. artifactPath in (proj, Test, fastOptJS) :=
  10. (crossTarget in (proj, Test)).value / "myproject-test.mjs"

The first setting is required to enable the support of ES modules in Node.js.The other two make sure that the JavaScript produced have the extension .mjs, which is required for Node.js to interpret them as ES modules.

The support for running and testing ES modules with Node.js is experimental, as the support of ES modules by Node.js is itself experimental.Things could change in future versions of Node.js and/or Scala.js.