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)
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()
})
}
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
}
/>
}
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
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
}
}
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
Top comments (49)
Regarding your "Why would I pick this over TypeScript?" points:
a
andb
variables as numbers by default when code islet add = (a, b) => a + b
doesn't make sense to me. Calling thisadd()
function with strings is perfectly valid in JS.Don't really see why any TS developer would want to switch to this.
First off, thanks for taking the time to read and leave a comment! I'll answer your points one by one.
You need a secondary tool configured correctly to handle this, and developers can easily by-pass that with a quick ESlint ignore comment. ReScript does not require additional tooling to enforce type safety, and it's not dependent on how you have it configured. Everyone works with the same strong type system.
That is valid code in JS, which of course makes it valid code in TS. In ReScript the
+
operator is only for adding togetherint
. There's+.
forfloat
and++
forstring
. Because the type system is simple everything can only ever be of one type, so function calls and operators can only ever work with one type. This allows it to always correctly know the types based on usage.While you can rely on type inference, it can really slow down the compiler in large projects. Microsoft recommends that you use type annotations to avoid slow downs on large projects. The ReScript compiler will not slow down on large codebases or when you omit annotations.
This again depends on an external tool to enforce, which can be configured in any number of ways. You can also rely on code reviews and establish patterns on your team for what you consider to be the "bad" parts. ReScript doesn't have all of the JS baggage, which means you don't need to work around the footguns of JS or add extra linters.
Yes, it is getting much better, but it's still not "blazing fast". I have project with 32k TypeScript files that takes 2 minutes to run full typechecking with TSC on a cold start, and saving a file with watch mode on takes 4 seconds to type check. A ReScript project with 50k files takes 1 minute to fully compile on a cold start, and when saving it takes under 400 ms to typecheck. It's enough of a difference to greatly improve the feedback loop of local development. I've never once had to wait to see type definitions in VSCode when using ReScript.
Rust is one of the most loved languages by developers, and for good reason. Working with tagged unions, pattern matching, and option and result types make it easier to handle business logic and prevent bugs. I dig more into that here: dev.to/jderochervlk/rescript-rust-...
There are probably a ton of TS devs that are happy with the state of TS, but there are also many devs forced to work with TS coming from other languages that want something better, or maybe they just want a language that works out of the box the same way for everyone using it without the need for external linters and formatters. TS will probably be on top for a long time, but it's nice to have other options available.
OMG! Split that in smaller modules/packages dude! 🤣 ...and then you won't ever have a problem ever again with the typechecking performance as bonus.
Haha we have since broken that up into a monorepo, for better TS and eslint performance. While monorepos can be a nice solution, I don't like that we were forced into it to avoid VSCode crashing on us.
I still can't wrap my head around it. A project with 50k files ...just navigating it must be a nightmare ...and the resulting bundle must be so heavy. What the heck does your software do to be that large?! Just for context, out of curiosity, I checked how many source files the react repo has. It's a monorepo containing 26 packages, totalling ~1600 files. That's more human. You definitely don't have some average project.
It's a project with 40 devs working in one repo. A good chunk of it is probably not even used, but we keep adding to it. This also includes unit tests.
I've worked on larger projects at saas companies in the past, things can get complicated and large real quick.
Man... split that monster ...it's overdue ...40 people on the same repo is too much ...I don't get it
Hey, Google has just 1 repo, so if it works it works.
But seriously, we have a large website that requires a lot of devs to build features at the pace we want, and the teams often work on the same pages. We've tossed around the idea of a poly repo approach, but we'd have to pull in federated microfrontends, which are a headache, and even then devs would still just be locally linking 4 repos together to complete a feature. A monorepo was a good choice for us.
So what's the website requiring this 50k files? Now I'm even more curious of that beast :P
You would be very disappointed lol. Its a lot of logic for a/b testing and landing pages that most people will never see.
Isn't Google's homegrown version-control system a piece of history by now? ...somehow I doubt they have a monorepo in the sense we speak of. I also think they switched to git by now.
They do have a massive amount of tooling, and as far as I know they still have billions of sloc in a single repo. research.google/pubs/why-google-st...
Microsoft has a million files in 1 repo: devblogs.microsoft.com/bharry/the-...
Thank you for taking the time to write such an extensive answer 🙂
After reading your "ReScript: Rust like features for JavaScript" article, I have to admit that ReScript does have some very nice features and should be a great choice for people looking for a better than JS, simple and fast scripting language.
BTW, talking about simple and fast languages, do you know "V programming language" (vlang.io)? It's basically a better version of Go. Used V for a hobby project and loved its simplicity.
I'll have to check it out, thanks!
I think the great strength of typescript is that it's not trying to replace javascript, but rather to play along with it. That's the reason of existence for types like "any" and many other design choices.
There were countless attempts of languages "compiling into" JS. They all failed, since decades. The reason is that the important thing is not the language. It's the ecosystem. The sheer amount of libraries, the frameworks, the tooling, etc. Sure, you can use your new fancy language with a super syntax in your bubble ...but it'll stay there, in the small bubble.
I agree 100% that easy interop and gradual adoption is what led to TypeScript's success.
Other compile to JS languages have always been slightly outside the JS ecosystem, such as Elm, PureScript, Reason, or Clojure. They had they're own package manager, frameworks, or difficult interop with JS. ReScript is on NPM and you can easily use it with your existing build tools, or even partially use it in a JS/TS project. It's not a separate ecosystem, which is what hurt adoption of other compile to JS languages.
But I can't use it in my IntelliJ or the integration may be crappy, I can't use it in vue or react or angular. Heck, I can't even use another lib if it hasn't an "any" type! ...and please don't tell me I should just write the definitions of every lib I want to use first, that would be so contra-productive.
The VScode integration is great actually and has had a ton of effort put into it.
And for any JS library you want to use, you'll either need to write some bindings for the functions you use (which is usually not everything in a library) or find one published on NPM. It's similar to typescript in 2017ish, where you had to find types on NPM or write a d.ts file.
Rescript has JSX and React support out of the box, which covers a lot of ground for most web dev projects.
Still, I guess you can notice your arguments are not really convincing.
While I respect your efforts and it is certainly impressive, I sadly cannot encourage your endeavor. It just leads to even more fragmentation of the community, which I consider counter-productive from a birds eye perspective.
I can't say I come to the same conclusion, but I always expect to have typescript defenders pop up. I had this same debate about why typescript was good in the first place, and again when everyone hated JSX. Progress is tough, and adoption is hard. Not everything will break through, but I try and advocate for the tools that I believe will make our lives as developers easier.
Sure, and you are right to so. It's also not that I love TS or JS ...actually, I find them kind of poor. JS is kind of fucked up if you think about it. There is no sensible language where
[77, 5, 123].sort()
would lead to[123, 5, 77]
. But hey, we have to live with, that's where all the millions of libs, and the frameworks, and the tooling is.Maybe someday WASM will save us all.
support counter arguments!
good points but you are really missing out in the powerful pattern marching variants with the switch
I wish I can downvote because of last sentence. so this is downvote
Good post. I done bigger projects in both rescript and typescript and I must say rescript is really a wonderful language. Having a properly inferred sound nominal type system is one of the biggest things. Just because a thing looks like a duck, quacks like a duck it doesn't mean its a duck. Having the ability to alias a string to a custom type then hide it using an interface file is super powerful.
Other things like having tail recursive functions be compiled down to a while loop is something TS can't do.
Having named arguments for functions reduces the need for adding objects all over the place.
Having proper sum types not the discriminated unions in TS that are so verbose that they are pretty useless.
Having a blazingly fast compiler really changes how you develop things.
The list just goes on and on.
Typescript has some fancy type magic as well that rescript doesn't but really never missed that when doing rescript. The type system is just cleaner, simpler and more elegant.
I think a lot of TS developers really need to try this language write a 10kloc project in it then you can say what you think about it. I think a lot of people would be converted if they just past the barrier and are open minded.
Once you have sum types and pattern matching you wonder how anyone gets anything done without them.
Yes once you have proper sum types you start to reason about problems that way and it frustrating when you don't have that language feature.
Thanks for this post! It's been a few years since I've heard about ReScript, I'm very glad to hear it's still going strong!
For anyone intrigued by this language remember that it does compile to very readable JavaScript. IIRC it would be human editable if need be. That means you have a very easy gradual migration path (and a fallback if you don't end up liking it). No reason not to try it out.
Yup! It's very easy to just commit the compiled JS to GitHub and delete rescript. You would probably want to clean up some function names, but it'll work as is and be readable.
Well this is something new . I guess trying is not too harmful ..
Give it a shot! rescript-lang.org/try
rescript-lang.org/docs/manual/late...
Hi Josh Derocher-Vlk,
Top, very nice !
Thanks for sharing
thanks for taking the time to write this, I am always looking for ways to avoid Typescript and this looks promising, my initial reaction to the synatx below is negative
only as it reminds me of PHP in the scheme of things but that is a non issue
does it have "Either" ?
question arises w.r.t your section on bindings the final actual pattern matching seems to me a bit verbose, I am not an OCaml dev but could that not be a simple Either?
Rescript looks really good
This is the pipe syntax. If you haven't worked with a language that has it, it can be really strange to see, but once you get used to working with pipes you never want to work in a language that lacks them.
Without pipes that last function would have looked like this:
It has a Result type.
Yeah, I selected my most recent bindings and the underlying library has a weird API, so it was tricky to keep simple. It returns an object, 'undefined' or
false
. I had to make a custom variant type to figure that out. Variant types can have any name, and choosingMatch
andFalse
is probably not clear. This is internal to the module I was working on, so the outside doesn't have to look at it.It would be clearer to have named it
Yes
andNo
like this:I've updated the example in the article (and in my project!) to be a bit more clear.
amazing
yes definitly better than Either in this case
At the end it doesn’t matter. These languages doesn’t provide runtime type safety just because they are transpiled to javascript which doesn’t have type safety. So in essence these “type safety” languages just benefit on IDE’s
"Runtime type safety" isn't really a thing, even Haskell and Rust are compile-time checks.
ReScript is a compiled language, so it's more than just a check for the IDE. If I have a source file that has a type error in it, that file will never become JavaScript, which means it will never run on Node or in the browser. Unfortunately for TypeScript, this usually isn't the case since most build tools are just stripping out the type information and we type check as a separate step in our build pipeline.
Rust does go way beyond simple type checking. The robust static type checking and ownership model is strong enough to ensure the application is stable even during runtime.
Sugar-coated languages doesn't bring any benefits of any of their typing models once they are compiled to Javascript which is purely untyped language.
If you are comparing the rust binaries to some javascript bundles then you are comparing apple to oranges.
Yes, if you are looking for auto-completes, documentation and development experience, yes these fancy languages are good on IDE's.
I can technically write a function that receive input as
int
for example and still bind it to an html number input that returns a floating point. Which completely defeats the purpose.One downside of not being able to have two files with the same name is that when you use a React framework which uses file based routing you have to wrap ReScript React code/components in a JS or TS file, because you can't have multiple
page.res
orroute.res
files.Yeah, that's the one pain point I've found, but the workaround is pretty easy. You have to create thin JS files with the correct name and folder that just re-export from the compiled ReScript files.
I don't really like the weird "~" and "->" as opposed to plain "."
when you want to appeal average typescript devs, you need to make it seamless familiar to them.
Typescript is already good enough most of the case
The
~
and->
are not equivalent or replacements for.
.~
is a labled argument which allows you to pass in an argument in any order, it compiles down to an object in JS.rescript-lang.org/docs/manual/late...
And
->
is the pipe operator. It takes the previous value and passes it as the first argument of the next function.rescript-lang.org/docs/manual/late...
You lost me at
We had to do this with TypeScript for years.