Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ jobs:
runs-on: ubuntu-latest
container:
# Keep this image tag aligned with package.json's playwright version.
image: mcr.microsoft.com/playwright:v1.59.1-noble
image: mcr.microsoft.com/playwright:v1.63.0-noble
env:
# The pinned Playwright image provides Chromium from this browser cache.
PLAYWRIGHT_BROWSERS_PATH: /ms-playwright
Expand Down Expand Up @@ -121,7 +121,7 @@ jobs:
runs-on: ubuntu-latest
container:
# Keep this image tag aligned with package.json's playwright version.
image: mcr.microsoft.com/playwright:v1.59.1-noble
image: mcr.microsoft.com/playwright:v1.63.0-noble
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/update-visual-screenshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
container:
# Keep this image tag aligned with package.json's playwright version.
image: mcr.microsoft.com/playwright:v1.59.1-noble
image: mcr.microsoft.com/playwright:v1.63.0-noble
steps:
- name: Refuse default branch
if: github.ref_name == github.event.repository.default_branch
Expand Down Expand Up @@ -39,6 +39,7 @@ jobs:
- name: Commit changed baselines
id: commit-baselines
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
git add apps/docs/__tests__/visual/__screenshots__

if git diff --cached --quiet; then
Expand Down
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## 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**.
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 v8 + Vite 8 + Tailwind CSS v4**, deployed to **Cloudflare Pages**.

## System Requirements

Expand Down Expand Up @@ -98,7 +98,7 @@ The project uses several patterns for JavaScript interop. Follow the existing co

## ReScript React

- This project uses **React 19** and **React Router v7** (framework mode).
- This project uses **React 19** and **React Router v8** (framework mode).
Comment thread
jderochervlk marked this conversation as resolved.
- 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.
Expand Down Expand Up @@ -164,7 +164,7 @@ let default: unit => React.element

## Testing

- Tests use **Vitest 4** in browser mode with **Playwright** (Chromium).
- Tests use **Vitest 5** in browser mode with **Playwright** (Chromium).
- Test files live in `__tests__/` and are named `ComponentName_.test.res` (compiled to `.test.jsx`).
- Tests use custom ReScript bindings in `src/bindings/Vitest.res`.
- Tests are visual/integration tests that render components and assert visibility, interactions, and screenshots.
Expand Down Expand Up @@ -212,5 +212,5 @@ let default: unit => React.element
- Do **not** modify generated `.jsx` / `.mjs` files directly — they are ReScript compiler output.
- Do **not** use `@genType` — the project does not use it.
- The `src/ffi/` directory is legacy; prefer `%raw` statements for new JS interop.
- The README references some outdated structures (Next.js, `pages/` directory) — ignore those references. The project has migrated to React Router v7.
- The README references some outdated structures (Next.js, `pages/` directory) — ignore those references. The project has migrated to React Router v8.
- When editing route files, always update both the `.res` and `.resi` files.
25 changes: 25 additions & 0 deletions apps/docs/__tests__/MdxFile_.test.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
open Vitest

test("MdxFile.normalizePathname preserves a document route", async () => {
let result = MdxFile.normalizePathname("/blog/archived/a-small-step-for-bucklescript")

expect(result)->toBe("/blog/archived/a-small-step-for-bucklescript")
})

test("MdxFile.normalizePathname removes a data route suffix", async () => {
let result = MdxFile.normalizePathname("/blog/archived/a-small-step-for-bucklescript.data")

expect(result)->toBe("/blog/archived/a-small-step-for-bucklescript")
})

test("MdxFile.normalizePathname removes React Router's data path segment", async () => {
let result = MdxFile.normalizePathname("/blog/archived/a-small-step-for-bucklescript/_.data")

expect(result)->toBe("/blog/archived/a-small-step-for-bucklescript")
})

test("MdxFile.normalizePathname removes a trailing slash", async () => {
let result = MdxFile.normalizePathname("/blog/archived/a-small-step-for-bucklescript/")

expect(result)->toBe("/blog/archived/a-small-step-for-bucklescript")
})
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/docs/app/routes/ApiRoute.res
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ let makeBreadcrumbs = (~prefix: Url.breadcrumb, route: Path.t): list<Url.breadcr
let loader: ReactRouter.Loader.t<loaderData> = async args => {
let path =
WebAPI.URL.make(~url=args.request.url).pathname
->MdxFile.normalizePathname
Comment thread
jderochervlk marked this conversation as resolved.
->String.replace("/docs/manual/api/", "")
->String.split("/")

Expand Down
1 change: 1 addition & 0 deletions apps/docs/app/routes/ApiRoute.resi
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type rec apiItem = {
docStrings: array<string>,
}

/** Loads API documentation after normalizing React Router data-request pathnames. */
let loader: ReactRouter.Loader.t<loaderData>

let default: unit => React.element
21 changes: 10 additions & 11 deletions apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"@headlessui/react": "^2.2.9",
"@mdx-js/mdx": "^3.1.1",
"@node-cli/static-server": "^3.1.10",
"@react-router/node": "^7.15.0",
"@react-router/node": "^8.3.1",
"@rescript-lang/playground": "workspace:*",
"@rescript-lang/shared": "workspace:*",
"@rescript/react": "^0.14.2",
Expand All @@ -56,11 +56,10 @@
"mdast-util-from-markdown": "^2.0.3",
"mdast-util-to-string": "^4.0.0",
"mdast-util-toc": "^7.1.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react": "^19.3.0",
"react-dom": "^19.3.0",
"react-markdown": "^10.1.0",
"react-router": "^7.15.0",
"react-router-dom": "^7.15.0",
"react-router": "^8.3.1",
Comment thread
jderochervlk marked this conversation as resolved.
"rehype-slug": "^6.0.0",
"rehype-stringify": "^10.0.1",
"remark": "^15.0.1",
Expand All @@ -74,11 +73,11 @@
"vfile-matter": "^5.0.1"
},
"devDependencies": {
"@react-router/dev": "^7.15.0",
"@react-router/dev": "^8.3.1",
"@tailwindcss/vite": "^4.3.0",
"@types/react": "^19.2.14",
"@vitejs/plugin-react": "^6.0.1",
"@vitest/browser-playwright": "^4.1.5",
"@vitest/browser-playwright": "^5.0.0",
"auto-image-converter": "^2.2.0",
"chokidar": "^4.0.3",
"cypress": "^15.13.1",
Expand All @@ -87,18 +86,18 @@
"lefthook": "^2.1.4",
"lightningcss": "^1.32.0",
"oxfmt": "^0.46.0",
"playwright": "^1.59.1",
"playwright": "^1.63.0",
Comment thread
jderochervlk marked this conversation as resolved.
"remark-cli": "^12.0.1",
"search-insights": "^2.17.3",
"tailwindcss": "^4",
"to-vfile": "^8.0.0",
"vfile-reporter": "^8.1.1",
"vite": "^8.1.5",
"vite": "^8.3.0",
"vite-plugin-devtools-json": "^1.1.0",
"vite-plugin-env-compatible": "^2.0.1",
"vite-plugin-page-reload": "^0.2.3",
"vitest": "^4.1.5",
"vitest-browser-react": "^2.2.0",
"vitest": "^5.0.0",
Comment thread
jderochervlk marked this conversation as resolved.
"vitest-browser-react": "^2.3.0",
"wrangler": "^4.130.0"
},
"engines": {
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/react-router.config.mjs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import * as fs from "node:fs";
import * as os from "node:os";

const { stdlibPaths } = await import("./app/DocsRoutes.jsx");

export default {
ssr: false,

prerender: {
concurrency: os.availableParallelism(),
// Restore os.availableParallelism() after https://github.com/remix-run/react-router/issues/15255 is fixed.
concurrency: 1,
async paths({ getStaticPaths }) {
return [...(await getStaticPaths()), ...stdlibPaths];
},
Expand Down
14 changes: 14 additions & 0 deletions apps/docs/src/markdown/MdxFile.res
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,21 @@ let compileMdx = async (content, ~filePath, ~remarkPlugins=[]) => {
compiled->CompiledMdx.fromCompileResult
}

let normalizePathname = pathname => {
let pathname =
pathname->String.endsWith("/_.data")
? pathname->String.slice(~start=0, ~end=String.length(pathname) - String.length("/_.data"))
: pathname->String.endsWith(".data")
? pathname->String.slice(~start=0, ~end=String.length(pathname) - String.length(".data"))
: pathname

pathname !== "/" && pathname->String.endsWith("/")
? pathname->String.slice(~start=0, ~end=String.length(pathname) - 1)
: pathname
}

let resolveFilePath = (pathname, ~dir, ~alias) => {
let pathname = pathname->normalizePathname
Comment thread
jderochervlk marked this conversation as resolved.
let path = if pathname->String.startsWith("/") {
pathname->String.slice(~start=1, ~end=String.length(pathname))
} else {
Expand Down
3 changes: 3 additions & 0 deletions apps/docs/src/markdown/MdxFile.resi
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ type fileData = {
frontmatter: JSON.t,
}

/** Removes React Router's `.data` request suffix from a route pathname. */
let normalizePathname: string => string

/** Maps a URL pathname to an .mdx file path on disk.
* e.g. `/blog/release-12-0-0` with ~dir="markdown-pages/blog" ~alias="blog"
* → `markdown-pages/blog/release-12-0-0.mdx`
Expand Down
36 changes: 18 additions & 18 deletions apps/guide/__tests__/GuideHome_.test.res
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ test("loads saved guide editor code into the editor", async () => {
GuideLayout.saveExerciseCode(~exerciseId, ~code="let sisko = \"emissary\"")

let screen = await renderGuideHome()
let editor = await screen->getByTestId("guide-code-editor")
let savedCode = await screen->getByText("let sisko = \"emissary\"")

await editor->element->toHaveTextContent("let sisko = \"emissary\"")
await savedCode->element->toBeVisible

GuideLayout.clearExerciseCode(exerciseId)
})
Expand All @@ -35,9 +35,10 @@ test("resets the current exercise code without clearing its completion", async (
await resetButton->element->toBeVisible
await resetButton->click

let editor = await screen->getByTestId("guide-code-editor")
await editor->element->toHaveTextContent(`let greet = name => "Hello, " ++ name ++ "!"`)
await editor->element->toHaveTextContent(`let greeting = greet("ReScript")`)
let greetCode = await screen->getByText(`let greet = name => "Hello, " ++ name ++ "!"`)
await greetCode->element->toBeVisible
let greetingCode = await screen->getByText(`let greeting = greet("ReScript")`)
await greetingCode->element->toBeVisible
expect(GuideLayout.loadExerciseCode(exerciseId)->Option.isNone)->toBe(true)
expect(GuideLayout.isExerciseCompleted(exerciseId))->toBe(true)

Expand Down Expand Up @@ -87,9 +88,9 @@ test("shows the first checkpoint as complete when output matches", async () => {
await viewport(1440, 900)

let screen = await renderGuideHome()
let checkpoint = await screen->getByTestId("guide-check-status")
let checkpoint = await screen->getByText("Checkpoint complete")

await checkpoint->element->toHaveTextContent("Checkpoint complete")
await checkpoint->element->toBeVisible
})

test("navigates to the function argument page", async () => {
Expand All @@ -106,11 +107,12 @@ test("navigates to the function argument page", async () => {
await (await screen->getByText("Change the argument passed to greet from ReScript to Spock."))
->element
->toBeVisible
let editor = await screen->getByTestId("guide-code-editor")
await editor->element->toHaveTextContent(`let greet = name => "Hello, " ++ name ++ "!"`)
await editor->element->toHaveTextContent(`let greeting = greet("ReScript")`)
let checkpoint = await screen->getByTestId("guide-check-status")
await checkpoint->element->toHaveTextContent("Waiting for matching output")
let greetCode = await screen->getByText(`let greet = name => "Hello, " ++ name ++ "!"`)
await greetCode->element->toBeVisible
let greetingCode = await screen->getByText(`let greeting = greet("ReScript")`)
await greetingCode->element->toBeVisible
let checkpoint = await screen->getByText("Waiting for matching output")
await checkpoint->element->toBeVisible

GuideLayout.clearExerciseCode(secondLesson.exercise.id)
})
Expand Down Expand Up @@ -256,11 +258,9 @@ test("renders the first guide MVP exercise and output", async () => {
->element
->toBeVisible
await (await screen->getByText("Next"))->element->toBeVisible
let editor = await screen->getByTestId("guide-code-editor")
await editor->element->toBeVisible
await editor->element->toHaveTextContent("let greeting = \"hello, world!\"")
let output = await screen->getByTestId("guide-output")
let editorCode = await screen->getByText("let greeting = \"hello, world!\"")
await editorCode->element->toBeVisible
let outputPanel = await screen->getByTestId("guide-output")
let output = await outputPanel->getByText("hello, world!")
await output->element->toBeVisible

await output->element->toHaveTextContent("hello, world!")
})
21 changes: 10 additions & 11 deletions apps/guide/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,16 @@
"@babel/generator": "^7.29.1",
"@babel/parser": "^7.29.2",
"@babel/types": "^7.29.0",
"@react-router/node": "^7.15.0",
"@react-router/node": "^8.3.1",
"@rescript-lang/playground": "workspace:*",
"@rescript-lang/shared": "workspace:*",
"@rescript/react": "^0.14.2",
"@rescript/webapi": "0.1.0-experimental-29db5f4",
"isbot": "^5",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react": "^19.3.0",
"react-dom": "^19.3.0",
"react-markdown": "^10.1.0",
"react-router": "^7.15.0",
"react-router-dom": "^7.15.0",
"react-router": "^8.3.1",
"remark-comment": "^1.0.0",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1",
Expand All @@ -37,16 +36,16 @@
"vfile-matter": "^5.0.1"
},
"devDependencies": {
"@react-router/dev": "^7.15.0",
"@react-router/dev": "^8.3.1",
"@types/react": "^19.2.14",
"@vitejs/plugin-react": "^6.0.1",
"@vitest/browser-playwright": "^4.1.5",
"@vitest/browser-playwright": "^5.0.0",
"lightningcss": "^1.32.0",
"playwright": "^1.59.1",
"vite": "^8.1.5",
"playwright": "^1.63.0",
"vite": "^8.3.0",
"vite-plugin-env-compatible": "^2.0.1",
"vitest": "^4.1.5",
"vitest-browser-react": "^2.2.0",
"vitest": "^5.0.0",
"vitest-browser-react": "^2.3.0",
"wrangler": "^4.130.0"
},
"engines": {
Expand Down
6 changes: 3 additions & 3 deletions packages/playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"@rescript/react": "^0.14.2",
"@rescript/webapi": "0.1.0-experimental-29db5f4",
"lz-string": "^1.5.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router": "^7.15.0"
"react": "^19.3.0",
"react-dom": "^19.3.0",
"react-router": "^8.3.1"
}
}
4 changes: 2 additions & 2 deletions packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
"@rescript/webapi": "0.1.0-experimental-29db5f4",
"@tsnobip/rescript-lezer": "^0.8.0",
"highlight.js": "^11.11.1",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react": "^19.3.0",
"react-dom": "^19.3.0",
"remark-comment": "^1.0.0",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1",
Expand Down
Loading
Loading