Compilation and optimization pipeline
- compiling: compile .scala files to one .sjsir file per class using the scalac compiler with the Scala.js compiler plugin.
For libraries, it stops here. The compiled .sjsir files are put together with .class files in the binary jars that can be published to an Ivy or Maven repository.
For the application project, one more mandatory step creates one or three .js files consumable by browsers. Then an optional step can be used to optimize the result.
- linking all the .sjsir files together, including those in transitive
dependencies. This is either one of:
- fastoptimizing: apply a type-aware inter-method global dead code elimination that results in one preoptimized .js file.
- packaging: concatenate blindly all compiled .sjsir files in three big .js files. This used to be the default in earlier versions of Scala.js. Packaging is discouraged and might fall away in future versions since the fast optimization is now faster.
- optimizing (optional, with the Closure Compiler): apply the Closure Compiler to the linked .js file(s).
Compilation is similar to using the Scala compiler daily. .scala source files are compiled with incremental compilation by the Scala compiler. They can depend on external libraries or other projects in the build, as usual.
An .sjsir file contains a Scala.js specific intermediate representation. You may use
scalajsp from the Scala.js
CLI to display it in a human readable form.
The .sjsir files are bundled together with .class files in jars published on Ivy or Maven repositories.
This step completely supports separate compilation and incremental compilation, just like the regular Scala compiler.
The fast optimizer uses information stored by the compilation step in .sjsir to derive a reachability graph from the entry points. Then it produces a single .js file containing all the code that is actually useful (or, since in theory the algorithm computes an over-approximation, all the code that could not be proved to be useless).
In sbt, the fast optimizing task is called
fastOptJS. The result of
fast optimization is typically between 1.5 MB and 2.5 MB.
The fast optimizer can also be called programmatically using the class ScalaJSOptimizer in the Scala.js toolbox.
Packaging is discouraged, since fast optimizing has become faster. This section is for information only, as packaging can be useful for very specific use-cases.
In sbt, the packaging task is called
Decomposition in three files
Concatenating everything every time one changes one source file in the project
takes time, because this is a heavily I/O-bound operation. However, typically
most of the code is concentrated in the external dependencies of the project
(in sbt terminology, i.e., the libraries referenced in
and these tend to change rarely (definitely not in a quick edit-compile-reload
cycle). Next, much less code is located in the internal dependencies, i.e.,
the other projects in the same build referenced via
dependsOn). Finally, the
code that changes on every single dev cycle are in the project itself, which
are the exported products of the project.
The packaging takes advantage of this obversation by producing three .js files
instead of just one:
app-pack-extdeps.js contains the external dependencies,
app-pack-intdeps.js contains the internal dependencies, and
app-pack-app.js contains the
app-pack-extdeps.js is typically very large (the Scala standard library itself
app-pack-app.js has to be repackaged on every single dev cycle, but contains
relatively much fewer code, and so this is fast.
The internal structure of the produced .js files imposes a constraint on the order in which they are packaged. A class must always be packaged after its superclass (and hence, transitively, all its superclasses).
To do so, the .sjsir file contains (among other things), the number of class ancestors of each class. Since a subclass A of a superclass B has at least one more ancestor than B (namely, B), it is sufficient, to guarantee the required ordering, that classes with more ancestors are ordered after classes with less ancestors.
Optimizing using Closure
In sbt, the optimizing task is called
fullOptJS and is applied on the result
fastOptJS. The result optimization is typically between
150 KB and a few hundreds of KB.
The optimizer can also be called programmatically using the class ScalaJSClosureOptimizer in the Scala.js toolbox.