diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..eef55bc62 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,204 @@ +# ReScript Lang Website – Agent Guidelines + +## Project Overview + +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**. + +## System Requirements + +- Node.js ≥ 22 +- Yarn 4.12.0 (via Corepack) + +## Project Structure + +``` +app/ → React Router app shell (root layout, route definitions, route modules) + routes/ → Route components with loaders (e.g. BlogRoute.res, MdxRoute.res) +src/ → Core ReScript source code + bindings/ → Zero-cost bindings to JS libraries (@module externals) + common/ → Shared utilities, hooks, helpers (non-component modules) + components/ → Reusable React components + ffi/ → Plain JS interop files (prefer %raw over adding new files here) + layouts/ → Page layout components (DocsLayout, SidebarLayout, etc.) +markdown-pages/ → MDX content (blog posts, docs, community pages, syntax-lookup) +data/ → Hand-curated data (API docs JSON, sidebar ordering) +styles/ → CSS files (Tailwind v4 config in main.css, custom utilities) +scripts/ → Build/codegen scripts (ReScript + JS) +functions/ → Cloudflare Pages Functions (e.g. OG image generation) +compilers/ → Bundled ReScript compiler versions (for the playground) +plugins/ → HighlightJS & CodeMirror plugins +public/ → Static assets (images, fonts, favicons) +__tests__/ → Vitest browser-mode tests (Playwright) +``` + +## Key Commands + +| Command | Purpose | +| ---------------- | -------------------------------------------------------------- | +| `yarn dev` | Run ReScript watcher + Vite dev server + Wrangler (parallel) | +| `yarn build` | Full production build (ReScript → scripts → Vite/React Router) | +| `yarn build:res` | ReScript compilation only | +| `yarn dev:res` | ReScript watch mode only | +| `yarn format` | Run Prettier + ReScript formatter | +| `yarn test` | Run example and href validation scripts | +| `yarn vitest` | Run Vitest browser tests with Playwright | +| `make` | Build (install deps + ReScript compile + update index) | + +## Coding Best Practices + +- Prefer small functions with a single purpose. +- Use a functional approach but don't make it too hardcore or difficult to understand. +- Keep files and modules small and focused. +- `.res` files must always be capitalized (PascalCase), matching ReScript module conventions. +- Use the pipe-first operator (`->`) for chaining, which is idiomatic ReScript. +- Resolve all warnings and treat them as errors. The project has `"error": "+8"` in `rescript.json`. + +## ReScript Rules + +- You use **ReScript v12** (latest). Ensure all suggestions match this version. +- Ensure any produced JSX matches ReScript JSX v4 syntax (configured with `"preserve": true`). +- **Never use the `Belt` or `Js` modules** — these are legacy. Use the modern `Stdlib` / core modules instead. +- Always use the `JSON.t` type for JSON values. +- When dealing with promises, prefer `async/await` syntax. +- The project opens `WebAPI.Global` globally via compiler flags, so Web APIs are available without prefix. +- Output format is ES modules with `.jsx` suffix, compiled in-source (`.jsx` files sit alongside `.res` files). +- Reference the abridged documentation for clarification on how ReScript's APIs work: https://rescript-lang.org/llms/manual/llm-small.txt +- 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 + +### ReScript Dependencies + +- `@rescript/react` — React bindings +- `@rescript/webapi` — Web API bindings (opened globally) + +### JS Interop Patterns + +The project uses several patterns for JavaScript interop. Follow the existing conventions: + +- **`@module` externals** for binding to npm packages (see `src/bindings/` for examples): + ``` + @module("react-router") external useNavigate: unit => string => unit = "useNavigate" + ``` +- **`@module` with `@react.component`** for binding to external React components: + ``` + @module("react-router") @react.component + external make: (~children: React.element) => React.element = "Outlet" + ``` +- **`%raw()`** for inline JS when no clean binding is possible: + ``` + let copyToClipboard: string => bool = %raw(`function(str) { ... }`) + ``` +- **`%%raw()`** (double percent) for top-level side-effectful JS imports. +- **`@scope`** for binding to object properties like `process.env`. +- **`external ... = "%identity"`** for zero-cost type coercions. +- Put new bindings in `src/bindings/` as a dedicated module (e.g. `Fuse.res`, `DocSearch.res`). + +## ReScript React + +- This project uses **React 19** and **React Router v7** (framework mode). +- 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. +- Route modules live in `app/routes/` and export a `loader` and a `default` component. +- Route modules **require** both a `.res` and a `.resi` (interface) file for Vite HMR to work. +- Only a single React component can be exposed from a module's JS output. + +### Route Module Pattern + +Every route follows this pattern: + +```rescript +// SomeRoute.res +type loaderData = { ... } + +let loader: ReactRouter.Loader.t = async ({request}) => { + // filesystem access is available here (pre-render time only) + let data = ... + data +} + +@react.component +let default = () => { + let data = ReactRouter.useLoaderData() + ... +} +``` + +With a matching interface file: + +```rescript +// SomeRoute.resi +type loaderData = { ... } +let loader: ReactRouter.Loader.t + +@react.component +let default: unit => React.element +``` + +### JSX Syntax Rules + +- `React.useState` takes a function: `let (age, setAge) = React.useState(_ => 4)` +- Every expression inside an interpolated string must be of type `string`: + - Wrong: `` `age = ${42}` `` + - Right: `` `age = ${42->Int.toString}` `` +- `type` is a keyword in ReScript. Use `type_` for JSX props: `