You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: doc/project/module.md
+119-12
Original file line number
Diff line number
Diff line change
@@ -78,19 +78,20 @@ class Foobaz {
78
78
exports.Babar = Foobaz;
79
79
{% endhighlight %}
80
80
81
-
## Module splitting
81
+
## Module Splitting
82
82
83
83
When emitting modules, the Scala.js linker is able to split its output into multiple JavaScript modules (i.e. files).
84
84
85
85
There are several reasons to split the JavaScript output into multiple files:
86
86
87
87
* Share code between different parts of an application (e.g. user/admin interfaces).
88
+
* Load parts of a large app progressively
88
89
* 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
90
91
91
The Scala.js linker can split a full Scala.js application automatically based on:
92
92
93
-
* The entry points (top-level exports and module initializers)
93
+
* Entry points (top-level exports and module initializers)
94
+
* Dynamic import boundaries (calls to `js.dynamicImport`)
94
95
* The split style (fewest modules or smallest modules)
95
96
96
97
### Entry Points
@@ -151,15 +152,72 @@ Further, importing `b.js` will call `AppB.main`.
151
152
152
153
Note that there is no public module `main.js`, because there is no entry point using the default `moduleID`.
153
154
154
-
### Module Split Styles
155
+
### Dynamic Imports
156
+
157
+
Warning: Dynamic imports in Scala.js 1.4.0 are affected by [#4386](https://github.com/scala-js/scala-js/issues/4386), see the issue for a workaround.
155
158
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.
159
+
Dynamic imports allow a Scala.js application to be loaded in multiple steps to reduce initial loading time.
160
+
To defer loading of a part of your Scala.js application to a later point in time, use [`js.dynamicImport`]({{ site.production_url }}/api/scalajs-library/latest/scala/scalajs/js/index.html#dynamicImport[A](body:=%3EA):scala.scalajs.js.Promise[A]):
val resultPromise: js.Promise[Int] = js.dynamicImport {
179
+
new HeavyFeature().doHeavyFeature(input)
180
+
}
181
+
for (result <- resultPromise.toFuture)
182
+
updateUIWithOutput(result)
183
+
}
184
+
185
+
private def updateUIWithOutput(i: Int): Unit = ???
186
+
}
187
+
{% endhighlight %}
188
+
189
+
The `js.dynamicImport` method has the following signature:
190
+
191
+
{% highlight scala %}
192
+
def dynamicImport[A](body: => A): js.Promise[A]
193
+
{% endhighlight %}
194
+
195
+
Semantically, it will evaluate `body` asynchronously and return a Promise of the result.
196
+
More importantly, it acts as a border for the Scala.js linker to split out a module that will be dynamically loaded.
197
+
The above program would generate
198
+
* a public module `main.js` containing `onClick` and its direct dependencies
199
+
* an internal module `MyApp$$anon$1.js` containing `HeavyFeature`
200
+
* an internal module `main-MyApp$$anon$1.js` containing common dependencies of `main.js` and `MyApp$$anon$1.js`.
201
+
202
+
Internal modules allow the Scala.js linker to split code internally.
158
203
Unlike public modules, internal modules may not be imported by user code.
159
204
Doing so is undefined behavior and subject to change at any time.
160
205
161
-
The linker generates internal modules automatically based on the dependency graph of the code and `moduleSplitStyle`.
162
-
You can change it as follows:
206
+
In the example above, the `js.dynamicImport` is replaced by `import("./MyApp$$anon$1.js")`, followed by an invocation of the main entry point in `MyApp$$anon$1.js` (the `body` passed to `js.dynamicImport`).
207
+
Therefore, when `main.js` is loaded, we do not need to load, nor download `MyApp$$anon$1.js`.
208
+
It will only be loaded the first time `onClick` is actually called.
209
+
This reduces the initial download time for users.
210
+
211
+
Dynamic imports and entry points can be arbitrarily combined.
212
+
213
+
### Module Split Styles
214
+
215
+
So far, we have seen how public modules and dynamic import boundaries can be defined.
216
+
217
+
Based on these, the Scala.js linker automatically uses the dependency graph of the code to generate appropriate internal modules.
218
+
219
+
However, there are still choices involved.
220
+
They can be configured with the `moduleSplitStyle`:
@@ -170,33 +228,82 @@ There are currently two module split styles: `FewestModules` and `SmallestModule
170
228
171
229
#### `FewestModules`
172
230
173
-
Create as few modules as possible without including unnecessary code.
231
+
Create as few modules as possible
232
+
* while respecting dynamic import boundaries and
233
+
* without including unnecessary code.
234
+
174
235
This is the default.
175
236
176
-
In the example above, this would generate:
237
+
In the entry points example above, this would generate:
177
238
178
239
*`a.js`: public module, containing `AppA` and the export of `start`.
179
240
*`b.js`: public module, containing `AppB`, `mutable.Set`, the export of `start` and the call to `AppB.main`
180
241
*`a-b.js`: internal module, Scala.js core and the implementation of `println`.
181
242
182
243
This also works for more than two public modules, creating intermediate shared (internal) modules as necessary.
183
244
245
+
The dynamic import example above already assumes this module split style so a module listing is omitted.
246
+
184
247
#### `SmallestModules`
185
248
186
249
Create modules that are as small as possible.
187
-
The smallest unit of splitting is a Scala class.
250
+
The smallest unit of splitting is a Scala class (see [Splitting Granularity](#splitting-granularity) below for more).
188
251
189
252
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
253
191
-
In the example above, this would generate:
254
+
In the entry points example above, this would generate:
192
255
193
256
*`a.js`: public module, containing the export of `start`.
194
257
*`b.js`: public module, containing the export of `start` and the call to `AppB.main`
195
258
* many internal small modules (~50 for this example), approximately one per class.
196
259
260
+
In the dynamic import example, this would generate:
261
+
*`main.js`: public module, containing the export of `onClick`.
262
+
* many internal small modules (~150 for this example), approximately one per class.
263
+
197
264
Generating many small modules can be useful if the output of Scala.js is further processed by downstream JavaScript bundling tools.
198
265
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
266
267
+
### Splitting Granularity
268
+
269
+
Scala.js only splits modules along class boundaries.
270
+
It is important to be aware of this when structuring your application to avoid unnecessary grouping.
271
+
272
+
For example, the following structure likely leads to poor splitting (if `FeatureN`s are not always used together):
273
+
274
+
{% highlight scala %}
275
+
object UI {
276
+
def renderFeature1(): Unit = ???
277
+
def renderFeature2(): Unit = ???
278
+
def renderFeature3(): Unit = ???
279
+
}
280
+
281
+
object Calc {
282
+
def calcFeature1(): Unit = ???
283
+
def calcFeature2(): Unit = ???
284
+
def calcFeature3(): Unit = ???
285
+
}
286
+
{% endhighlight %}
287
+
288
+
For better splitting, group code that belongs to the same feature:
289
+
290
+
{% highlight scala %}
291
+
object Feature1 {
292
+
def render(): Unit = ???
293
+
def calc(): Unit = ???
294
+
}
295
+
296
+
object Feature2 {
297
+
def render(): Unit = ???
298
+
def calc(): Unit = ???
299
+
}
300
+
301
+
object Feature3 {
302
+
def render(): Unit = ???
303
+
def calc(): Unit = ???
304
+
}
305
+
{% endhighlight %}
306
+
200
307
### Linker Output
201
308
202
309
With module splitting, the set of files created by the linker is not known at invocation time.
0 commit comments