|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: Announcing Scala.js 1.3.0 |
| 4 | +category: news |
| 5 | +tags: [releases] |
| 6 | +permalink: /news/2020/10/16/announcing-scalajs-1.3.0/ |
| 7 | +--- |
| 8 | + |
| 9 | + |
| 10 | +We are excited to announce the release of Scala.js 1.3.0! |
| 11 | + |
| 12 | +This release brings one of the most awaited features for Scala.js: module splitting support! |
| 13 | +It is now possible to split the generated .js file into multiple modules, to optimize download size in multi-page applications or speed up incremental bundling. |
| 14 | + |
| 15 | +In addition, this release contains a number of new methods and classes in the JDK implementation, among which the higher-order methods of the `java.util` collections, and the locale-sensitive overloads of `String.toLowerCase`, `toUpperCase` and `format`. |
| 16 | + |
| 17 | +Read on for more details. |
| 18 | + |
| 19 | +<!--more--> |
| 20 | + |
| 21 | +## Getting started |
| 22 | + |
| 23 | +If you are new to Scala.js, head over to [the tutorial]({{ BASE_PATH }}/tutorial/). |
| 24 | + |
| 25 | +If you need help with anything related to Scala.js, you may find our community [on Gitter](https://gitter.im/scala-js/scala-js) and [on Stack Overflow](https://stackoverflow.com/questions/tagged/scala.js). |
| 26 | + |
| 27 | +Bug reports can be filed [on GitHub](https://github.com/scala-js/scala-js/issues). |
| 28 | + |
| 29 | +## Release notes |
| 30 | + |
| 31 | +If upgrading from Scala.js 0.6.x, make sure to read [the release notes of Scala.js 1.0.0]({{ BASE_PATH }}/news/2020/02/25/announcing-scalajs-1.0.0/) first, as they contain a host of important information, including breaking changes. |
| 32 | + |
| 33 | +This is a **minor** release: |
| 34 | + |
| 35 | +* It is backward binary compatible with all earlier versions in the 1.x series: libraries compiled with 1.0.x through 1.2.x can be used with 1.3.0 without change. |
| 36 | +* It is *not* forward binary compatible with 1.2.x: libraries compiled with 1.3.0 cannot be used with 1.2.x or earlier. |
| 37 | +* It is *not* entirely backward source compatible: it is not guaranteed that a codebase will compile *as is* when upgrading from 1.2.x (in particular in the presence of `-Xfatal-warnings`). |
| 38 | + |
| 39 | +As a reminder, libraries compiled with 0.6.x cannot be used with Scala.js 1.x; they must be republished with 1.x first. |
| 40 | + |
| 41 | +## Known source breaking changes |
| 42 | + |
| 43 | +### `js.Promise.then` |
| 44 | + |
| 45 | +The result type of `js.Promise.then` was changed from `js.Thenable` to `js.Promise`. |
| 46 | +This is unlikely to cause any issue in most cases, since `js.Promise` extends `js.Thenable`. |
| 47 | +It might cause compilation errors in some rare cases due to type inference, or if you declare a subclass of `js.Promise`. |
| 48 | + |
| 49 | +## Module splitting |
| 50 | + |
| 51 | +### Quickstart |
| 52 | + |
| 53 | +First, instead of `fastOptJS` / `fullOptJS`, use `fastLinkJS` / `fullLinkJS`. |
| 54 | +The outputs of those commands will be in a subdirectory like `project/target/scala-2.13/project-fastopt/`, instead of as a single file `.../scala-2.13/project-fastopt.js`. |
| 55 | +You can then create different entry points and/or generate as many small modules as possible using the following setups. |
| 56 | + |
| 57 | +#### For different entry points |
| 58 | + |
| 59 | +Set the `moduleID` for your top-level exports and/or module initializers explicitly (a module initializer is basically a main method for the module). |
| 60 | +The default `moduleID` is `"main"`. |
| 61 | + |
| 62 | +{% highlight scala %} |
| 63 | +@JSExportTopLevel(name = "startAdmin", moduleID = "admin") |
| 64 | +def startAdmin(): Unit = ??? |
| 65 | +{% endhighlight %} |
| 66 | + |
| 67 | +{% highlight scala %} |
| 68 | +import org.scalajs.linker.interface.ModuleInitializer |
| 69 | +scalaJSModuleInitializers in Compile += { |
| 70 | + ModuleInitializer.mainMethod("my.app.admin.Main", "main") |
| 71 | + .withModuleID("admin") |
| 72 | +} |
| 73 | +{% endhighlight %} |
| 74 | + |
| 75 | +Everything with the same `moduleID` will go into the same entry point module. |
| 76 | + |
| 77 | +#### For many small modules |
| 78 | + |
| 79 | +By default, module splitting genereates as few modules as possible. |
| 80 | +In some cases, generates as many modules as small as possible is preferable, which can be configured with: |
| 81 | + |
| 82 | +{% highlight scala %} |
| 83 | +import org.scalajs.linker.interface.ModuleSplitStyle |
| 84 | +scalaJSLinkerConfig ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules)) |
| 85 | +{% endhighlight %} |
| 86 | + |
| 87 | +### What is Module Splitting? |
| 88 | + |
| 89 | +With module splitting, the Scala.js linker splits its output into multiple JavaScript modules (i.e. files). |
| 90 | +Both ES6 modules (`ModuleKind.ESModule`) and CommonJS modules (`ModuleKind.CommonJSModule`) are supported. |
| 91 | + |
| 92 | +There are several reasons to split JavaScript output into multiple files: |
| 93 | + |
| 94 | +* Share code between different parts of an application (e.g. user/admin interfaces). |
| 95 | +* Create smaller files to minimize changes for incremental downstream tooling. |
| 96 | +* Load parts of a large app progressively (not supported yet, see [#4201](https://github.com/scala-js/scala-js/issues/4201)). |
| 97 | + |
| 98 | +The Scala.js linker can split a full Scala.js application automatically based on: |
| 99 | + |
| 100 | +* The entry points (top-level exports and module initializers) |
| 101 | +* The split style (fewest modules or smallest modules) |
| 102 | + |
| 103 | +### Entry Points |
| 104 | + |
| 105 | +Scala.js-generated code has two different kinds of entry points: |
| 106 | + |
| 107 | +* Top level exports: Definitions to be called from external JS code. |
| 108 | +* Module initializers: Code that gets executed when a module is imported (i.e., main methods). |
| 109 | + |
| 110 | +The Scala.js linker determines how to group entry points into different (public) modules by using their assigned `moduleID`. |
| 111 | +The default `moduleID` is `"main"`. |
| 112 | + |
| 113 | +The `moduleID` of a top-level export can be specified using the `moduleID` parameter. |
| 114 | +The `moduleID` of a `ModuleInitializer` can be specified by the `withModuleID` method. |
| 115 | + |
| 116 | +**Example**: |
| 117 | + |
| 118 | +Say you have the following `App.scala` and `build.sbt`: |
| 119 | + |
| 120 | +{% highlight scala %} |
| 121 | +package my.app |
| 122 | + |
| 123 | +import scala.collection.mutable |
| 124 | +import scala.scalajs.js.annotation._ |
| 125 | + |
| 126 | +// Separate objects to allow for splitting. |
| 127 | + |
| 128 | +object AppA { |
| 129 | + @JSExportTopLevel(name = "start", moduleID = "a") |
| 130 | + def a(): Unit = println("hello from a") |
| 131 | +} |
| 132 | + |
| 133 | +object AppB { |
| 134 | + private val x = mutable.Set.empty[String] |
| 135 | + |
| 136 | + @JSExportTopLevel(name = "start", moduleID = "b") |
| 137 | + def b(): Unit = { |
| 138 | + println("hello from b") |
| 139 | + println(x) |
| 140 | + } |
| 141 | + |
| 142 | + def main(): Unit = x.add("something") |
| 143 | +} |
| 144 | +{% endhighlight %} |
| 145 | + |
| 146 | +{% highlight scala %} |
| 147 | +import org.scalajs.linker.interface.ModuleInitializer |
| 148 | + |
| 149 | +scalaJSModuleInitializers in Compile += { |
| 150 | + ModuleInitializer.mainMethod("my.app.AppB", "main").withModuleID("b") |
| 151 | +} |
| 152 | +{% endhighlight %} |
| 153 | + |
| 154 | +This would generate two public modules `a.js` / `b.js`. |
| 155 | +`a.js` will export a method named `start` that calls `AppA.a`. |
| 156 | +`b.js` will export a method named `start` that calls `AppB.b`. |
| 157 | +Further, importing `b.js` will call `AppB.main`. |
| 158 | + |
| 159 | +Note that there is no public module `main.js`, because there is no entry point using the default `moduleID`. |
| 160 | + |
| 161 | +### Module Split Styles |
| 162 | + |
| 163 | +So far, we have seen how public modules can be configured. |
| 164 | +Based on the public modules, the Scala.js linker generates internal modules for the shared code between the public modules. |
| 165 | +Unlike public modules, internal modules may not be imported by user code. |
| 166 | +Doing so is undefined behavior and subject to change at any time. |
| 167 | + |
| 168 | +The linker generates internal modules automatically based on the dependency graph of the code and `moduleSplitStyle`. |
| 169 | +You can change it as follows: |
| 170 | + |
| 171 | +{% highlight scala %} |
| 172 | +import org.scalajs.linker.interface.ModuleSplitStyle |
| 173 | +scalaJSLinkerConfig ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules)) |
| 174 | +{% endhighlight %} |
| 175 | + |
| 176 | +There are currently two module split styles: `FewestModules` and `SmallestModules`. |
| 177 | + |
| 178 | +#### `FewestModules` |
| 179 | + |
| 180 | +Create as few modules as possible without including unnecessary code. |
| 181 | +This is the default. |
| 182 | + |
| 183 | +In the example above, this would generate: |
| 184 | + |
| 185 | +* `a.js`: public module, containing `AppA` and the export of `start`. |
| 186 | +* `b.js`: public module, containing `AppB`, `mutable.Set`, the export of `start` and the call to `AppB.main` |
| 187 | +* `a-b.js`: internal module, Scala.js core and the implementation of `println`. |
| 188 | + |
| 189 | +This also works for more than two public modules, creating intermediate shared (internal) modules as necessary. |
| 190 | + |
| 191 | +#### `SmallestModules` |
| 192 | + |
| 193 | +Create modules that are as small as possible. |
| 194 | +The smallest unit of splitting is a Scala class. |
| 195 | + |
| 196 | +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. |
| 197 | + |
| 198 | +In the example above, this would generate: |
| 199 | + |
| 200 | +* `a.js`: public module, containing the export of `start`. |
| 201 | +* `b.js`: public module, containing the export of `start` and the call to `AppB.main` |
| 202 | +* many internal small modules (~50 for this example), approximately one per class. |
| 203 | + |
| 204 | +Generating many small modules can be useful if the output of Scala.js is further processed by downstream JavaScript bundling tools. |
| 205 | +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. |
| 206 | + |
| 207 | +### Linker Output |
| 208 | + |
| 209 | +With module splitting, the set of files created by the linker is not known at invocation time. |
| 210 | +To support this new requirement, the linker output is configured as follows: |
| 211 | + |
| 212 | +* A directory where all files go: `scalaJSLinkerOutputDirectory` |
| 213 | +* Patterns for output file names: `outputPatterns` on `scalaJSLinkerConfig`. |
| 214 | + |
| 215 | +Both of these have reasonable defaults and usually do not need to be changed. |
| 216 | +The exception is file extensions. |
| 217 | +If you need to produce `*.mjs` files for Node.js, use: |
| 218 | + |
| 219 | +{% highlight scala %} |
| 220 | +import org.scalajs.linker.interface.OutputPatterns |
| 221 | +scalaJSLinkerConfig ~= (_.withOutputPatterns(OutputPatterns.fromJSFile("%s.mjs"))) |
| 222 | +{% endhighlight %} |
| 223 | + |
| 224 | +In order to make sense of the files in the directory, linking returns a `Report` listing the public modules and their file names. |
| 225 | + |
| 226 | +### sbt backwards compatibility |
| 227 | + |
| 228 | +Since the `fastOptJS` / `fullOptJS` keys/tasks assume that linking will produce a single file, we had to introduce two new keys/tasks for linking: `fastLinkJS` / `fullLinkJS`. |
| 229 | +These tasks return the `Report` instead of an individual `File`. |
| 230 | + |
| 231 | +In order to ensure backwards compatibility, the `fastOptJS` / `fullOptJS` tasks now invoke `fastLinkJS` / `fullLinkJS` respectively and copy the produced files to their target location. |
| 232 | +However, this only works if the linker produced a single public module. |
| 233 | +So with actual module splitting, `fastOptJS` / `fullOptJS` will fail. |
| 234 | + |
| 235 | +The `run` and `test` tasks now depend on `fastLinkJS` / `fullLinkJS` (depending on the `scalaJSStage`) and load the public module with `moduleID="main"` (they fail if no such module exists). |
| 236 | +This does not change their behavior for existing builds but allows running and testing with module splitting enabled. |
| 237 | + |
| 238 | +## Miscellaneous |
| 239 | + |
| 240 | +### New JDK APIs |
| 241 | + |
| 242 | +This release contains a significant amount of additions in the JDK APIs that we support, notably thanks to contributions by [@er1c](https://github.com/er1c), [@ekrich](https://github.com/ekrich) and [@exoego](https://github.com/exoego). |
| 243 | + |
| 244 | +New interface definitions in `java.util.function.*`: |
| 245 | + |
| 246 | +* `BiConsumer`, `Supplier`, `Function`, `BiFunction`, `UnaryOperator`, `BinaryOperator` and `BiPredicate` |
| 247 | +* Specializations of `Supplier`, and `Predicate` |
| 248 | + |
| 249 | +New classes: |
| 250 | + |
| 251 | +* `java.lang.Character.UnicodeBlock` |
| 252 | +* `java.util.StringTokenizer` |
| 253 | +* `java.io.CharArrayWriter` |
| 254 | +* `java.io.CharArrayReader` |
| 255 | + |
| 256 | +Methods with fixed behavior to comply with the JDK specification: |
| 257 | + |
| 258 | +* In `java.lang.Character`: |
| 259 | + * `toLowerCase(Char)` and `toUpperCase(Char)` |
| 260 | +* In `java.lang.String`: |
| 261 | + * `compareTo` |
| 262 | + * `equalsIgnoreCase` |
| 263 | + * `compareToIgnoreCase` |
| 264 | + |
| 265 | +New methods in existing classes and interfaces (some are only available when compiling on a recent enough JDK): |
| 266 | + |
| 267 | +* In `java.lang.String`: |
| 268 | + * `repeat` (JDK 11+) |
| 269 | +* In `java.lang.Character`: |
| 270 | + * `toLowerCase(codePoint: Int)` and `toUpperCase(codePoint: Int)` |
| 271 | + * `toTitleCase(ch: Char)` and `toTitleCase(codePoint: Int)` |
| 272 | + * `highSurrogate` and `lowSurrogate` |
| 273 | + * `hashCode(ch: Char)` |
| 274 | + * `reverseBytes(ch: Char)` |
| 275 | + * `toString(codePoint: Int)` (JDK 11+) |
| 276 | +* Default methods in `java.util.Iterator`: |
| 277 | + * `remove` |
| 278 | + * `forEachRemaining` |
| 279 | +* Default methods in `java.util.List`: |
| 280 | + * `sort` |
| 281 | + * `replaceAll` |
| 282 | +* Default methods in `java.util.Map`: |
| 283 | + * `getOrDefault` |
| 284 | + * `forEach` |
| 285 | + * `replaceAll` |
| 286 | + * `putIfAbsent` |
| 287 | + * `remove(key, value)` |
| 288 | + * `replace(key, oldValue, newValue)` |
| 289 | + * `replace(key, value)` |
| 290 | + * `computeIfAbsent` |
| 291 | + * `computeIfPresent` |
| 292 | + * `compute` |
| 293 | + * `merge` |
| 294 | +* In `java.util.Optional`: |
| 295 | + * `isEmpty` (JDK 11+) |
| 296 | + * `ifPresent` |
| 297 | + * `ifPresentOrElse` (JDK 9+) |
| 298 | + * `filter` |
| 299 | + * `map` |
| 300 | + * `flatMap` |
| 301 | + * `or` (JDK 9+) |
| 302 | + * `orElse` |
| 303 | + * `orElseGet` |
| 304 | + * `orElseThrow(Supplier)` |
| 305 | + * `orElseThrow()` (JDK 10+) |
| 306 | +* In `java.util.Properties`: |
| 307 | + * `load` |
| 308 | + * `save` |
| 309 | + * `store` |
| 310 | + * `list` |
| 311 | + |
| 312 | +Finally, the following `Locale`-sensitive methods have been added, although they will only transitively link if support for `java.util.Locale` APIs is enabled using [scala-java-locales](https://github.com/cquiroz/scala-java-locales): |
| 313 | + |
| 314 | +* In `java.lang.String`: |
| 315 | + * `toLowerCase(Locale)` and `toUpperCase(Locale)` |
| 316 | + * `format(Locale, ...)` |
| 317 | +* In `java.util.Formatter`: |
| 318 | + * constructors with a `Locale` parameter |
| 319 | + * `format(Locale, ...)` |
| 320 | + |
| 321 | +Speaking of locales, we have slightly changed the definition of the default locale of Scala.js. |
| 322 | +Previously, it was specified as `en-US`. |
| 323 | +Starting from Scala.js 1.3.0, it is specified as `Locale.ROOT`. |
| 324 | +This change makes no difference in terms of behavior (only in terms of "spirit"), since all the methods that were previously implemented in Scala.js have the same behavior for `ROOT` than for `en-US`. |
| 325 | + |
| 326 | +Note that it is not possible to change the default locale, as methods that do not take `Locale` arguments are hard-coded for the behavior of `Locale.ROOT` (even when `scala-java-locales` is used). |
| 327 | +To get locale-sensitive behavior, the overloads taking explicit `Locale` arguments must be used. |
| 328 | + |
| 329 | +## Bug fixes |
| 330 | + |
| 331 | +Among others, the following bugs have been fixed in 1.3.0: |
| 332 | + |
| 333 | +* [#4195](https://github.com/scala-js/scala-js/issues/4195) `LinkedHashMap` iteration not empty after `clear` |
| 334 | +* [#4188](https://github.com/scala-js/scala-js/issues/4188) Make `js.Promise.then` return `js.Promise` instead of `js.Thenable` |
| 335 | +* [#4203](https://github.com/scala-js/scala-js/issues/4203) `Matcher.region` not mutating the matcher |
| 336 | +* [#4204](https://github.com/scala-js/scala-js/issues/4204) `Matcher.start()/end()` give incorrect results |
| 337 | +* [#4210](https://github.com/scala-js/scala-js/issues/4210) `java.util.Date.from(Instant)` throws the wrong kind of exception |
| 338 | + |
| 339 | +You can find the full list [on GitHub](https://github.com/scala-js/scala-js/issues?q=is%3Aissue+milestone%3Av1.3.0+is%3Aclosed). |
0 commit comments