@jvlk/rescript-remix
ReScript bindings, modules, and functions for Remix.
Requires ReScript v11.
This is a work in progress and is not complete
Installation
npm install @jvlk/rescript-remix
Update rescript.json
to include it in your dependencies.
"bs-dependencies": [
"@jvlk/rescript-remix"
],
If you want to have .res
files directly in the app/routes
directory you will need to set the file extension to .jsx
in rescript.json
.
Note: ReScript does not allow you to have
$
in file names so you will need to create a JavaScript file for routes with params such asblog/$id.jsx
and import from your generated JavaScript files.
Add ReScript extenstions to Remix's ignored files so it doesn't try and load them in as routes and uses the generated .jsx
files.
// remix.config.js
export default {
ignoredRouteFiles: ["**/.*", "**/*.res", "**/*.resi"],
}
root.js
You can add types to Remix's links and meta functions
@module("./style.css")
external styles: string = "default"
let links: Remix.Links.t = () => [Remix.Links.Href({rel: "stylesheet", href: styles})]
let meta: Remix.Meta.t = () => [
Remix.Meta.Content({name: "viewport", content: "width=device-width, initial-scale=1"}),
Remix.Meta.Title({title: "My site"}),
Remix.Meta.Content({
name: "description",
content: "a website",
}),
Remix.Meta.Charset({charset: "UTF-8"}),
]
@react.component
let make = () => {
<html lang="en">
<head>
<link rel="icon" href="data:image/x-icon;base64,AA" />
<Remix.Meta />
<Remix.Links />
</head>
<body>
<main>
<Remix.Outlet />
</main>
<Remix.Scripts />
<Remix.LiveReload />
</body>
</html>
}
let default = make
Loader
To create a typed loader you can use Remix.MakeLoader
higher order module (functor). This will create a type for a loader as well as a typed useLoaderData
hook that has a typed response.
// _index.res
module Data = {
type t = {posts: Posts.t} // type for the data returned by the loader
type params // route params
type context = { "NODE_ENV": string, "API_KEY": string } // context passed from the server
}
module Loader = Remix.MakeLoader(Data) // create a Loader module for this route
let loader: Loader.t /** use Loader.t to add typing to the exported loader function */= async ({context}) => {
let secret = context["API_KEY"]
let data = await Posts.query(secret)
Loader.json( // this is Remix's json function typed to Data.t
{posts: data}, // if this doesn't match Data.t you'll get a type error
~jsonOptions={
headers: { // you can add headers to the response
"Cache-Control": "max-age=300, s-maxage=3600",
},
},
)
}
@react.component
let make = () => {
let {posts} = Loader.useLoaderData() // the data from the hook is fully typed
<div>
{posts
->Array.map(post => <Post post />)
->React.array}
</div>
}
let default = make // you have to export make as default for route files
Actions
To create an action you can use Remix.MakeAction
.
module ActionData = {
type t
type context = Env.context
}
module Action = Remix.MakeAction(ActionData)
let action: Action.t = async ({context, request}) => {
/** */
Js.null // actions expect you to return a value or null
}