ReScript has come a long way, maybe it's time to switch from TypeScript?

Josh Derocher-Vlk - May 28 - - Dev Community

ReScript, the "Fast, Simple, Fully Typed JavaScript from the Future", has been around for awhile now. The name "ReScript" came into existence in 2020, but the project has a history going back to 2016 under the combo of Reason and BuckleScript. The name change came about as the goal of BuckleScript shifted to try and create a language that was a part of the JavaScript ecosystem; JavaScript is the only build target, support for all of the features JavaScript devs expect to have like async/await syntax, and an easy to use standard library for JavaScript's built in functions.

Since the re-brand to ReScript the team behind the project have been very busy. If you look around the internet to see what people have to say about ReScript there are some things that people didn't like or JavaScript features that were missing. Many of these have been addressed, so lets take a look at some of these complaints and features that have been recently addressed. Hopefully you'll give ReScript another look as a strong alternative to TypeScript

My sources of past issues

To create this list of issues I have poured over the official ReScript forum, Hacker News, Reddit, and Twitter. I tried not to omit anything and I wasn't just looking for issues that have been solved. I'll call out anything that hasn't changed that might still be a pain point for some people, but hopefully you'll see the massive steps forward the language has taken in the past 4 years.

I have to understand Reason, BuckleScript, and OCaml before I can use ReScript

You don't need to know anything about Reason, BuckleScript or OCaml to use ReScript.

ReScript's history comes from these languages and tools, but today it's very much it's own thing that works with the standard JS ecosystem of NPM, PNPM, Yarn, or whatever package manager you want to use. The JS it spits out can be used by any build tool like Vite, ESBuild, Webpack, etc... ReScript is fully part of the JS ecosystem and you don't need to know any other languages, new build tools, or new package managers.

Ocaml is still a wonderful language if you want to look into it, and Reason is still going strong as an alternate syntax for OCaml. With either OCaml or Reason you can compile to native code, or use the continuation of BuckleScript now called Melange.

Should I use Belt.Array, Js.Array, or Js.Array2?

Due to ReScript's history being rooted in Ocaml and Reason, it came with some baggage from those languages. Not bad baggage, but Ocaml isn't JavaScript and had it's own patterns and ways of doing things that made sense for that language. Trying to match these patterns to JavaScript was cumbersome when the goal of Reason was to compile to native code or JavaScript while maintaining full interop with OCaml. ReScript now focuses only on the land of JavaScript, so it has a new Core library that is meant to be easy to pick up and understand. The previous standard Js library is being deprecated and will soon be removed from the language, and Belt will evolve into a more fully featured utility library like Lodash that will be optional to use.

The answer to the above question is to just use Array.

let t = [1, 2, 3]->Array.map(n => n + 1)
Enter fullscreen mode Exit fullscreen mode

Core is a separate library for now, but the roadmap is to include it in the language directly before the end of the year.

Lack of async, await, and Promises are cumbersome

Native support for async/await syntax was added in ReScript 10.1 in Early 2023. The new Core library I mentioned above also introduced a streamlined way to work with Promises.

// async/await
let fn = async () => {
  try {
    let result = await getData()
    Console.log(result)
  } catch {
  | err => Console.error(err)
  }
}

// Promises
let fn = () => {
  getData()
  ->Promise.thenResolve(result => Console.log(result))
  ->Promise.catch(err => {
    Console.error(err)
    Promise.resolve()
  })
}
Enter fullscreen mode Exit fullscreen mode

Lack of community types

When ReScript was young you would have to write your own bindings and types for most existing JS libraries before you could use them. There are now hundreds of NPM packages that have bindings and documentation on how to use your favorite JS libraries with ReScript. Bindings exist for React (which also still has first class support directly in the ReScript compiler), Node, Jotai, React Query, React Hook Form, AntD, Material UI, Bun, Vitest, Graphqljs and many others!

There are even some incredible ReScript first libraries out there that really shine and can take advantage of ReScript powerful type system and compiler, like rescript-relay.

/* Avatar.res */
module UserFragment = %relay(`
  fragment Avatar_user on User {
    firstName
    lastName
    avatarUrl
  }
`)

@react.component
let make = (~user) => {
  // this is fully typed!
  let userData = UserFragment.use(user)

  <img
    className="avatar"
    src=userData.avatarUrl
    alt={
      userData.firstName ++ " "
      userData.lastName
    }
  />
}
Enter fullscreen mode Exit fullscreen mode

ReScript also has amazing tooling for parsing JSON into types. Here's an example of rescript-schema:

@schema
type cat = {
  name: string,
  color: string,
}

let _ =
  %raw(`{ name: 'fluffy', color: 'orange' }`)
  ->S.parseWith(catSchema) // catSchema is generated automatically!
  ->Console.log
Enter fullscreen mode Exit fullscreen mode

I don't understand how to use existing JavaScript libraries

While there is a growing number of community bindings and libraries written in ReScript, at some point you'll need to dig into writing your own bindings. Thankfully, the documentation is excellent and you can reference one of the many existing NPM packages for ideas.

Just this week I needed to use a function from path-to-regexp and it took me just a few minutes to create bindings and the function I needed.

  type t = string => option<{.}>

  type m = {
    path: string,
    params: {.}, // this means any object
  }

  @unboxed // @unboxed is a way to have a variant type that compiles to simple javascript without a runtime object
  type isMatch =
    | Yes(option<m>) // the JS for this is just an object of type m
    | @as(false) No // the JS for this is just 'false'

  type match = string => isMatch

  @module("path-to-regexp") // this is the actual binding using the types I made above
  external match: string => match = "match"

  let make = url => {
    let fn = match(url) // and now I can call it!
    path =>
      switch fn(path) {
      | Yes(t) => t->Option.map(t => t.params)
      | No => None
      }
  }
Enter fullscreen mode Exit fullscreen mode

I can't have 2 files with the same name?

This hasn't changed, and due to the way the module system works it never will. It might feel weird at first for JS devs, but this isn't that weird in other languages. You get used to it very quickly.

Why would I pick this over TypeScript?

I have another article that dives into this: Tired of Typescript? Check out ReScript!

The short answer is that you would pick ReScript if:

  • You want a strong, sound type system (no any types!)
  • You want types, but you don't want to write type annotations (the compiler just knows!)
  • You want only the "Good Parts" of JavaScript
  • You want a blazing fast compiler and fast Intellisense in VSCode, even when your repo is massive
  • You want amazing next gen language features like pattern matching, Option and Result types, and powerful variant types.

Not convinced?

Leave a comment! I'm more than happy to discuss things in more detail and answer questions about ReScript! I've been using it for a few projects over the past 4 years, and it's hard to switch back to writing TypeScript when I need to. It's been incredible to watch the language grow and evolve and it should be considered as an alternative to TypeScript.

Cover Photo by Patrick Tomasso on Unsplash

. . . . . . . . .