Announcing Scala.js 1.20.1

Sep 6, 2025.

We are pleased to announce the release of Scala.js 1.20.1!

This is technically a hotfix patch release for 1.20.0, which was discovered to be severely broken, and was therefore never announced. These release notes therefore present it as a “minor release” compared to 1.19.0.

This release mainly comes with many performance improvements to the WebAssembly and JavaScript backends alike.

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.

Read on for more details.

Getting started

If you are new to Scala.js, head over to the tutorial.

If you need help with anything related to Scala.js, you may find our community in #scala-js on Discord and on Stack Overflow.

Bug reports can be filed on GitHub.

Release notes

If upgrading from Scala.js 0.6.x, make sure to read the release notes of Scala.js 1.0.0 first, as they contain a host of important information, including breaking changes.

This is a minor release, compared to 1.19.0:

  • 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.x without change.
  • It is not forward binary compatible with 1.19.x: libraries compiled with 1.20.x cannot be used with 1.19.x or earlier.
  • 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).

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.

Changes with compatibility concerns

Drop the performance.webkitNow fallback of System.nanoTime()

System.nanoTime() normally uses the JavaScript API performance.now() under the hood if it is available, and Date.now() otherwise. Until Scala.js 1.19.0, it also used performance.webkitNow() as possible fallback. Since browsers supporting webkitNow() have been supporting the official performance.now() method for more than 10 years now, we have dropped that fallback.

This changed allowed to improve the run-time performance of System.nanoTime(). It may degrade its precision on the old browsers that supported webkitNow() but not now().

Enhancements

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. Typical examples include polyfills, as illustrated in the documentation of scala.scalajs.LinkingInfo:

if (esVersion >= ESVersion.ES2018 || featureTest())
  useES2018Feature()
else
  usePolyfill()

which gets folded away to nothing but

useES2018Feature()

when optimizing for ES2018+.

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. 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:

def pow(x: Double, y: Double): Double = {
  if (esVersion >= ESVersion.ES2016) {
    (x.asInstanceOf[js.Dynamic] ** y.asInstanceOf[js.Dynamic]) // does not link!
      .asInstanceOf[Double]
  } else {
    Math.pow(x, y)
  }
}

Scala.js 1.20.1 introduces scala.scalajs.LinkingInfo.linkTimeIf, a conditional branch that is guaranteed by spec to be resolved at link-time. 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.

import scala.scalajs.LinkingInfo.linkTimeIf

def pow(x: Double, y: Double): Double = {
  linkTimeIf(esVersion >= ESVersion.ES2016) {
    // this branch is only *linked* if we are targeting ES2016+
    (x.asInstanceOf[js.Dynamic] ** y.asInstanceOf[js.Dynamic])
      .asInstanceOf[Double]
  } {
    Math.pow(x, y)
  }
}

The condition of linkTimeIf must be a link-time constant expression. It can only contain:

  • int and boolean literals and constants,
  • boolean operators (!, && and ||),
  • int comparison operators (such as == and <),
  • link-time properties in the LinkingInfo object, notably esVersion, productionMode and isWebAssembly.

Performance improvements

Scala.js 1.20.1 brings a number of performance improvements compared to 1.19.0.

For both JavaScript and WebAssembly:

  • Many low-level methods in java.lang.Integer, Long, Float, Double, Math and System have been micro-optimized.
  • Scala Ranges have a faster initialization path (this particular enhancement is also coming in Scala 2.13.17 for the JVM).

For JavaScript:

  • Long performance was significantly enhanced, notably additive operations, conversions to strings and conversions to floating-point numbers.

For WebAssembly, performance improvements in the following areas:

  • Varargs, when compiled with Scala.js 1.20.0+ on Scala 2, and with the upcoming Scala 3.8.0+ on Scala 3
  • java.util.ArrayList, ArrayDeque, PriorityQueue and java.util.concurrent.CopyOnWriteArrayList
  • java.util.RedBlackTree.fromOrdered
  • Startup time in the presence of large arrays of constants
  • Generally, fewer hops across the Wasm-JS boundary

Miscellaneous

New JDK APIs

This release adds support for the following JDK methods:

  • In java.lang.Math:
    • multiplyFull
    • multiplyHigh
    • unsignedMultiplyHigh
  • In java.lang.Double:
    • doubleToRawLongBits
  • In java.lang.Float:
    • floatToRawIntBits
  • In java.lang.Integer and java.lang.Long:
    • compress
    • expand

Improved debugging experience on Wasm

The Wasm backend now emits more debugging information: the names of types, object fields, local variables and global variables. This improves the debugging experience when targeting WebAssembly.

Bug fixes

The following bugs have been fixed in 1.20.0 and 1.20.1:

  • #5159 Regression in v1.19.0: AssertionError with module splitting on and optimizer off.
  • #5165 Wasm: try..finally with non-nullable reference type produces invalid code.
  • #5208 doubleToLongBits can observe non-canonical NaN bit patterns.
  • #5231 AssertionError: rolled-back RuntimeLong inlining (regression in 1.20.0).

You can find the full list on GitHub.