How to set up a React app using ReScript, Vite, and Tailwind for lightning fast development.

Josh Derocher-Vlk - Sep 24 '23 - - Dev Community

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 or unknown. 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 the any 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


Enter fullscreen mode Exit fullscreen mode

Open this folder in your IDE.

Install ReScript

Let's install ReScript and the ReScript Core library.



npm i rescript @rescript/core


Enter fullscreen mode Exit fullscreen mode

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"
    ]
}


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

Create a file named src/hello.res and log something to the console:



Console.log("Hello world!")


Enter fullscreen mode Exit fullscreen mode

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 


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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>


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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>


Enter fullscreen mode Exit fullscreen mode

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!")
}


Enter fullscreen mode Exit fullscreen mode

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
    }
  )],
});


Enter fullscreen mode Exit fullscreen mode

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"
  },


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

Add html and mjs files to the tailwind.config.js file:



/** @type {import('tailwindcss').Config} */
export default {
  content: ["./src/**/*.{html,mjs}"],
  theme: {
    extend: {},
  },
  plugins: [],
};


Enter fullscreen mode Exit fullscreen mode

Add src/main.css with the basic Tailwind setup:



@tailwind base;
@tailwind components;
@tailwind utilities;


Enter fullscreen mode Exit fullscreen mode

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>


Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode




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!

. . . . . . . . .