Skip to content

Commit

Permalink
Merge pull request #646 from sjrd/scalajs-1.17.0
Browse files Browse the repository at this point in the history
Announcing Scala.js 1.17.0, and add doc for the Wasm backend.
  • Loading branch information
sjrd authored Sep 28, 2024
2 parents 66972b8 + 08d2baf commit 41707f6
Show file tree
Hide file tree
Showing 8 changed files with 287 additions and 1 deletion.
2 changes: 1 addition & 1 deletion _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ colors: #in hex code if not noted else

### VERSIONS ###
versions:
scalaJS: 1.16.0
scalaJS: 1.17.0
scalaJSBinary: 1
scalaJS06x: 0.6.33
scalaJS06xBinary: 0.6
Expand Down
2 changes: 2 additions & 0 deletions _data/doc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
url: /doc/project/module.html
- text: JavaScript Environments
url: /doc/project/js-environments.html
- text: Emitting WebAssembly
url: /doc/project/webassembly.html
- text: Cross-building
url: /doc/project/cross-build.html
- text: Testing
Expand Down
1 change: 1 addition & 0 deletions _data/library/versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@
- 1.12.0
- 1.13.0
- 1.16.0
- 1.17.0
142 changes: 142 additions & 0 deletions _posts/news/2024-09-28-announcing-scalajs-1.17.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
---
layout: post
title: Announcing Scala.js 1.17.0
category: news
tags: [releases]
permalink: /news/2024/09/28/announcing-scalajs-1.17.0/
---


We are excited to announce the release of Scala.js 1.17.0!

This release comes with a brand new, shiny, experimental WebAssembly backend.
You can now, under certain conditions, take your existing Scala.js application and compile it to WebAssembly instead.

There were also some bug fixes.
Despite the abnormally long release cycle (v1.16.0 was released 6 months ago), the only external bug report came in 3 weeks ago.
As far as we can tell, nobody was blocked waiting for a bugfix for this long.

Read on for more details.

<!--more-->

## Getting started

If you are new to Scala.js, head over to [the tutorial]({{ BASE_PATH }}/tutorial/).

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

Bug reports can be filed [on GitHub](https://github.com/scala-js/scala-js/issues).

## Release notes

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.

This is a **minor** release:

* It is backward binary compatible with all earlier versions in the 1.x series: libraries compiled with 1.0.x through 1.16.x can be used with 1.17.0 without change.
* It is *not* forward binary compatible with 1.16.x: libraries compiled with 1.17.0 cannot be used with 1.16.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.16.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.

## Enhancements with compatibility concerns

### Changes to the IR and linker APIs

For tooling authors who directly manipulate the IR and linker APIs, there have been some breaking changes in that area.
This is in line with our version policy for the linker APIs.

The most likely changes you may hit are:

* The reference types in the IR, such as `ClassType` and `ArrayType`, now have a `nullable: Boolean` flag.
There is also a new type `AnyNotNullType`.
* The `NewArray` node does not accept multiple dimensions anymore.
If you want to emit a multi-dimensional array creation, emit a call to `java.lang.reflect.Array.newInstance` instead.

## Enhancements

### Experimental WebAssembly backend

Starting with this release, Scala.js ships with an *experimental* WebAssembly backend.
Under some conditions, you may use it as a drop-in replacement for the usual JavaScript backend.

#### Minimal setup

You can set it up as follows:

{% highlight scala %}
// Emit ES modules with the Wasm backend
scalaJSLinkerConfig := {
scalaJSLinkerConfig.value
.withExperimentalUseWebAssembly(true) // use the Wasm backend
.withModuleKind(ModuleKind.ESModule) // required by the Wasm backend
},

// Configure Node.js (at least v22) to support the required Wasm features
jsEnv := {
val config = NodeJSEnv.Config()
.withArgs(List(
"--experimental-wasm-exnref", // required
"--experimental-wasm-imported-strings", // optional (good for performance)
"--turboshaft-wasm", // optional, but significantly increases stability
))
new NodeJSEnv(config)
},
{% endhighlight %}

Make sure `node -v` reports at least v22.0.0.
If not, install a newer version.

You are then set up to `run` and `test` your codebase with the WebAssembly backend from sbt.

#### Limitations

Note that the WebAssembly backend *silently ignores* all the `@JSExport` and `@JSExportAll` annotations.
It is never possible to call methods of Scala classes from JavaScript, which includes `toString()`, even through string concatenation.
JavaScript code may still call all public members of JavaScript classes (classes that inherit from `js.Any`).
Moreover, `@JSExportTopLevel` is supported, as well as all the other `@JS...` annotations.

The WebAssembly backend does not yet support emitting multiple modules.
The module split style must be set to `ModuleSplitStyle.FewestModules` (which is the default).
Moreover, the codebase must not contain any feature that require emitting multiple modules: `@JSExportToplevel` annotations with several module names, or `js.dynamicImport`.
We expect to lift that limitation in the future.

Other than that, we expect the WebAssembly backend to support all Scala.js semantics.
Please report any issues you may find.

Stack traces are currently suboptimal.

#### Use in browsers

If you want to use it in browsers, you will need:

* For Firefox: in `about:config`, enable `javascript.options.wasm_exnref`.
Also make sure to *disable* `javascript.options.wasm_js_string_builtins`: Firefox has two issues with it that break Scala.js ([1919901](https://bugzilla.mozilla.org/show_bug.cgi?id=1919901) and [1920337](https://bugzilla.mozilla.org/show_bug.cgi?id=1920337))
* For Chrome: in `chrome://flags/`, enable ["Experimental WebAssembly"](chrome://flags/#enable-experimental-webassembly-features).

#### More information

Read more detailed information about [the WebAssembly backend in the docs]({{ BASE_PATH }}/doc/project/webassembly.html).

## Miscellaneous

### New JDK APIs

This release adds support for the following JDK methods:

* In `java.lang.Character`: `codePointAt`, `codePointBefore`, `codePointCount` and `offsetByCodePoints`
* In `java.util.concurrent.ConcurrentHashMap`: `forEach`, `forEachKey` and `forEachValue`

### Unicode version

The Unicode database used by the methods of `java.lang.Character` was updated to Unicode v15.0.

## Bug fixes

Among others, the following bugs have been fixed in 1.17.0:

* [#5026](https://github.com/scala-js/scala-js/issues/5026) Output .js file names can be too long on Windows, esp. for non-ASCII class names.
* [#5044](https://github.com/scala-js/scala-js/issues/5044) `jl.reflect.Array.newInstance()` does not throw the `IllegalArgumentException`s it is supposed to.

You can find the full list [on GitHub](https://github.com/scala-js/scala-js/issues?q=is%3Aissue+milestone%3Av1.17.0+is%3Aclosed).
1 change: 1 addition & 0 deletions assets/badges/scalajs-1.17.0.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions doc/all-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ title: All previous versions of the Scala.js API

## All previous versions of the API

### Scala.js 1.17.0
* [1.17.0 scalajs-library]({{ site.production_url }}/api/scalajs-library/1.17.0/scala/scalajs/js/index.html)
* [1.17.0 scalajs-test-interface]({{ site.production_url }}/api/scalajs-test-interface/1.17.0/)
* [1.17.0 scalajs-javalib-intf]({{ site.production_url }}/api/scalajs-javalib-intf/1.17.0/)
* [1.17.0 scalajs-ir]({{ site.production_url }}/api/scalajs-ir/1.17.0/org/scalajs/ir/index.html)
* [1.17.0 scalajs-linker-interface]({{ site.production_url }}/api/scalajs-linker-interface/1.17.0/org/scalajs/linker/interface/index.html) ([Scala.js version]({{ site.production_url }}/api/scalajs-linker-interface-js/1.17.0/org/scalajs/linker/interface/index.html))
* [1.17.0 scalajs-linker]({{ site.production_url }}/api/scalajs-linker/1.17.0/org/scalajs/linker/index.html) ([Scala.js version]({{ site.production_url }}/api/scalajs-linker-js/1.17.0/org/scalajs/linker/index.html))
* [1.17.0 scalajs-test-adapter]({{ site.production_url }}/api/scalajs-sbt-test-adapter/1.17.0/org/scalajs/testing/adapter/index.html)
* [1.17.0 sbt-scalajs]({{ site.production_url }}/api/sbt-scalajs/1.17.0/#org.scalajs.sbtplugin.package)

### Scala.js 1.16.0
* [1.16.0 scalajs-library]({{ site.production_url }}/api/scalajs-library/1.16.0/scala/scalajs/js/index.html)
* [1.16.0 scalajs-test-interface]({{ site.production_url }}/api/scalajs-test-interface/1.16.0/)
Expand Down
1 change: 1 addition & 0 deletions doc/internals/version-history.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ title: Version history

## Version history of Scala.js

- [1.17.0](/news/2024/09/28/announcing-scalajs-1.17.0/)
- [1.16.0](/news/2024/03/19/announcing-scalajs-1.16.0/)
- [1.15.0](/news/2023/12/29/announcing-scalajs-1.15.0/)
- [1.14.0](/news/2023/09/25/announcing-scalajs-1.14.0/)
Expand Down
129 changes: 129 additions & 0 deletions doc/project/webassembly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
layout: doc
title: Emitting JavaScript modules
---

# Experimental WebAssembly backend

Since Scala.js 1.17.0, there is an *experimental* WebAssembly backend (Wasm for short).
Under some conditions, it is designed to be a *drop-in* replacement for the usual JavaScript backend.

## Experimental status

Being experimental means that:

* The Wasm backend may be removed in a future *minor* version of Scala.js (or moved to a separate plugin).
* Future versions of Scala.js may emit Wasm that requires *newer* versions of Wasm engines, dropping support for older engines.

However, we do *not* expect the the Wasm backend to be any less *correct* than the JS backend, modulo the limitations listed below.
Feel free to report any issue you may experience with the same expectations as for the JS backend.

Non-functional aspects, notably performance and size of the generated code, may not be as good as the JS backend for now.
The backend is also not incremental yet, which means a slower `fastLinkJS` in the development cycle.

## Requirements

The Wasm backend emits code with the following requirements:

* A JavaScript host (i.e., we do not currently generate standalone Wasm)
* A Wasm engine with support for:
* Wasm 3.0
* Wasm GC
* Exception handling, including the latest `exnref`-based variant
* The `ESModule` module kind (see [emitting modules](./module.html))
* Strict floats (which is the default since Scala.js 1.9.0; non-strict floats are deprecated)

Supported engines include Node.js 22, Chrome and Firefox, all using some experimental flags (see below).

## Language semantics

The Wasm backend is nothing but an alternative backend for the Scala.js language.
Its semantics are the same as Scala.js-on-JS, including JavaScript interoperability features, with one big limitation.

### Limitation: no `@JSExport` support

Due to the current feature set of Wasm, it is not possible to implement the semantics of `@JSExport`.
Therefore, the Wasm backend currently *silently ignores* all `@JSExport` and `@JSExportAll` annotations (the latter being sugar for many `@JSExport`s).

This limitation has the following consequences:

* JavaScript code cannot call `@JSExport`ed methods of Scala classes.
* Since that includes `toString()`, instances of Scala classes cannot be converted to string from JavaScript, including as part of string concatenation.

(String concatenation *in Scala.js code* is supported.)

### Limitation: no support for emitting multiple modules

The WebAssembly backend does not yet support emitting multiple modules.
The module split style must be set to `ModuleSplitStyle.FewestModules` (which is the default).
Moreover, the codebase must not contain any feature that require emitting multiple modules: `@JSExportToplevel` annotations with several module names, or `js.dynamicImport`.
We expect to lift that limitation in the future.

## Minimal setup

The following sbt setup enables the Wasm backend and configures flags for Node.js 22.

{% highlight scala %}
// Emit ES modules with the Wasm backend
scalaJSLinkerConfig := {
scalaJSLinkerConfig.value
.withExperimentalUseWebAssembly(true) // use the Wasm backend
.withModuleKind(ModuleKind.ESModule) // required by the Wasm backend
},

// Configure Node.js (at least v22) to support the required Wasm features
jsEnv := {
val config = NodeJSEnv.Config()
.withArgs(List(
"--experimental-wasm-exnref", // required
"--experimental-wasm-imported-strings", // optional (good for performance)
"--turboshaft-wasm", // optional, but significantly increases stability
))
new NodeJSEnv(config)
},
{% endhighlight %}

Compared to a setup with ES modules with the JS backend, the above setup should be a drop-in replacement.
The backend emits ES modules with the same layout and interface as those produced by the JS backend.

## Supported engines

Here are some engines known to support enough Wasm features.

### Node.js 22

As mentioned above, Node.js 22 and above requires the following flags:

* `--experimental-wasm-exnref`: required
* `--experimental-wasm-imported-strings`: optional (good for performance)
* `--turboshaft-wasm`: optional, bug significantly increases stability

### Chrome

In `chrome://flags/`, enable ["Experimental WebAssembly"](chrome://flags/#enable-experimental-webassembly-features).

### Firefox

In `about:config`, enable `javascript.options.wasm_exnref`.

Make sure to *disable* `javascript.options.wasm_js_string_builtins`.
Firefox has two issues with it that break Scala.js ([1919901](https://bugzilla.mozilla.org/show_bug.cgi?id=1919901) and [1920337](https://bugzilla.mozilla.org/show_bug.cgi?id=1920337)).

## Performance

Performance of the generated code is currently a hit-or-miss.
Depending on the codebase, it may be several times faster or slower than the JS backend.

Further work on improving performance is ongoing.
Keep in mind that performance work on the Wasm backend is a few months old, compared to a decade of optimizations in the JS backend.

## Code size

The generated code size of the Wasm backend is currently about twice as big as the JS backend in `fullLink` mode.

We hope to significantly improve code size in the future by using [`wasm-opt`](https://github.com/WebAssembly/binaryen), a Wasm-to-Wasm optimizer.

## Implementation details

Looking for some implementation details of how we compile Scala.js to WebAssembly?
Start with [the technical readme of the Wasm backend](https://github.com/scala-js/scala-js/tree/main/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter#readme).

0 comments on commit 41707f6

Please sign in to comment.