ReScript 9.0
Featuring a new external stdlib configuration, some syntax improvements and a small breaking change for nested records.
Introduction
We are happy to announce ReScript 9.0!
ReScript is a robustly typed language that compiles to efficient and human-readable JavaScript. It comes with one of the fastest build toolchains and offers first class support for interoperating with ReactJS and other existing JavaScript code.
Use npm
to install the newest 9.0.1 release with the following command:
npm install bs-platform@9.0.1
You can also try our new release in the Online Playground.
In this post we will highlight the most notable changes. The full changelog for this release can be found here.
Compiler Improvements
New External Stdlib Configuration
This is a long-awaited feature request.
Our compiler comes with a set of stdlib modules (such as Belt
, Pervasives
, etc.) for core functionality. Compiled ReScript code relies on the JS runtime version of these stdlib modules.
In previous versions, users couldn't ship their compiled JS code without defining a package.json
dependency on bs-platform
. Whenever a ReScript developer wanted to publish a package just for pure JS consumption / lean container deployment, they were required to use a bundler to bundle up their library / stdlib code, which made things way more complex and harder to understand.
To fix this problem, we introduced an external-stdlib
configuration that allows specifying a pre-compiled stdlib npm package (@rescript/std
). More details on how to use that feature can be found in our External Stdlib documentation.
Less Bundle Bloat when Adding ReScript
With each release we keep a close eye on generating code that is optimized for tree-shaking. We also believe that we reached a milestone where ReScript reliably produces output that has almost no impact on our final JS bundle-sizes (this is what we call our "zero-cost" philosophy).
The bundled code is almost ReScript runtime free because our generated library code fits the tree-shaking principle really well. Tools like esbuild
can easily drop unnecessary code and make sure that the final code stays lean.
We made a small demo repo and added the precompiled JS bundles to demonstrate what we've achieved. Check it out!
Improved Code Generation for Pattern Matching
We fine-tuned our pattern matching engine to optimize the JS output even more. Here is an example of a pretty substantial optimization, based on this issue:
REStype test =
| NoArg
| AnotherNoArg
| OtherArg(int)
let test = x =>
switch x {
| NoArg => true
| _ => false
}
The snippet above will compile to the following JS output:
function test(x){
return x === 0
}
As you can see, the 9.0 compiler removes all the unnecessary typeof
checks!
This is possible because our optimizer will try to analyze several predicates and get rid of redundant ones. More diffs can be found here.
Another important improvement is that we fixed the pattern match offset issue, which lead to the consequence that magic numbers will not be generated for complex pattern matches anymore.
For those interested in the details, here is a representative diff resulting from this cleanup:
DIFFfunction is_space(param){
- var switcher = param - 9 | 0;
- if (switcher > 4 || switcher < 0) {
- return switcher == 23 ;
+ if (param > 13 || param < 9) {
+ return param === 32;
} else {
- return switcher !== 2;
+ return param != 11;
}
}
Syntax Improvements
when
-> if
Starting from 9.0, when
clauses within a switch
statement will automatically convert to the if
keyword instead.
The when
keyword is deprecated. The syntax will continue supporting it and the formatter will automatically convert to if
, for a pain-free upgrade.
Cleaner Polyvariant Syntax
Polyvariants with invalid identifier names (e.g. names including hypens -
), don't require any special escaping syntax anymore:
We introduced this change to allow easier interop with existing JS string enums. In pure ReScript code, we'd still recommend our users to stick with valid identifier names instead (e.g. easeIn
instead of ease-in
).
Breaking Changes
This release comes with a minor breaking change that shouldn't have much impact on the upgrade of existing codebases.
Nested Records within Objects
Previously, if you wrote {"user": {age: 10}}
, the inner record was interpreted as an object instead of a record ({"user": {"age": 10}}
); this is a byproduct of some internal interop transformation details; with the ReScript syntax, this went from understandable to confusing, so we're changing it so that the inner record is indeed now treated as a record. This is an obvious fix, but a breaking change if you were accidentally leveraging that nested record as object.
Here is a code example before and after the change. Note how the user
record secretly turns into a ReScipt object in the previous version:
More discussions on this change can be found here.
Closing Note
We only highlighted a few user-facing features, but there are also some pretty interesting internal changes happening right now.
For example, we are tinkering with the idea on using WASM to replace Camlp4, and we are also working on a generalized visitor pattern that doesn't require objects.
We will discuss these topics in a separate development post, but we are already excited about the new possibilities this will bring within the compiler toolchain.
Happy Hacking!