|
| 1 | +# ReScript Lang Website – Agent Guidelines |
| 2 | + |
| 3 | +## Project Overview |
| 4 | + |
| 5 | +This is the official documentation website for the [ReScript](https://rescript-lang.org) programming language. It is a **fully pre-rendered static site** (no server-side rendering at runtime) built with **ReScript v12 + React 19 + React Router v7 + Vite 7 + Tailwind CSS v4**, deployed to **Cloudflare Pages**. |
| 6 | + |
| 7 | +## System Requirements |
| 8 | + |
| 9 | +- Node.js ≥ 22 |
| 10 | +- Yarn 4.12.0 (via Corepack) |
| 11 | + |
| 12 | +## Project Structure |
| 13 | + |
| 14 | +``` |
| 15 | +app/ → React Router app shell (root layout, route definitions, route modules) |
| 16 | + routes/ → Route components with loaders (e.g. BlogRoute.res, MdxRoute.res) |
| 17 | +src/ → Core ReScript source code |
| 18 | + bindings/ → Zero-cost bindings to JS libraries (@module externals) |
| 19 | + common/ → Shared utilities, hooks, helpers (non-component modules) |
| 20 | + components/ → Reusable React components |
| 21 | + ffi/ → Plain JS interop files (prefer %raw over adding new files here) |
| 22 | + layouts/ → Page layout components (DocsLayout, SidebarLayout, etc.) |
| 23 | +markdown-pages/ → MDX content (blog posts, docs, community pages, syntax-lookup) |
| 24 | +data/ → Hand-curated data (API docs JSON, sidebar ordering) |
| 25 | +styles/ → CSS files (Tailwind v4 config in main.css, custom utilities) |
| 26 | +scripts/ → Build/codegen scripts (ReScript + JS) |
| 27 | +functions/ → Cloudflare Pages Functions (e.g. OG image generation) |
| 28 | +compilers/ → Bundled ReScript compiler versions (for the playground) |
| 29 | +plugins/ → HighlightJS & CodeMirror plugins |
| 30 | +public/ → Static assets (images, fonts, favicons) |
| 31 | +__tests__/ → Vitest browser-mode tests (Playwright) |
| 32 | +``` |
| 33 | + |
| 34 | +## Key Commands |
| 35 | + |
| 36 | +| Command | Purpose | |
| 37 | +| ---------------- | -------------------------------------------------------------- | |
| 38 | +| `yarn dev` | Run ReScript watcher + Vite dev server + Wrangler (parallel) | |
| 39 | +| `yarn build` | Full production build (ReScript → scripts → Vite/React Router) | |
| 40 | +| `yarn build:res` | ReScript compilation only | |
| 41 | +| `yarn dev:res` | ReScript watch mode only | |
| 42 | +| `yarn format` | Run Prettier + ReScript formatter | |
| 43 | +| `yarn test` | Run example and href validation scripts | |
| 44 | +| `yarn vitest` | Run Vitest browser tests with Playwright | |
| 45 | +| `make` | Build (install deps + ReScript compile + update index) | |
| 46 | + |
| 47 | +## Coding Best Practices |
| 48 | + |
| 49 | +- Prefer small functions with a single purpose. |
| 50 | +- Use a functional approach but don't make it too hardcore or difficult to understand. |
| 51 | +- Keep files and modules small and focused. |
| 52 | +- `.res` files must always be capitalized (PascalCase), matching ReScript module conventions. |
| 53 | +- Use the pipe-first operator (`->`) for chaining, which is idiomatic ReScript. |
| 54 | +- Resolve all warnings and treat them as errors. The project has `"error": "+8"` in `rescript.json`. |
| 55 | + |
| 56 | +## ReScript Rules |
| 57 | + |
| 58 | +- You use **ReScript v12** (latest). Ensure all suggestions match this version. |
| 59 | +- Ensure any produced JSX matches ReScript JSX v4 syntax (configured with `"preserve": true`). |
| 60 | +- **Never use the `Belt` or `Js` modules** — these are legacy. Use the modern `Stdlib` / core modules instead. |
| 61 | +- Always use the `JSON.t` type for JSON values. |
| 62 | +- When dealing with promises, prefer `async/await` syntax. |
| 63 | +- The project opens `WebAPI.Global` globally via compiler flags, so Web APIs are available without prefix. |
| 64 | +- Output format is ES modules with `.jsx` suffix, compiled in-source (`.jsx` files sit alongside `.res` files). |
| 65 | +- Reference the abridged documentation for clarification on how ReScript's APIs work: https://rescript-lang.org/llms/manual/llm-small.txt |
| 66 | +- If you need more information you can access the full documentation, but do this only when needed as the docs are very large: https://rescript-lang.org/llms/manual/llm-full.txt |
| 67 | + |
| 68 | +### ReScript Dependencies |
| 69 | + |
| 70 | +- `@rescript/react` — React bindings |
| 71 | +- `@rescript/webapi` — Web API bindings (opened globally) |
| 72 | + |
| 73 | +### JS Interop Patterns |
| 74 | + |
| 75 | +The project uses several patterns for JavaScript interop. Follow the existing conventions: |
| 76 | + |
| 77 | +- **`@module` externals** for binding to npm packages (see `src/bindings/` for examples): |
| 78 | + ``` |
| 79 | + @module("react-router") external useNavigate: unit => string => unit = "useNavigate" |
| 80 | + ``` |
| 81 | +- **`@module` with `@react.component`** for binding to external React components: |
| 82 | + ``` |
| 83 | + @module("react-router") @react.component |
| 84 | + external make: (~children: React.element) => React.element = "Outlet" |
| 85 | + ``` |
| 86 | +- **`%raw()`** for inline JS when no clean binding is possible: |
| 87 | + ``` |
| 88 | + let copyToClipboard: string => bool = %raw(`function(str) { ... }`) |
| 89 | + ``` |
| 90 | +- **`%%raw()`** (double percent) for top-level side-effectful JS imports. |
| 91 | +- **`@scope`** for binding to object properties like `process.env`. |
| 92 | +- **`external ... = "%identity"`** for zero-cost type coercions. |
| 93 | +- Put new bindings in `src/bindings/` as a dedicated module (e.g. `Fuse.res`, `DocSearch.res`). |
| 94 | + |
| 95 | +## ReScript React |
| 96 | + |
| 97 | +- This project uses **React 19** and **React Router v7** (framework mode). |
| 98 | +- The site is **pre-rendered** (`ssr: false`), so loaders have access to the filesystem during build. Loaders do **not** run on a server after the build. |
| 99 | +- Route modules live in `app/routes/` and export a `loader` and a `default` component. |
| 100 | +- Route modules **require** both a `.res` and a `.resi` (interface) file for Vite HMR to work. |
| 101 | +- Only a single React component can be exposed from a module's JS output. |
| 102 | + |
| 103 | +### Route Module Pattern |
| 104 | + |
| 105 | +Every route follows this pattern: |
| 106 | + |
| 107 | +```rescript |
| 108 | +// SomeRoute.res |
| 109 | +type loaderData = { ... } |
| 110 | +
|
| 111 | +let loader: ReactRouter.Loader.t<loaderData> = async ({request}) => { |
| 112 | + // filesystem access is available here (pre-render time only) |
| 113 | + let data = ... |
| 114 | + data |
| 115 | +} |
| 116 | +
|
| 117 | +@react.component |
| 118 | +let default = () => { |
| 119 | + let data = ReactRouter.useLoaderData() |
| 120 | + <SomeLayout> ... </SomeLayout> |
| 121 | +} |
| 122 | +``` |
| 123 | + |
| 124 | +With a matching interface file: |
| 125 | + |
| 126 | +```rescript |
| 127 | +// SomeRoute.resi |
| 128 | +type loaderData = { ... } |
| 129 | +let loader: ReactRouter.Loader.t<loaderData> |
| 130 | +
|
| 131 | +@react.component |
| 132 | +let default: unit => React.element |
| 133 | +``` |
| 134 | + |
| 135 | +### JSX Syntax Rules |
| 136 | + |
| 137 | +- `React.useState` takes a function: `let (age, setAge) = React.useState(_ => 4)` |
| 138 | +- Every expression inside an interpolated string must be of type `string`: |
| 139 | + - Wrong: `` `age = ${42}` `` |
| 140 | + - Right: `` `age = ${42->Int.toString}` `` |
| 141 | +- `type` is a keyword in ReScript. Use `type_` for JSX props: `<button type_="submit" />` |
| 142 | +- **You cannot add text as a direct child of a React component.** Everything must be a `React.element`: |
| 143 | + - Wrong: `<h1>Hello World</h1>` |
| 144 | + - Right: `<h1>{React.string("Hello World")}</h1>` |
| 145 | + - Use `React.string`, `React.int`, `React.float`, and `React.array` for primitive conversions. |
| 146 | +- Use `React.null` for rendering nothing. |
| 147 | +- Optional props use `?` syntax: `<button ?onClick>` to pass `option<handler>`. |
| 148 | + |
| 149 | +## Styling |
| 150 | + |
| 151 | +- **Tailwind CSS v4** configured via the Vite plugin (`@tailwindcss/vite`). There is no `tailwind.config.js`. |
| 152 | +- All Tailwind configuration is in `styles/main.css` using CSS-native `@theme` blocks. |
| 153 | +- **LightningCSS** is used as the CSS transformer. |
| 154 | +- The project defines custom design tokens in `styles/main.css`: |
| 155 | + - Custom colors: `gray-*`, `fire-*`, `sky-*`, `berry-*`, `water`, `turtle`, `orange-*` |
| 156 | + - Custom font sizes: `text-11` through `text-68` |
| 157 | + - Custom utility classes: `hl-title`, `hl-1`–`hl-5`, `body-lg`, `body-md`, `body-sm`, `captions` |
| 158 | + - Fonts: Inter (sans), Roboto Mono (mono) |
| 159 | +- Use existing custom utilities and design tokens. Check `styles/main.css` before creating new ones. |
| 160 | + |
| 161 | +## Testing |
| 162 | + |
| 163 | +- Tests use **Vitest 4** in browser mode with **Playwright** (Chromium). |
| 164 | +- Test files live in `__tests__/` and are named `ComponentName_.test.res` (compiled to `.test.jsx`). |
| 165 | +- Tests use custom ReScript bindings in `src/bindings/Vitest.res`. |
| 166 | +- Tests are visual/integration tests that render components and assert visibility, interactions, and screenshots. |
| 167 | +- Test pattern: |
| 168 | + |
| 169 | + ```rescript |
| 170 | + open Vitest |
| 171 | +
|
| 172 | + test("description", async () => { |
| 173 | + await viewport(1440, 500) |
| 174 | + let screen = await render(<MyComponent />) |
| 175 | + let el = await screen->getByTestId("my-element") |
| 176 | + await element(el)->toBeVisible |
| 177 | + await element(el)->toMatchScreenshot("screenshot-name") |
| 178 | + }) |
| 179 | + ``` |
| 180 | + |
| 181 | +- Components requiring React Router context must be wrapped in `<BrowserRouter>`. |
| 182 | +- Run tests with `yarn vitest`. |
| 183 | + |
| 184 | +## MDX Content |
| 185 | + |
| 186 | +- Documentation content is in `markdown-pages/` organized by section (blog, docs, community, syntax-lookup). |
| 187 | +- MDX is processed by `react-router-mdx` with remark/rehype plugins. |
| 188 | +- Custom MDX components are mapped in `app/routes/MdxRoute.res` (e.g. `<Info>`, `<Warn>`, `<CodeTab>`, `<Video>`). |
| 189 | +- Code examples in markdown use ` ```res example ` (runnable), ` ```res sig ` (signature), and ` ```res prelude ` (shared context). |
| 190 | + |
| 191 | +## Formatting & Git Hooks |
| 192 | + |
| 193 | +- **Prettier** with the `@prettier/plugin-oxc` parser for JS formatting. |
| 194 | +- **ReScript formatter** (`rescript format`) for `.res` files. |
| 195 | +- **Lefthook** runs `yarn format` on pre-commit (auto-stages fixed files). |
| 196 | +- Generated `.mjs`/`.jsx` output files from ReScript are git-tracked but excluded from Prettier. |
| 197 | + |
| 198 | +## Important Warnings |
| 199 | + |
| 200 | +- Do **not** modify generated `.jsx` / `.mjs` files directly — they are ReScript compiler output. |
| 201 | +- Do **not** use `@genType` — the project does not use it. |
| 202 | +- The `src/ffi/` directory is legacy; prefer `%raw` statements for new JS interop. |
| 203 | +- The README references some outdated structures (Next.js, `pages/` directory) — ignore those references. The project has migrated to React Router v7. |
| 204 | +- When editing route files, always update both the `.res` and `.resi` files. |
0 commit comments