|
1 | 1 | ---
|
2 | 2 | layout: doc
|
3 |
| -title: Emitting a JavaScript module |
| 3 | +title: Emitting JavaScript modules |
4 | 4 | ---
|
5 | 5 |
|
| 6 | +## Basic Module Setup |
| 7 | + |
6 | 8 | By default, the `-fastopt.js` and `-fullopt.js` files produced by Scala.js are top-level *scripts*, and their `@JSExport`ed stuff are sent to the global scope.
|
7 | 9 | With modern JavaScript toolchains, we typically write *modules* instead, which import and export things from other modules.
|
8 | 10 | You can configure Scala.js to emit a JavaScript module instead of a top-level script.
|
@@ -76,6 +78,138 @@ class Foobaz {
|
76 | 78 | exports.Babar = Foobaz;
|
77 | 79 | {% endhighlight %}
|
78 | 80 |
|
| 81 | +## Module splitting |
| 82 | + |
| 83 | +When emitting modules, the Scala.js linker is able to split its output into multiple JavaScript modules (i.e. files). |
| 84 | + |
| 85 | +There are several reasons to split the JavaScript output into multiple files: |
| 86 | + |
| 87 | +* Share code between different parts of an application (e.g. user/admin interfaces). |
| 88 | +* Create smaller files to minimize changes for incremental downstream tooling. |
| 89 | +* Load parts of a large app progressively (not supported yet, see [#4201](https://github.com/scala-js/scala-js/issues/4201)). |
| 90 | + |
| 91 | +The Scala.js linker can split a full Scala.js application automatically based on: |
| 92 | + |
| 93 | +* The entry points (top-level exports and module initializers) |
| 94 | +* The split style (fewest modules or smallest modules) |
| 95 | + |
| 96 | +### Entry Points |
| 97 | + |
| 98 | +Scala.js-generated code has two different kinds of entry points: |
| 99 | + |
| 100 | +* [Top-level exports]({{ site.production_url }}/doc/interoperability/export-to-javascript.html): Definitions to be called from external JS code. |
| 101 | +* [Module initializers](./building.html): Code that gets executed when a module is imported (i.e., main methods). |
| 102 | + |
| 103 | +The Scala.js linker determines how to group entry points into different (public) modules by using their assigned `moduleID`. |
| 104 | +The default `moduleID` is `"main"`. |
| 105 | + |
| 106 | +The `moduleID` of a top-level export can be specified using the [`moduleID` parameter]({{ site.production_url }}/api/scalajs-library/latest/scala/scalajs/js/annotation/JSExportTopLevel.html#%3Cinit%3E(name:String,moduleID:String):scala.scalajs.js.annotation.JSExportTopLevel). |
| 107 | +The `moduleID` of a `ModuleInitializer` can be specified by the [`withModuleID` method]({{ site.production_url }}/api/scalajs-linker-interface/latest/org/scalajs/linker/interface/ModuleInitializer.html#withModuleID(moduleID:String):org.scalajs.linker.interface.ModuleInitializer). |
| 108 | + |
| 109 | +**Example**: |
| 110 | + |
| 111 | +Say you have the following `App.scala` and `build.sbt`: |
| 112 | + |
| 113 | +{% highlight scala %} |
| 114 | +package my.app |
| 115 | + |
| 116 | +import scala.collection.mutable |
| 117 | +import scala.scalajs.js.annotation._ |
| 118 | + |
| 119 | +// Separate objects to allow for splitting. |
| 120 | + |
| 121 | +object AppA { |
| 122 | + @JSExportTopLevel(name = "start", moduleID = "a") |
| 123 | + def a(): Unit = println("hello from a") |
| 124 | +} |
| 125 | + |
| 126 | +object AppB { |
| 127 | + private val x = mutable.Set.empty[String] |
| 128 | + |
| 129 | + @JSExportTopLevel(name = "start", moduleID = "b") |
| 130 | + def b(): Unit = { |
| 131 | + println("hello from b") |
| 132 | + println(x) |
| 133 | + } |
| 134 | + |
| 135 | + def main(): Unit = x.add("something") |
| 136 | +} |
| 137 | +{% endhighlight %} |
| 138 | + |
| 139 | +{% highlight scala %} |
| 140 | +import org.scalajs.linker.interface.ModuleInitializer |
| 141 | + |
| 142 | +scalaJSModuleInitializers in Compile += { |
| 143 | + ModuleInitializer.mainMethod("my.app.AppB", "main").withModuleID("b") |
| 144 | +} |
| 145 | +{% endhighlight %} |
| 146 | + |
| 147 | +This would generate two public modules `a.js` / `b.js`. |
| 148 | +`a.js` will export a method named `start` that calls `AppA.a`. |
| 149 | +`b.js` will export a method named `start` that calls `AppB.b`. |
| 150 | +Further, importing `b.js` will call `AppB.main`. |
| 151 | + |
| 152 | +Note that there is no public module `main.js`, because there is no entry point using the default `moduleID`. |
| 153 | + |
| 154 | +### Module Split Styles |
| 155 | + |
| 156 | +So far, we have seen how public modules can be configured. |
| 157 | +Based on the public modules, the Scala.js linker generates internal modules for the shared code between the public modules. |
| 158 | +Unlike public modules, internal modules may not be imported by user code. |
| 159 | +Doing so is undefined behavior and subject to change at any time. |
| 160 | + |
| 161 | +The linker generates internal modules automatically based on the dependency graph of the code and `moduleSplitStyle`. |
| 162 | +You can change it as follows: |
| 163 | + |
| 164 | +{% highlight scala %} |
| 165 | +import org.scalajs.linker.interface.ModuleSplitStyle |
| 166 | +scalaJSLinkerConfig ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules)) |
| 167 | +{% endhighlight %} |
| 168 | + |
| 169 | +There are currently two module split styles: `FewestModules` and `SmallestModules`. |
| 170 | + |
| 171 | +#### `FewestModules` |
| 172 | + |
| 173 | +Create as few modules as possible without including unnecessary code. |
| 174 | +This is the default. |
| 175 | + |
| 176 | +In the example above, this would generate: |
| 177 | + |
| 178 | +* `a.js`: public module, containing `AppA` and the export of `start`. |
| 179 | +* `b.js`: public module, containing `AppB`, `mutable.Set`, the export of `start` and the call to `AppB.main` |
| 180 | +* `a-b.js`: internal module, Scala.js core and the implementation of `println`. |
| 181 | + |
| 182 | +This also works for more than two public modules, creating intermediate shared (internal) modules as necessary. |
| 183 | + |
| 184 | +#### `SmallestModules` |
| 185 | + |
| 186 | +Create modules that are as small as possible. |
| 187 | +The smallest unit of splitting is a Scala class. |
| 188 | + |
| 189 | +Using this mode typically results in an internal module per class with the exception of classes that have circular dependencies: these are put into the same module to avoid a circular module dependency graph. |
| 190 | + |
| 191 | +In the example above, this would generate: |
| 192 | + |
| 193 | +* `a.js`: public module, containing the export of `start`. |
| 194 | +* `b.js`: public module, containing the export of `start` and the call to `AppB.main` |
| 195 | +* many internal small modules (~50 for this example), approximately one per class. |
| 196 | + |
| 197 | +Generating many small modules can be useful if the output of Scala.js is further processed by downstream JavaScript bundling tools. |
| 198 | +In incremental builds, they will not need to reprocess the entire Scala.js-generated .js file, but instead only the small modules that have changed. |
| 199 | + |
| 200 | +### Linker Output |
| 201 | + |
| 202 | +With module splitting, the set of files created by the linker is not known at invocation time. |
| 203 | +To support this new requirement, the linker output is configured as follows: |
| 204 | + |
| 205 | +* A directory where all files go: `scalaJSLinkerOutputDirectory` |
| 206 | +* Patterns for output file names: `outputPatterns` on `scalaJSLinkerConfig`. |
| 207 | + |
| 208 | +Both of these have reasonable defaults and usually do not need to be changed. |
| 209 | +The exception is file extensions for Node.js, for that, see the next section. |
| 210 | + |
| 211 | +In order to make sense of the files in the directory, the linking tasks (`fastLinkJS`/`fullLinkJS`) return a [`Report`]({{ site.production_url }}/api/scalajs-linker-interface/latest/org/scalajs/linker/interface/Report.html) listing the public modules and their file names. |
| 212 | + |
79 | 213 | ## ES modules and Node.js
|
80 | 214 |
|
81 | 215 | Node.js needs explicit signaling that a module is an ECMAScript module (the default is CommonJS).
|
|
0 commit comments