DocsPlaygroundBlogCommunityPackages
  • Playground
  • Blog
  • Community
  • Packages
  • X
  • Bluesky
  • GitHub
  • Forum
Nov 25, 2025

Announcing ReScript 12

ReScript 12 arrives with a redesigned build toolchain, a modular runtime, and a wave of ergonomic language features.

ReScript Team
Core Development

Introduction

ReScript 12 is now available. This release completes the multi-year effort to separate the compiler from legacy constraints, refreshes critical developer workflows, and delivers a modern feature set that is grounded in real-world feedback. The headline upgrades include a rewritten build system, a standalone runtime package, unified operator support, JSX preserve mode, and numerous syntax improvements that make ReScript code easier to write and maintain.

Teams upgrading from ReScript 11 should review the highlighted breaking changes and schedule time for migration. The new tooling provides focused commands, better diagnostics, and smaller downloads, while the language additions flatten sharp edges that many of you encountered in larger codebases.

You can find the complete migration guide here.

New Features

New Build System

ReScript 12 ships with the new build system introduced earlier this month in the Reforging the Build System preview. The tooling now relies on a modern architecture that tracks dependencies more precisely, avoids unnecessary recompilations, and integrates with incremental workflows even across monorepo packages.

Improved Standard Library

The new standard library @rescript/core is now included in the compiler runtime. So you can get rid of the dependency on @rescript/core in your projects. To avoid collisions we have named the new internal library just Stdlib, so you can keep using it alongside Core if you cannot migrate yet. There have been tons of additions as well, so you can shrink your Utils files down a bit. Check it out here: Stdlib.

Operator Improvements

Unified Operators

ReScript 12 finalizes the unified operator work introduced earlier this year. Arithmetic operators (+, -, *, /, and the newly added % and **) now work consistently for int, float and bigint, allowing the compiler to infer the correct specialization from the left operand. You can ditch all the +., -., *., and /. now!

In addition you can also now use + for string concatenation, while ++ still works as before.

Bitwise Operators

ReScript now supports F#-style bitwise operators &&& (AND), ||| (OR), ^^^ (XOR), and ~~~ (NOT) for both int and bigint. Legacy OCaml-style bitwise functions such as land, lor, and lsl are deprecated.

Shift Operators

For shift operators there was luckily no conflict in the character space, which means ReScript now supports << (logical left shift), >> (logical right shift) and >>> (unsigned right shift), just like JavaScript.

Dict Literals and Pattern Matching

The language now supports dictionary literals (dict{"foo": "bar"}) that compile to plain JavaScript objects. Dict literals work with variables, multi-line formatting, and optional entries, and they drastically reduce the boilerplate compared to Dict.fromArray. Pattern matching also understands dicts, enabling concise destructuring of JSON payloads and configuration objects. The compiler emits the same optimized JavaScript while preserving ReScript's type guarantees.

RESCRIPT
let user = dict{"name": "Ada", "role": "engineer"} switch user { | dict{"name": name, "role": role} => Console.log2(name, role) | _ => Console.log("missing user metadata") }

Nested Record Types

Nested record definitions remove the need for auxiliary type declarations. You can define optional nested structures directly inside records, or attach record payloads to variant constructors without creating standalone types. The feature supports mutable fields, type parameters, and record spreading, providing better locality for complex domain models with no runtime penalty.

RESCRIPT
type profile = { name: string, extra?: { location: {city: string, country: string}, mutable note: option<string>, }, }

Variant Pattern Spreads

Pattern spreads (| ...SomeVariant as value =>) allow you to reuse handlers for entire subsets of constructors. When a variant extends another variant through spreads, you can match the shared constructors in one branch and delegate to helper functions, keeping exhaustive matches concise even as the hierarchy grows. The compiler enforces subtype relationships and ensures that runtime representations remain compatible.

RESCRIPT
type base = Start | Stop | Pause type extended = | ...base | Resume let handle = (event: extended) => switch event { | ...base as core => Console.log2("base", core) | Resume => Console.log("resuming") }

JSX Preserve Mode

Projects that rely on downstream JSX tooling can enable preserve mode via "jsx": { "version": 4, "preserve": true }. The compiler will emit JSX syntax directly instead of transforming elements to react/jsx-runtime calls, allowing bundlers such as ESBuild, SWC, or Babel to apply their own transforms. This mode keeps JSX readable in the output and maintains compatibility with React Compiler.

React classic mode is no longer supported, so projects must use the JSX v4 transform regardless of preserve mode settings. When preserve mode is disabled, the compiler continues to output the optimized runtime calls you are accustomed to.

Function-Level Directives

The new @directive attribute emits JavaScript directive strings at the top of generated functions. Use it to mark server actions with 'use server', memoized handlers with 'use memo', or any other directive that your framework requires. The attribute works on synchronous and asynchronous functions, supports labeled parameters, and removes the need for %raw blocks. Combined with JSX preserve mode, this enables clean integration with React Server Components and other directive-based runtimes.

Regex Literals

ReScript now understands JavaScript-style regular expression literals (/pattern/flags). They are full equivalents of %re expressions, supporting all ECMAScript flags, Unicode character classes, and sticky searches. The literals compile directly to JavaScript regex objects, so existing APIs like RegExp.exec and RegExp.test continue to work exactly as before, but with clearer syntax and better editor support.

RESCRIPT
let emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/i if emailPattern->RegExp.test("[email protected]") { Console.log("Valid email") } switch emailPattern->RegExp.exec("invalid") { | Some(match) => Console.log(match->RegExp.Result.fullMatch) | None => Console.log("No match") }

Experimental let? Syntax

This release introduces an experimental let? syntax for zero-cost unwrapping of option and result values. The syntax remains behind an opt-in flag while the community evaluates its ergonomics. Refer to the forum discussion at https://forum.rescript-lang.org/t/proposing-new-syntax-for-zero-cost-unwrapping-options-results/6227 for the current proposal, examples, and feedback guidelines.

RESCRIPT
type user = { address?: { city?: string, }, } let userCity = (user: user): option<string> => { let? Some(address) = user.address let? Some(city) = address.city Some(city) }

Platform and Tooling Improvements

Cleaner JavaScript Output

The printer now emits compact arrow functions and streamlines anonymous function expressions, making generated JavaScript easier to audit. These changes preserve semantics while aligning the output with modern JavaScript style.

Internal Architecture Updates

The compiler cleans up its internal abstract syntax tree, removes unused OCaml-era nodes, and tracks async and partial function metadata directly on AST nodes. These changes simplify future feature work and reduce maintenance overhead.

ESM-First Distribution

The rescript npm package declares "type": "module" and ships ESM code across the CLI. Import statements replace CommonJS require usage, improving compatibility with contemporary bundlers and enabling better tree-shaking.

Platform-Specific Binaries

Installer footprints shrink thanks to platform-specific binary packages such as @rescript/darwin-arm64, @rescript/linux-x64, and @rescript/win32-x64. npm installs only the binary that matches your operating system and architecture, delivering substantially faster installs and smaller cache footprints for CI pipelines. The primary rescript package loads the appropriate binary at runtime and surfaces clear error messages if the matching package is missing.

Breaking Changes

Build System

The old build system remains available through rescript-legacy, but active projects should switch to the new commands to benefit from faster feedback loops and clearer output.

Runtime Package Split

The runtime modules were moved from the main rescript npm package to a dedicated @rescript/runtime npm package. It is automatically installed as a dependency of the main rescript package. The new @rescript/runtime package also replaces the standalone runtime package @rescript/std from earlier versions.

API Naming Alignment

APIs that previously ended with Exn now end with OrThrow. Examples include Option.getOrThrow, List.headOrThrow, BigInt.fromStringOrThrow, and JSON.parseOrThrow. The change clarifies that these functions throw JavaScript errors, aligning the naming with the language’s semantics. The deprecated *Exn variants remain available in v12 to ease the transition, and the codemod bundled with the migration tool can perform a mechanical rename. Note that Result.getOrThrow now throws a JavaScript exception, so please update any exception-handling logic that relied on OCaml exception names. We also revamped the API around JS exceptions and errors.

We’ve renamed JS errors to JsError.t to better distinguish them from the Result.Error variant. Since JavaScript allows throwing anything (not only proper Error objects), the previous way of catching JS exceptions was unsafe:

RESCRIPT
let foo = switch myUnsafeJsResult() { | exception Exn.Error(e) => Error(e->Error.message) // ☝️ this will crash if `e` is undefined or null | myRes => Ok(myRes) }

Additionally, the coexistence of Result.Error and the Error module caused confusion.

The new recommended pattern is:

RESCRIPT
let foo = switch myUnsafeJsResult() { | exception JsExn(e) => Error(e->JsExn.message) // ☝️ this is now safe even if `e` is undefined or null | myRes => Ok(myRes) }

Utility helpers for working with JS errors are now in JsError, eg:

RESCRIPT
JsError.throw(JsError.RangeError.make("the payload should be below 16"))

JSX Version Requirement

JSX v3 has been removed. ReScript 12 requires JSX v4 configuration in rescript.json, using the "jsx": { "version": 4 } schema. ReScript React projects must update their configuration before moving to v12. Projects attempting to compile with v3 will receive an explicit error, ensuring that your codebase uses the current transform and associated tooling.

Deprecation of OCaml Compatibility Helpers

The standard library continues its shift away from OCaml-specific aliases. Functions such as succ, pred, abs_float, string_of_int, fst, raise, and the char type are now deprecated. The recommended replacements are the JavaScript-aligned counterparts in Int, Float, Bool, Pair, and related modules, alongside the throw keyword for exceptions. References to the OCaml composition operators (|>, @@) now warn and will be removed in a future version; the ReScript pipe operator -> replaces them. The migration tool highlights deprecated usage, and incremental cleanups are encouraged so your codebase is ready before the next major release.

Acknowledgments

Kick-off of this year's ReScript Retreat in Vienna

Thank you to every contributor, tester, and partner who helped shape ReScript 12. The core team gathered in Vienna earlier this year to map out this release, and your feedback guided every decision. Community pull requests, bug reports, and experiments across the ecosystem gave us the confidence to complete large refactors and deprecations.

Reach Out

Join the conversation on the community forum if you have migration questions or want to share what you build with ReScript 12. Businesses that rely on ReScript can contact the association at https://rescript-association.org/contact to explore support, sponsorship, or collaboration. Funding enables the team to ship features like the new runtime architecture faster, so every contribution, financial or otherwise, helps the language move forward.

We look forward to hearing what you create with ReScript 12.

The ReScript team at the 2025 retreat in Vienna
Want to read more?
Back to Overview

© 2025 The ReScript Project

About
  • Community
  • ReScript Association
Find us on