|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: Announcing Scala.js 1.20.0 |
| 4 | +category: news |
| 5 | +tags: [releases] |
| 6 | +permalink: /news/2025/09/05/announcing-scalajs-1.20.0/ |
| 7 | +--- |
| 8 | + |
| 9 | + |
| 10 | +We are pleased to announce the release of Scala.js 1.20.0! |
| 11 | + |
| 12 | +This release mainly comes with many performance improvements to the WebAssembly and JavaScript backends alike. |
| 13 | + |
| 14 | +As of this writing, the latest versions of Firefox (since v131), Safari (since v18.4) and Chrome (since v137) support all the WebAssembly features required to run Scala.js-on-Wasm. |
| 15 | + |
| 16 | +Read on for more details. |
| 17 | + |
| 18 | +<!--more--> |
| 19 | + |
| 20 | +## Getting started |
| 21 | + |
| 22 | +If you are new to Scala.js, head over to [the tutorial]({{ BASE_PATH }}/tutorial/). |
| 23 | + |
| 24 | +If you need help with anything related to Scala.js, you may find our community [in `#scala-js` on Discord](https://discord.com/invite/scala) and [on Stack Overflow](https://stackoverflow.com/questions/tagged/scala.js). |
| 25 | + |
| 26 | +Bug reports can be filed [on GitHub](https://github.com/scala-js/scala-js/issues). |
| 27 | + |
| 28 | +## Release notes |
| 29 | + |
| 30 | +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. |
| 31 | + |
| 32 | +This is a **minor** release: |
| 33 | + |
| 34 | +* It is backward binary compatible with all earlier versions in the 1.x series: libraries compiled with 1.0.x through 1.19.x can be used with 1.20.0 without change. |
| 35 | +* It is *not* forward binary compatible with 1.19.x: libraries compiled with 1.20.0 cannot be used with 1.19.x or earlier. |
| 36 | +* It is *not* entirely backward source compatible: it is not guaranteed that a codebase will compile *as is* when upgrading from 1.19.x (in particular in the presence of `-Xfatal-warnings`). |
| 37 | + |
| 38 | +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. |
| 39 | + |
| 40 | +## Enhancements |
| 41 | + |
| 42 | +### Link-time conditional branching |
| 43 | + |
| 44 | +Thanks to our optimizer's ability to inline, constant-fold, and then eliminate dead code, we have been able to write link-time conditional branches for a long time. |
| 45 | +Typical examples include polyfills, as illustrated in the documentation of [`scala.scalajs.LinkingInfo`](https://javadoc.io/static/org.scala-js/scalajs-library_2.13/1.19.0/scala/scalajs/LinkingInfo$.html#esVersion:Int): |
| 46 | + |
| 47 | +{% highlight scala %} |
| 48 | +if (esVersion >= ESVersion.ES2018 || featureTest()) |
| 49 | + useES2018Feature() |
| 50 | +else |
| 51 | + usePolyfill() |
| 52 | +{% endhighlight %} |
| 53 | + |
| 54 | +which gets folded away to nothing but |
| 55 | + |
| 56 | +{% highlight scala %} |
| 57 | +useES2018Feature() |
| 58 | +{% endhighlight %} |
| 59 | + |
| 60 | +when optimizing for ES2018+. |
| 61 | + |
| 62 | +However, this only works because both branches can *link* during the initial reachability analysis. We cannot use the same technique when one of the branches would refuse to link in the first place. |
| 63 | +The canonical example is the usage of the JS `**` operator, which does not link below ES2016. The following snippet produces good code when linking for ES2016+, but does not link at all for ES2015: |
| 64 | + |
| 65 | +{% highlight scala %} |
| 66 | +def pow(x: Double, y: Double): Double = { |
| 67 | + if (esVersion >= ESVersion.ES2016) { |
| 68 | + (x.asInstanceOf[js.Dynamic] ** y.asInstanceOf[js.Dynamic]) // does not link! |
| 69 | + .asInstanceOf[Double] |
| 70 | + } { |
| 71 | + Math.pow(x, y) |
| 72 | + } |
| 73 | +} |
| 74 | +{% endhighlight %} |
| 75 | + |
| 76 | +Scala.js 1.20.0 introduces `scala.scalajs.LinkingInfo.linkTimeIf`, a conditional branch that is *guaranteed* by spec to be resolved at link-time. |
| 77 | +Using a `linkTimeIf` instead of the `if` in `def pow`, we can successfully link the fallback branch on ES2015, avoiding the linking issue in the then branch. |
| 78 | + |
| 79 | +{% highlight scala %} |
| 80 | +import scala.scalajs.LinkingInfo.linkTimeIf |
| 81 | + |
| 82 | +def pow(x: Double, y: Double): Double = { |
| 83 | + linkTimeIf(esVersion >= ESVersion.ES2016) { |
| 84 | + // this branch is only *linked* if we are targeting ES2016+ |
| 85 | + (x.asInstanceOf[js.Dynamic] ** y.asInstanceOf[js.Dynamic]) |
| 86 | + .asInstanceOf[Double] |
| 87 | + } { |
| 88 | + Math.pow(x, y) |
| 89 | + } |
| 90 | +} |
| 91 | +{% endhighlight %} |
| 92 | + |
| 93 | +The condition of `linkTimeIf` must be a link-time constant expression. |
| 94 | +It can only contain: |
| 95 | + |
| 96 | +* int and boolean literals and constants, |
| 97 | +* boolean operators (`!`, `&&` and `||`), |
| 98 | +* int comparison operators (such as `==` and `<`), |
| 99 | +* link-time properties in the `LinkingInfo` object, notably `esVersion`, `productionMode` and `isWebAssembly`. |
| 100 | + |
| 101 | +### Performance improvements |
| 102 | + |
| 103 | +Scala.js 1.20.0 brings a number of performance improvements. |
| 104 | + |
| 105 | +For both JavaScript and WebAssembly: |
| 106 | + |
| 107 | +* Many low-level methods in `java.lang.Integer`, `Long`, `Float`, `Double`, `Math` and `System` have been micro-optimized. |
| 108 | +* Scala `Range`s have a faster initialization path (this particular enhancement is also coming in Scala 2.13.17 for the JVM). |
| 109 | + |
| 110 | +For JavaScript: |
| 111 | + |
| 112 | +* `Long` performance was significantly enhanced, notably additive operations, conversions to strings and conversions to floating-point numbers. |
| 113 | + |
| 114 | +For WebAssembly, performance improvements in the following areas: |
| 115 | + |
| 116 | +* Varargs, when compiled with Scala.js 1.20.0+ on Scala 2, and with the upcoming Scala 3.8.0+ on Scala 3 |
| 117 | +* `java.util.ArrayList`, `ArrayDeque`, `PriorityQueue` and `java.util.concurrent.CopyOnWriteArrayList` |
| 118 | + |
| 119 | +## Miscellaneous |
| 120 | + |
| 121 | +### New JDK APIs |
| 122 | + |
| 123 | +This release adds support for the following JDK methods: |
| 124 | + |
| 125 | +* In `java.lang.Math`: |
| 126 | + * `multiplyFull` |
| 127 | + * `multiplyHigh` |
| 128 | + * `unsignedMultiplyHigh` |
| 129 | +* In `java.lang.Double`: |
| 130 | + * `doubleToRawLongBits` |
| 131 | +* In `java.lang.Float`: |
| 132 | + * `floatToRawIntBits` |
| 133 | +* In `java.lang.Integer` and `java.lang.Long`: |
| 134 | + * `compress` |
| 135 | + * `expand` |
| 136 | + |
| 137 | +## Bug fixes |
| 138 | + |
| 139 | +The following bugs have been fixed in 1.20.0: |
| 140 | + |
| 141 | +* [#5159](https://github.com/scala-js/scala-js/issues/5159) Regression in v1.19.0: `AssertionError` with module splitting on and optimizer off. |
| 142 | +* [#5165](https://github.com/scala-js/scala-js/issues/5165) Wasm: `try..finally` with non-nullable reference type produces invalid code. |
| 143 | +* [#5208](https://github.com/scala-js/scala-js/issues/5208) `doubleToLongBits` can observe non-canonical NaN bit patterns. |
| 144 | + |
| 145 | +You can find the full list [on GitHub](https://github.com/scala-js/scala-js/issues?q=is%3Aissue+milestone%3Av1.20.0+is%3Aclosed). |
0 commit comments