DEV Community

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

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

Josh Derocher-Vlk on May 28, 2024

ReScript, the "Fast, Simple, Fully Typed JavaScript from the Future", has been around for awhile now. The name "ReScript" came into existence in 20...
Collapse
 
hakimio profile image
Tomas Rimkus

Regarding your "Why would I pick this over TypeScript?" points:

  • You can have TS code without "any". Just set-up ESLint rule to disallow "any".
  • Type inference is really good now in TS. TSC can auto-detect types in most of the cases where it makes sense. Typing a and b variables as numbers by default when code is let add = (a, b) => a + b doesn't make sense to me. Calling this add() function with strings is perfectly valid in JS.
  • If you want only "the good parts", then only use them. Use a linter to disallow the "bad" code constructs.
  • TSC is pretty fast nowadays.
  • Why would I want to have Rust code in my JS/TS code? I want better JS, not "Rust in JS".

Don't really see why any TS developer would want to switch to this.

Collapse
 
jderochervlk profile image
Josh Derocher-Vlk

First off, thanks for taking the time to read and leave a comment! I'll answer your points one by one.

You can have TS code without "any". Just set-up ESLint rule to disallow "any".

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.

Type inference is really good now in TS. TSC can auto-detect types in most of the cases where it makes sense. Typing a and b variables as numbers by default when code is let add = (a, b) => a + b doesn't make sense to me. Calling this add() function with strings is perfectly valid in JS.

That is valid code in JS, which of course makes it valid code in TS. In ReScript the + operator is only for adding together int. There's +. for float and ++ for string. 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.

If you want only "the good parts", then only use them. Use a linter to disallow the "bad" code constructs.

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.

TSC is pretty fast nowadays.

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.

Why would I want to have Rust code in my JS/TS code? I want better JS, not "Rust in JS".

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

Don't really see why any TS developer would want to switch to this.

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.

Collapse
 
dagnelies profile image
Arnaud Dagnelies • Edited

I have project with 32k TypeScript files ...

A ReScript project with 50k files ...

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.

Thread Thread
 
jderochervlk profile image
Josh Derocher-Vlk

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.

Thread Thread
 
dagnelies profile image
Arnaud Dagnelies

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.

Thread Thread
 
jderochervlk profile image
Josh Derocher-Vlk

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.

Thread Thread
 
jderochervlk profile image
Josh Derocher-Vlk

I've worked on larger projects at saas companies in the past, things can get complicated and large real quick.

Thread Thread
 
dagnelies profile image
Arnaud Dagnelies

Man... split that monster ...it's overdue ...40 people on the same repo is too much ...I don't get it

Thread Thread
 
jderochervlk profile image
Josh Derocher-Vlk

Hey, Google has just 1 repo, so if it works it works.

Thread Thread
 
jderochervlk profile image
Josh Derocher-Vlk

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.

Thread Thread
 
dagnelies profile image
Arnaud Dagnelies

So what's the website requiring this 50k files? Now I'm even more curious of that beast :P

Thread Thread
 
jderochervlk profile image
Josh Derocher-Vlk

You would be very disappointed lol. Its a lot of logic for a/b testing and landing pages that most people will never see.

 
dagnelies profile image
Arnaud Dagnelies

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.

Thread Thread
 
jderochervlk profile image
Josh Derocher-Vlk

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

Collapse
 
hakimio profile image
Tomas Rimkus • Edited

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.

Thread Thread
 
jderochervlk profile image
Josh Derocher-Vlk

I'll have to check it out, thanks!

Collapse
 
dagnelies profile image
Arnaud Dagnelies • Edited

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.

Thread Thread
 
jderochervlk profile image
Josh Derocher-Vlk

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.

Thread Thread
 
dagnelies profile image
Arnaud Dagnelies

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.

Thread Thread
 
jderochervlk profile image
Josh Derocher-Vlk

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.

Thread Thread
 
jderochervlk profile image
Josh Derocher-Vlk

Rescript has JSX and React support out of the box, which covers a lot of ground for most web dev projects.

Thread Thread
 
dagnelies profile image
Arnaud Dagnelies

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.

Thread Thread
 
jderochervlk profile image
Josh Derocher-Vlk

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.

Thread Thread
 
dagnelies profile image
Arnaud Dagnelies

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.

Thread Thread
 
jderochervlk profile image
Josh Derocher-Vlk

Maybe someday WASM will save us all.

Collapse
 
vasco3 profile image
JC

support counter arguments!

Collapse
 
vasco3 profile image
JC

good points but you are really missing out in the powerful pattern marching variants with the switch

Collapse
 
tombohub profile image
tombohub

I wish I can downvote because of last sentence. so this is downvote

Collapse
 
spocke78 profile image
Johan Sörlin • Edited

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.

Collapse
 
jderochervlk profile image
Josh Derocher-Vlk

Once you have sum types and pattern matching you wonder how anyone gets anything done without them.

Collapse
 
spocke78 profile image
Johan Sörlin

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.

Collapse
 
joefiorini profile image
Joe Fiorini

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.

Collapse
 
jderochervlk profile image
Josh Derocher-Vlk

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.

Collapse
 
srijanbaniyal profile image
Srijan Baniyal

Well this is something new . I guess trying is not too harmful ..

Collapse
 
jderochervlk profile image
Josh Derocher-Vlk
Collapse
 
jangelodev profile image
João Angelo

Hi Josh Derocher-Vlk,
Top, very nice !
Thanks for sharing

Collapse
 
nigel447 profile image
nigel447

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

->Promise
Enter fullscreen mode Exit fullscreen mode

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

Collapse
 
jderochervlk profile image
Josh Derocher-Vlk • Edited
->Promise
Enter fullscreen mode Exit fullscreen mode

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:

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

does it have "Either" ?

It has a Result type.

the final actual pattern matching seems to me a bit verbose

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 choosing Match and False 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 and No like this:

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

  type pathMatch = {
    path: string,
    params: {.},
  }

  @unboxed
  type isMatch =
    | Yes(option<pathMatch>)
    | @as(false) No

  type match = string => isMatch

  @module("path-to-regexp")
  external match: string => match = "match"

  let make = url => {
    let fn = match(url)
    path =>
      switch fn(path) {
      | Yes(t) => t->Option.map(t => t.params)
      | No => None
      }
  }
Enter fullscreen mode Exit fullscreen mode

I've updated the example in the article (and in my project!) to be a bit more clear.

Collapse
 
nigel447 profile image
nigel447

amazing

Collapse
 
nigel447 profile image
nigel447

yes definitly better than Either in this case

Collapse
 
sirajulm profile image
Sirajul Muneer

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

Collapse
 
jderochervlk profile image
Josh Derocher-Vlk

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

Collapse
 
sirajulm profile image
Sirajul Muneer

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.

Collapse
 
psb profile image
Paul Bacchus

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 or route.res files.

Collapse
 
jderochervlk profile image
Josh Derocher-Vlk

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.

Collapse
 
adicandra1 profile image
adicandra1

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

Collapse
 
jderochervlk profile image
Josh Derocher-Vlk

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.

let greet = (~name) => `Hello ${name}`

Console.log(greet(~name="Josh"))
Enter fullscreen mode Exit fullscreen mode

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.

"Hello"->Console.log
// is the same as
Console.log("Hello")
Enter fullscreen mode Exit fullscreen mode

rescript-lang.org/docs/manual/late...

Collapse
 
jakewilson profile image
Jake Wilson

You lost me at

you'll need to dig into writing your own bindings

Collapse
 
jderochervlk profile image
Josh Derocher-Vlk

We had to do this with TypeScript for years.