Let's set up a modern React application using tools that give the fastest local development experience while still enabling strict type checking.
ReScript modules can almost instantly compile to JavaScript Modules (ESM) which allows us to use a tool like Vite to quickly load them directly into the browser when changes are made.
Note: This guide will walk you through all of the steps manually to set up ReScript, Vite, and Tailwindcss. I suggest you do this at least once to learn how it all works, but you can also use
npm create rescript-app
to quickly create an app with these tools. Just select "vite" for a template and include the new@rescript/core
library when asked.
What is ReScript?
ReScript is a language that compiles to JavaScript with strong types, a speedy compiler, no null exceptions, and pattern-matching.
You can add it to your existing JS toolchain and build system, and it uses your standard JS package manager like NPM or Yarn.
Using it feels very similar to using TypeScript, but there are a few key differences to each language:
- ReScript is strongly typed, which means nothing can be of an
any
type orunknown
. If something has a type it is guaranteed to be correct. - TypeScript is a superset of JavaScript, which means that all valid JS is valid TS. While ReScript has a JS like syntax it is a different language, so most of the JS foot guns don't exist like knowing when to use
==
or===
. - While you can adopt both languages gradually into a project the approach is different. With TypeScript you start off without strict mode, rename the files from
.js
to.ts
, slowly convert all of theany
types to known types, and (hopefully) turn on strict mode. With ReScript you would convert a single file at a time.
You can see more differences here.
Set up your IDE
Installing a ReScript plugin into your IDE will give you syntax highlighting, auto-complete, and clicking to sources for functions, types, and values.
For VSCode you can search for and install the ReScript extension. If you are using a different IDE you can find instruction on the ReScript site.
Set up the workspace
I'll be using npm, but you can swap this out for your package manager of choice.
mkdir my-rescript-vite-app
cd my-rescript-vite-app
npm init -y
Open this folder in your IDE.
Install ReScript
Let's install ReScript and the ReScript Core library.
npm i rescript @rescript/core
Now we have to create the ReScript config file.
Note: ReScript is based on a combination of the Reason and BuckletScript, so you'll see
bs
used in places.
Create a rescript.json
file in the root of the application.
{
"name": "my-rescript-vite-app",
"sources": [
{
"dir": "src",
"subdirs": true
}
],
"package-specs": {
"module": "es6",
"in-source": true,
"suffix": ".mjs"
},
"bs-dependencies": [
"@rescript/core"
],
"bs-dev-dependencies": [],
"bsc-flags": [
"-open RescriptCore"
]
}
Quick breakdown of the config options
- name: the name of our application
- sources: where our code lives
- package-specs: what module system we want to use and if we want the output to live in the same folder as the source
- bs-dependencies: similar to npm dependencies
- bs-dev-dependencies: similar to npm dev-dependencies
- bsc-flags: Additional options for the compiler }
We've set up ESM files since we'll be using Vite, added @rescript/core
as a dependencies, and we're opening up that module to make it easier to access later.
You can read the full documentation on the ReScript site.
Verify that ReScript compiles
We should be able to compile and run some code now!
Add a couple scripts to the package.json
file:
"scripts": {
"res:build": "rescript",
"res:dev": "rescript -w"
}
Create a file named src/hello.res
and log something to the console:
Console.log("Hello world!")
Compile the source code with npm run res:build
and execute it with node src/hello.mjs
.
You should see Hello world!
print to the console.
Install React
Install React, React Dom, and Rescript bindings for React.
npm i react react-dom @rescript/react
Then add @rescript/react
as a dependency in bsconfig.json
and configuration for JSX.
"jsx": { "version": 4, "mode": "automatic" },
"bs-dependencies": ["@rescript/core", "@rescript/react"]
Let's change hello.res
to be a React component and rename it to App.res
.
@react.component
let make = () => <p> {React.string("Hello world!")} </p>
We'll also want to make an interface file (.resi
) for each React component. This enables Vite's hot module reloading (HMR) to work as expected when making changes to our ReScript components.
Note: For HMR to work correctly each file can only contain one component and cannot export any other functions.
Install Vite
npm i vite @vitejs/plugin-react -D
Create an index.html
file in the project root:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ReScript Vite App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./src/Main.mjs"></script>
</body>
</html>
Create our main app at src/Main.res
:
switch ReactDOM.querySelector("#root") {
| Some(rootElement) => {
let root = ReactDOM.Client.createRoot(rootElement)
ReactDOM.Client.Root.render(root, <App />)
}
| None => Exn.raiseError("No element has ID of #root!")
}
Create vite.config.mjs
in the root of the project:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(
{
include: ["**/*.mjs"], // since we're not using .jsx files we have to tell Vite to watch for HMR changes to .mjs files
}
)],
});
And finally let's add some more npm scripts to build our app:
"scripts": {
"res:build": "rescript",
"res:clean": "rescript clean",
"res:dev": "rescript build -w",
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
Open up two terminal windows or tabs. In the first one run npm run res:dev
and in the second run npm run dev
. If you are using VSCode with the ReScript extension installed it will ask you if you want to run a ReScript build server which you can use instead of running npm run res:dev
. I choose to run it from the terminal so I can see both outputs next to each other easily.
You should now be able to open up http://127.0.0.1:5173/
and see "Hello world!". Make a change to App.res
and you should see Vite apply the changes with HMR.
Adding Tailwind CSS
Starting by installing Tailwind and running the setup command.
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Add html
and mjs
files to the tailwind.config.js
file:
/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/**/*.{html,mjs}"],
theme: {
extend: {},
},
plugins: [],
};
Add src/main.css
with the basic Tailwind setup:
@tailwind base;
@tailwind components;
@tailwind utilities;
Import that file into index.html
.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="./src/main.css" rel="stylesheet">
<title>ReScript + Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/Main.mjs"></script>
</body>
</html>
Now we can use Tailwind classes in our ReScript components! Go ahead and add some to App.res
.
@react.component
let make = () =>
<div className="text-3xl text-center m-5 text-gray-700"> {React.string("Hello world.")} </div>
Wrapping up
You now have the basic shell to build out a React application quickly and with confidence.
During local development we can see change appear in milliseconds instead of minutes or seconds, and we can do this while still having confidence in our code because of ReScript's strong type system and compiler. ReScript removes the need for TypeScript (has types already), ESLint (ReScript doesn't have the "bad parts" of JS and the compiler helps us write clean code), and Prettier (ReScript has a built in formatter). Since we don't need these tools it speeds up VSCode because these don't have to run every time we save a file.
Make sure to check out other articles on ReScript!