diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 32c88e252..5ea4b01ce 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -43,6 +43,10 @@ jobs: run: yarn ci:generated - name: Test examples and hrefs run: yarn test + - name: Check unused ReScript values and exports + run: yarn check:dead-code + - name: Test dead-code diagnostics + run: node --test scripts/__tests__/check-dead-code.test.mjs # Check the built Docs Pages routes and the Guide local dev server. Wrangler: runs-on: ubuntu-24.04 diff --git a/.gitignore b/.gitignore index 86bfbc802..4f7939a37 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,6 @@ apps/docs/public/blog/feed.xml node_modules/ out/ apps/docs/out/ -index_data/*.json -apps/docs/index_data/*.json # Generated via docs test examples script temp diff --git a/.oxfmtrc.json b/.oxfmtrc.json index aade3da38..7662b39ee 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -12,7 +12,6 @@ "build", "data/api/**/*.json", "dist", - "apps/docs/index_data/*.json", "node_modules/", "apps/docs/out/", "apps/docs/public/playground-bundles/", diff --git a/AGENTS.md b/AGENTS.md index cbf94c8d9..0bc80cdc9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -66,9 +66,9 @@ use the installed `rescript` package, not a separate compiler installation. - 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 PascalCase for new `.res` modules. Follow the existing filenames for framework entrypoints such as `root.res` and `routes.res`, and for script entrypoints. - 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`. +- Resolve all warnings before committing. `yarn build:res` applies the repository's strict warning checks. - Never directly edit generated `.jsx` files; edit the corresponding `.res` file and rebuild. Most compiler output is ignored by Git; the guide's `apps/guide/app/root.jsx` and `apps/guide/app/routes.jsx` are tracked and must be regenerated with their sources. ## ReScript Rules @@ -83,7 +83,7 @@ use the installed `rescript` package, not a separate compiler installation. - 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 - Never use `%raw` unless you are specifically asked to -- Never use `Object.magic` +- Never use `Obj.magic` - Don't add type annotations unless necessary for clarity or to resolve an error. ReScript's type inference is powerful, and often explicit annotations are not needed. ### ReScript Dependencies @@ -96,13 +96,18 @@ use the installed `rescript` package, not a separate compiler installation. The project uses several patterns for JavaScript interop. Follow the existing conventions: - **`@module` externals** for binding to npm packages (see `apps/docs/src/bindings/` and `packages/shared/src/` for examples): + ``` - @module("react-router") external useNavigate: unit => string => unit = "useNavigate" + type navigateOptions = {replace?: bool} + type navigate = (string, ~options: navigateOptions=?) => unit + + @module("react-router") external useNavigate: unit => navigate = "useNavigate" ``` + - **`@module` with `@react.component`** for binding to external React components: ``` @module("react-router") @react.component - external make: (~children: React.element) => React.element = "Outlet" + external make: unit => React.element = "Outlet" ``` - `%raw` and `%%raw` embed JavaScript; use them only when specifically requested. - **`@scope`** for binding to object properties like `process.env`. @@ -115,12 +120,12 @@ The project uses several patterns for JavaScript interop. Follow the existing co - The site uses hybrid rendering: most routes are pre-rendered during the build, while `/try` is server-rendered by Cloudflare Pages Functions at runtime. - Loaders for pre-rendered routes can access the filesystem during the build. Runtime SSR loaders, including shared loader code reachable from `/try`, execute in Cloudflare and must not depend on Node-only filesystem APIs or other build-time-only operations. - Docs route modules live in `apps/docs/app/routes/`; the guide's route modules live in `apps/guide/app/`. Routes can export a `loader`, `links`, and a `default` component as needed. -- 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. +- Give route modules a `.resi` interface that exposes their route exports and keeps helper components private. +- Export a route's page component as `default`. Reusable component modules may export multiple components, as `Markdown.resi` does. ### Route Module Pattern -Every route follows this pattern: +Example of a route with a loader. Routes without loader data can omit the loader and its type: ```rescript // SomeRoute.res @@ -197,7 +202,7 @@ let default: unit => React.element ``` - Components requiring React Router context must be wrapped in ``. -- Do not use `` in tests. +- Do not use `` in component tests. End-to-end tests exercise the app's normal routing and browser history. - Run `yarn build:res` first so the tests use current compiler output. Run `yarn vitest --browser.headless --run` for docs tests or `yarn vitest:guide --browser.headless --run` for guide tests. Run `yarn ci:test` to check both suites. - Do not update snapshots without asking for confirmation. Approved docs visual baseline changes use the `Update Visual Regression Screenshots` GitHub Actions workflow against the feature branch; pull and review the generated commit. Local screenshot updates are blocked by the Vitest configuration. @@ -229,4 +234,4 @@ 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 `apps/docs/src/ffi/` directory is legacy; prefer bindings in `apps/docs/src/bindings/` or `packages/shared/src/` for new JS interop. -- When editing route files, always update both the `.res` and `.resi` files. +- Keep route `.resi` files in sync when public exports or their types change. Implementation-only edits need only change the `.res` file. diff --git a/README.md b/README.md index fc5a91433..bd4578c9e 100644 --- a/README.md +++ b/README.md @@ -94,28 +94,44 @@ Tailwind v4 uses the Vite plugin and [`apps/docs/styles/main.css`](apps/docs/sty The [recommended VS Code extensions](.vscode/extensions.json) provide ReScript support and [Oxfmt editor integration](https://oxc.rs/docs/guide/usage/formatter/editors.html). Oxfmt configuration lives in [`.oxfmtrc.json`](.oxfmtrc.json). +## Dead-code checks + +`yarn check:dead-code` compiles every workspace and runs reanalyze across the +monorepo, including FFI exports. CI fails on unused values and exports. Bindings +whose names start with `_` are treated as intentional discards. Record fields, +variant cases, and argument diagnostics remain advisory because JavaScript and +FFI access can be invisible to the analyzer; inspect them with `yarn reanalyze`. + +React Router and Cloudflare call exports from JavaScript. Their entry points are +marked `@live` individually in the `.resi` interface, or in `.res` when there is +no interface. Keep these annotations limited to real entry points and do not +duplicate them between files; check JavaScript imports and MDX mappings before +removing a reported export. + ## Common Commands Run these commands from the repository root. To target a workspace, use `yarn workspace `; the workspace names are `@rescript-lang/docs`, `@rescript-lang/guide`, `@rescript-lang/playground`, and `@rescript-lang/shared`. -| Command | Purpose | -| -------------------- | ---------------------------------------------------------------- | -| `yarn dev` | Prepare generated files and run the docs development environment | -| `yarn build` | Build and pre-render the docs site | -| `yarn build:guide` | Build and pre-render the interactive guide | -| `yarn preview` | Build and serve the generated docs client locally | -| `yarn build:res` | Generate route types and compile all four workspaces | -| `yarn build:routes` | Regenerate the shared ReScript route type | -| `yarn check:routes` | Check that the shared route type matches the current routes | -| `yarn dev:res` | Run the ReScript compiler in watch mode | -| `yarn format` | Run Oxfmt and format ReScript across all four workspaces | -| `yarn test` | Run docs markdown example, href, and redirect validation | -| `yarn ci:test` | Run docs and guide Vitest browser tests headlessly | -| `yarn vitest` | Run docs Vitest browser tests in watch mode | -| `yarn vitest:guide` | Run guide Vitest browser tests in watch mode | -| `yarn vitest:update` | Update screenshot baselines headlessly | +| Command | Purpose | +| ------------------- | ---------------------------------------------------------------- | +| `yarn dev` | Prepare generated files and run the docs development environment | +| `yarn build` | Build and pre-render the docs site | +| `yarn build:guide` | Build and pre-render the interactive guide | +| `yarn preview` | Build and serve the generated docs client locally | +| `yarn build:res` | Generate route types and compile all four workspaces | +| `yarn build:routes` | Regenerate the shared ReScript route type | +| `yarn check:routes` | Check that the shared route type matches the current routes | +| `yarn dev:res` | Run the ReScript compiler in watch mode | +| `yarn format` | Run Oxfmt and format ReScript across all four workspaces | +| `yarn test` | Run docs markdown example, href, and redirect validation | +| `yarn ci:test` | Run docs and guide Vitest browser tests headlessly | +| `yarn vitest` | Run docs Vitest browser tests in watch mode | +| `yarn vitest:guide` | Run guide Vitest browser tests in watch mode | +| `yarn cy:open` | Open the full-site Cypress suite against the running dev server | +| `yarn cy:e2e` | Run the full-site Cypress suite headlessly in Chrome | +| `yarn test:e2e` | Build and run homepage Cypress tests with a temporary server | ## Testing @@ -161,13 +177,39 @@ yarn ci:test For a headless run of a single suite, use `yarn vitest --browser.headless --run` for docs or `yarn vitest:guide --browser.headless --run` for the guide. -To update screenshot baselines, run: +Visual screenshot baselines are generated in CI's pinned Linux browser environment. Local snapshot updates are blocked. Push your feature branch, then open [Update Visual Regression Screenshots](https://github.com/rescript-lang/rescript-lang.org/actions/workflows/update-visual-screenshots.yml), select that branch, and choose **Run workflow**. The workflow refuses `master` and commits any changed PNGs back to the selected branch. + +You can also dispatch the workflow with the GitHub CLI, replacing `` with your pushed branch: + +```sh +gh workflow run update-visual-screenshots.yml --repo rescript-lang/rescript-lang.org --ref +``` + +After the workflow finishes, pull its commit and review the screenshot diff. Only accept screenshots intentionally affected by your change. + +### Cypress E2E Tests + +The full-site suite covers documentation, navigation, and the playground. Start the local development server in one terminal: + +```sh +yarn dev +``` + +Wait for Vite to report its local URL. In another terminal, run one of these commands: + +```sh +yarn cy:open # Interactive Cypress runner (Electron) +yarn cy:run # Headless run (Electron) +yarn cy:e2e # Headless run (Chrome must be installed) +``` + +These commands compile the ReScript specs and default to `http://localhost:5173`. If Vite reports another port, or you want to test a deployed site, pass its URL explicitly: ```sh -yarn vitest:update +yarn cy:e2e --config baseUrl=http://localhost:5174 ``` -Only update screenshots that are intentionally affected by your change. +The homepage suite uses the production build and its own configuration. `yarn test:e2e` builds the site, starts a temporary server at `http://127.0.0.1:4173`, runs the homepage specs in Chrome, and stops the server when the tests finish. It does not require `yarn dev`. ### Markdown Example and Link Checks diff --git a/apps/docs/__tests__/Url_.test.res b/apps/docs/__tests__/Url_.test.res deleted file mode 100644 index fd29c2185..000000000 --- a/apps/docs/__tests__/Url_.test.res +++ /dev/null @@ -1,29 +0,0 @@ -open Vitest - -test("Url.parse splits an unversioned route into path segments", async () => { - let result = Url.parse("/docs/manual/introduction") - expect(result.base)->toEqual(["docs", "manual", "introduction"]) - expect(result.pagepath)->toEqual([]) - expect(result.fullpath)->toEqual(["docs", "manual", "introduction"]) -}) - -test("Url.parse treats version-like segments as ordinary path content", async () => { - let result = Url.parse("/docs/manual/v12.0.0/introduction") - expect(result.base)->toEqual(["docs", "manual", "v12.0.0", "introduction"]) - expect(result.pagepath)->toEqual([]) - expect(result.fullpath)->toEqual(["docs", "manual", "v12.0.0", "introduction"]) -}) - -test("Url.parse treats latest as ordinary path content", async () => { - let result = Url.parse("/docs/manual/latest/arrays") - expect(result.base)->toEqual(["docs", "manual", "latest", "arrays"]) - expect(result.pagepath)->toEqual([]) - expect(result.fullpath)->toEqual(["docs", "manual", "latest", "arrays"]) -}) - -test("Url.parse parses routes outside docs without special handling", async () => { - let result = Url.parse("/community/overview") - expect(result.base)->toEqual(["community", "overview"]) - expect(result.pagepath)->toEqual([]) - expect(result.fullpath)->toEqual(["community", "overview"]) -}) diff --git a/apps/docs/app/DocsRoot.resi b/apps/docs/app/DocsRoot.resi index e9a4c6ad1..cde121a48 100644 --- a/apps/docs/app/DocsRoot.resi +++ b/apps/docs/app/DocsRoot.resi @@ -1,5 +1,5 @@ /** Shared shell, token styles, and the stable Cypress bootstrap slot. Runtime grammars belong to content routes and the playground. */ /** Keep server-rendered content visible; head stylesheets handle render blocking. */ -@react.component +@react.component @live let default: unit => Jsx.element diff --git a/apps/docs/app/layouts/ContentLayoutRoute.resi b/apps/docs/app/layouts/ContentLayoutRoute.resi index 381da2b21..d11cb3818 100644 --- a/apps/docs/app/layouts/ContentLayoutRoute.resi +++ b/apps/docs/app/layouts/ContentLayoutRoute.resi @@ -1,7 +1,8 @@ type stylesheet = {rel: string, href: string} +@live let links: unit => array /** Own content styles and runtime highlighting without adding a DOM wrapper. */ -@react.component +@react.component @live let default: unit => React.element diff --git a/apps/docs/app/layouts/DocsLayoutRoute.resi b/apps/docs/app/layouts/DocsLayoutRoute.resi index 47fa1ac2e..30cde7b0b 100644 --- a/apps/docs/app/layouts/DocsLayoutRoute.resi +++ b/apps/docs/app/layouts/DocsLayoutRoute.resi @@ -1,2 +1,2 @@ -@react.component +@react.component @live let default: unit => React.element diff --git a/apps/docs/app/layouts/HomepageLayoutRoute.resi b/apps/docs/app/layouts/HomepageLayoutRoute.resi index bf0517cf9..61ed8faa7 100644 --- a/apps/docs/app/layouts/HomepageLayoutRoute.resi +++ b/apps/docs/app/layouts/HomepageLayoutRoute.resi @@ -1,7 +1,8 @@ type stylesheet = {rel: string, href: string} +@live let links: unit => array /** Own homepage styles without adding a DOM wrapper. */ -@react.component +@react.component @live let default: unit => React.element diff --git a/apps/docs/app/routes/ApiDocs.res b/apps/docs/app/routes/ApiDocs.res index ba0430536..0b4ea59f3 100644 --- a/apps/docs/app/routes/ApiDocs.res +++ b/apps/docs/app/routes/ApiDocs.res @@ -258,7 +258,8 @@ module DocstringsStylize = { } } -@react.componentWithProps +// reanalyze does not see the JSX props spread call in ApiRoute. +@live @react.componentWithProps let make = (props: props) => { let children = { open Markdown diff --git a/apps/docs/app/routes/ApiOverviewRoute.resi b/apps/docs/app/routes/ApiOverviewRoute.resi index 44e515171..6d1265c42 100644 --- a/apps/docs/app/routes/ApiOverviewRoute.resi +++ b/apps/docs/app/routes/ApiOverviewRoute.resi @@ -1,11 +1,11 @@ -let categories: array - type loaderData = { compiledMdx: CompiledMdx.t, title: string, description: string, } +@live let loader: ReactRouter.Loader.t +@live let default: unit => React.element diff --git a/apps/docs/app/routes/ApiRoute.resi b/apps/docs/app/routes/ApiRoute.resi index aab0723f3..2f73ea401 100644 --- a/apps/docs/app/routes/ApiRoute.resi +++ b/apps/docs/app/routes/ApiRoute.resi @@ -1,6 +1,8 @@ type loaderData = ApiDocs.props /** Loads page content and navigation from the same published compiler dataset. */ +@live let loader: ReactRouter.Loader.t +@live let default: unit => React.element diff --git a/apps/docs/app/routes/BlogArticleRoute.resi b/apps/docs/app/routes/BlogArticleRoute.resi index 40ba9580c..c459baab4 100644 --- a/apps/docs/app/routes/BlogArticleRoute.resi +++ b/apps/docs/app/routes/BlogArticleRoute.resi @@ -4,6 +4,8 @@ type loaderData = { title: string, } +@live let loader: ReactRouter.Loader.t +@live let default: unit => React.element diff --git a/apps/docs/app/routes/BlogRoute.resi b/apps/docs/app/routes/BlogRoute.resi index 34ac9a2a6..d3a21e01a 100644 --- a/apps/docs/app/routes/BlogRoute.resi +++ b/apps/docs/app/routes/BlogRoute.resi @@ -1,6 +1,7 @@ type loaderData = {posts: array, category: Blog.category} +@live let loader: ReactRouter.Loader.t -@react.component +@react.component @live let default: unit => React.element diff --git a/apps/docs/app/routes/BrandRoute.resi b/apps/docs/app/routes/BrandRoute.resi index 5f02c123c..453551df9 100644 --- a/apps/docs/app/routes/BrandRoute.resi +++ b/apps/docs/app/routes/BrandRoute.resi @@ -1 +1,2 @@ +@live let default: unit => React.element diff --git a/apps/docs/app/routes/CommunityRoute.resi b/apps/docs/app/routes/CommunityRoute.resi index 456908ac9..7a46a4347 100644 --- a/apps/docs/app/routes/CommunityRoute.resi +++ b/apps/docs/app/routes/CommunityRoute.resi @@ -7,6 +7,8 @@ type loaderData = { categories: array, } +@live let loader: ReactRouter.Loader.t +@live let default: unit => React.element diff --git a/apps/docs/app/routes/DocsGuidesRoute.resi b/apps/docs/app/routes/DocsGuidesRoute.resi index 6eea0299c..1f083c417 100644 --- a/apps/docs/app/routes/DocsGuidesRoute.resi +++ b/apps/docs/app/routes/DocsGuidesRoute.resi @@ -7,6 +7,8 @@ type loaderData = { filePath: string, } +@live let loader: ReactRouter.Loader.t +@live let default: unit => React.element diff --git a/apps/docs/app/routes/DocsManualRoute.resi b/apps/docs/app/routes/DocsManualRoute.resi index 6eea0299c..1f083c417 100644 --- a/apps/docs/app/routes/DocsManualRoute.resi +++ b/apps/docs/app/routes/DocsManualRoute.resi @@ -7,6 +7,8 @@ type loaderData = { filePath: string, } +@live let loader: ReactRouter.Loader.t +@live let default: unit => React.element diff --git a/apps/docs/app/routes/DocsReactRoute.resi b/apps/docs/app/routes/DocsReactRoute.resi index 6eea0299c..1f083c417 100644 --- a/apps/docs/app/routes/DocsReactRoute.resi +++ b/apps/docs/app/routes/DocsReactRoute.resi @@ -7,6 +7,8 @@ type loaderData = { filePath: string, } +@live let loader: ReactRouter.Loader.t +@live let default: unit => React.element diff --git a/apps/docs/app/routes/LandingPageRoute.resi b/apps/docs/app/routes/LandingPageRoute.resi index 1c6062553..c1ab3df4e 100644 --- a/apps/docs/app/routes/LandingPageRoute.resi +++ b/apps/docs/app/routes/LandingPageRoute.resi @@ -8,12 +8,14 @@ type fontPreload = { } /** Preload above-the-fold faces, including Inter 700 only at the desktop breakpoint. */ +@live let links: unit => array type loaderData = {playgroundData: LandingPagePlayground.playgroundData} /** Prerender the fixed example so highlighting and URL compression stay off the client. */ +@live let loader: ReactRouter.Loader.t -@react.component +@react.component @live let default: unit => React.element diff --git a/apps/docs/app/routes/NotFoundRoute.resi b/apps/docs/app/routes/NotFoundRoute.resi index efddc2079..dbe44edf2 100644 --- a/apps/docs/app/routes/NotFoundRoute.resi +++ b/apps/docs/app/routes/NotFoundRoute.resi @@ -1,5 +1,7 @@ +@live let loader: ReactRouter.Loader.t> +@live let clientLoader: ReactRouter.Loader.t -@react.component +@react.component @live let default: unit => React.element diff --git a/apps/docs/app/routes/PackagesRoute.resi b/apps/docs/app/routes/PackagesRoute.resi index fc06a02b0..4da28a456 100644 --- a/apps/docs/app/routes/PackagesRoute.resi +++ b/apps/docs/app/routes/PackagesRoute.resi @@ -1,2 +1,4 @@ +@live let loader: unit => promise +@live let default: unit => React.element diff --git a/apps/docs/app/routes/SyntaxLookup.res b/apps/docs/app/routes/SyntaxLookup.res index fdddb1a60..2c88212aa 100644 --- a/apps/docs/app/routes/SyntaxLookup.res +++ b/apps/docs/app/routes/SyntaxLookup.res @@ -51,16 +51,6 @@ module Category = { | _ => Other } } - - @react.component - let make = (~title, ~children) => { -
-

- {React.string(title)} -

-
children
-
- } } module Status = { @@ -119,15 +109,6 @@ module Item = { } } -type itemInfo = { - id: string, - keywords: array, - name: string, - summary: string, - category: Category.t, - status: Status.t, -} - module Tag = { @react.component let make = (~deprecated: bool, ~text: string, ~href) => { @@ -174,40 +155,6 @@ type state = | ShowFiltered(string, array) // (search, filteredItems) | ShowDetails(Item.t) -let scrollToTop = () => WebAPI.Window.scrollTo(window, ~options={left: 0.0, top: 0.0}) - -type params = {slug: string} - -let decode = (json: JSON.t) => { - open JSON - switch json { - | Object(dict{ - "id": String(id), - "keywords": Array(keywords), - "name": String(name), - "summary": String(summary), - "category": String(category), - "status": ?status, - }) => { - id, - name, - summary, - category: Category.fromString(category), - keywords: keywords->Array.filterMap(k => - switch k { - | String(k) => Some(k) - | _ => None - } - ), - status: switch status { - | Some(String(status)) => status->Status.fromString - | _ => Status.Active - }, - } - | _ => throw(Failure(`Failed to decode SyntaxLookup. ${__LOC__}`)) - } -} - type item = { id: string, keywords: array, diff --git a/apps/docs/app/routes/SyntaxLookupDetailRoute.resi b/apps/docs/app/routes/SyntaxLookupDetailRoute.resi index 5436eda52..e6b480cac 100644 --- a/apps/docs/app/routes/SyntaxLookupDetailRoute.resi +++ b/apps/docs/app/routes/SyntaxLookupDetailRoute.resi @@ -6,6 +6,8 @@ type loaderData = { description: string, } +@live let loader: ReactRouter.Loader.t +@live let default: unit => React.element diff --git a/apps/docs/app/routes/SyntaxLookupRoute.resi b/apps/docs/app/routes/SyntaxLookupRoute.resi index fa1131538..e7f2f6f88 100644 --- a/apps/docs/app/routes/SyntaxLookupRoute.resi +++ b/apps/docs/app/routes/SyntaxLookupRoute.resi @@ -1,5 +1,7 @@ type loaderData = {mdxSources: array} +@live let loader: ReactRouter.Loader.t +@live let default: unit => React.element // TODO: move this out of the route file so it can export only a single react component let convert: Mdx.attributes => SyntaxLookup.item diff --git a/apps/docs/app/routes/TryRoute.resi b/apps/docs/app/routes/TryRoute.resi index 65e640435..9285f0b75 100644 --- a/apps/docs/app/routes/TryRoute.resi +++ b/apps/docs/app/routes/TryRoute.resi @@ -1,5 +1,6 @@ type stylesheet = {rel: string, href: string} +@live let links: unit => array type props = { @@ -7,6 +8,8 @@ type props = { versions: array, } +@live let loader: unit => promise> +@live let default: unit => React.element diff --git a/apps/docs/compilers/README.md b/apps/docs/compilers/README.md deleted file mode 100644 index ceebd0b28..000000000 --- a/apps/docs/compilers/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# compilers - -This folder is needed to install additional rescript compiler versions for testing our documented source code on multiple compiler versions. - -Our `scripts/test-examples.mjs` script will automatically install all package.json dependencies if it cannot find any compiler versions. diff --git a/apps/docs/compilers/dummy/Dummy.res b/apps/docs/compilers/dummy/Dummy.res deleted file mode 100644 index 4992ee3d2..000000000 --- a/apps/docs/compilers/dummy/Dummy.res +++ /dev/null @@ -1 +0,0 @@ -Console.debug("I am a dummy file") diff --git a/apps/docs/compilers/package-lock.json b/apps/docs/compilers/package-lock.json deleted file mode 100644 index 9a20b57aa..000000000 --- a/apps/docs/compilers/package-lock.json +++ /dev/null @@ -1,287 +0,0 @@ -{ - "name": "compilers", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "compilers", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "@rescript/core": "^1.3.0", - "@rescript/react": "^0.12.0", - "rescript-1000": "npm:rescript@10.0.0", - "rescript-1010": "npm:rescript@10.1.0", - "rescript-1100": "npm:rescript@11.0.0", - "rescript-1110": "npm:rescript@11.1.0", - "rescript-820": "npm:bs-platform@8.2.0", - "rescript-902": "npm:bs-platform@9.0.2", - "rescript-912": "npm:rescript@9.1.2" - } - }, - "node_modules/@rescript/core": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rescript/core/-/core-1.3.0.tgz", - "integrity": "sha512-wNZOZ63sYcaIYZCmTZeIPCeLa3HCGgPbIOR8zjyNkoBYUlxNV8Nb2ZyqlXR5Mb9ttvv8fTV56JbKhyVEZEYo8g==", - "peerDependencies": { - "rescript": "^11.1.0-rc.7" - } - }, - "node_modules/@rescript/react": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@rescript/react/-/react-0.12.0.tgz", - "integrity": "sha512-EBLsf5rD7sJOjgfLLGwuLw/hONszc3UtYnIVgv7OdTyUNR41/m4deVm62PI0agvr3kWakXz4KchKRSd+19/bRA==", - "peerDependencies": { - "react": ">=18.0.0", - "react-dom": ">=18.0.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "peer": true - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "peer": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - }, - "peerDependencies": { - "react": "^18.2.0" - } - }, - "node_modules/rescript": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/rescript/-/rescript-11.1.1.tgz", - "integrity": "sha512-FMELeoiR1n3LzBdBt+k7U4l0vsz5Xh0HBSHf+0NhyhzZkMRLkEKEDNrcqZc6RIux9bxmxoO+zEa9qFM01VOXAw==", - "hasInstallScript": true, - "peer": true, - "bin": { - "bsc": "bsc", - "bstracing": "lib/bstracing", - "rescript": "rescript" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/rescript-1000": { - "name": "rescript", - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/rescript/-/rescript-10.0.0.tgz", - "integrity": "sha512-LhNg/4+0j8NvoFeslgAeYLlzUwkq6kR6l6v8BnZ61VDTxopK2l96uT1lq5lv1aMxzMDynvE2qnX0zalre+6XxA==", - "hasInstallScript": true, - "bin": { - "bsc": "bsc", - "bsrefmt": "bsrefmt", - "bstracing": "lib/bstracing", - "rescript": "rescript" - } - }, - "node_modules/rescript-1010": { - "name": "rescript", - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/rescript/-/rescript-10.1.0.tgz", - "integrity": "sha512-85KLtBECo+hYwObbifXk5xpxUCUFMWgCR1w7hDDGhhXIXqEpHQ2ds6o++YPxzXjLuU1/qA7Iovq2zUX4opejQw==", - "hasInstallScript": true, - "bin": { - "bsc": "bsc", - "bsrefmt": "bsrefmt", - "bstracing": "lib/bstracing", - "rescript": "rescript" - } - }, - "node_modules/rescript-1100": { - "name": "rescript", - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/rescript/-/rescript-11.0.0.tgz", - "integrity": "sha512-uIUwDZZmDUb7ymGkBiiGioxMg8hXh1mze/2k/qhYQcZGgi7PrLHQIW9AksM7gb9WnpjCAvFsA8U2VgC0nA468w==", - "hasInstallScript": true, - "bin": { - "bsc": "bsc", - "bstracing": "lib/bstracing", - "rescript": "rescript" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/rescript-1110": { - "name": "rescript", - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/rescript/-/rescript-11.1.0.tgz", - "integrity": "sha512-9la2Dv+ACylQ77I8s4spPu1JnLZXbH5WgxcLHLLUBWgFFSiv0wXqgzWztrBIZqwFgVX5BYcwldUqUVcEzdCyHg==", - "hasInstallScript": true, - "bin": { - "bsc": "bsc", - "bstracing": "lib/bstracing", - "rescript": "rescript" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/rescript-820": { - "name": "bs-platform", - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/bs-platform/-/bs-platform-8.2.0.tgz", - "integrity": "sha512-quvmUac/ZxGDsT7L5+6RNXrLPvLHkWFownacaqlwVoyAm770bPyupTRU49ALPGk3HpjfD5eE+lpGdOSPtuwJiA==" - }, - "node_modules/rescript-902": { - "name": "bs-platform", - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/bs-platform/-/bs-platform-9.0.2.tgz", - "integrity": "sha512-Ye9JqJ4Oa7mcjjoOVRYI8Uc2Cf8N7jQLWDcdUplY7996d/YErSR7WitmV7XnSwr4EvdrbwjEsg1NxNjUQv3ChA==" - }, - "node_modules/rescript-912": { - "name": "rescript", - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/rescript/-/rescript-9.1.2.tgz", - "integrity": "sha512-4wHvTDv3nyYnAPJHcg1RGG8z7u3HDiBf6RN3P/dITDv859Qo35aKOzJWQtfBzbAs0EKNafLqei3TnUqiAv6BwQ==", - "hasInstallScript": true, - "bin": { - "bsc": "bsc", - "bsrefmt": "bsrefmt", - "bstracing": "lib/bstracing", - "rescript": "rescript" - } - }, - "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - } - } - }, - "dependencies": { - "@rescript/core": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rescript/core/-/core-1.3.0.tgz", - "integrity": "sha512-wNZOZ63sYcaIYZCmTZeIPCeLa3HCGgPbIOR8zjyNkoBYUlxNV8Nb2ZyqlXR5Mb9ttvv8fTV56JbKhyVEZEYo8g==", - "requires": {} - }, - "@rescript/react": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@rescript/react/-/react-0.12.0.tgz", - "integrity": "sha512-EBLsf5rD7sJOjgfLLGwuLw/hONszc3UtYnIVgv7OdTyUNR41/m4deVm62PI0agvr3kWakXz4KchKRSd+19/bRA==", - "requires": {} - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "peer": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "peer": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "peer": true, - "requires": { - "loose-envify": "^1.1.0" - } - }, - "react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "peer": true, - "requires": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - } - }, - "rescript": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/rescript/-/rescript-11.1.1.tgz", - "integrity": "sha512-FMELeoiR1n3LzBdBt+k7U4l0vsz5Xh0HBSHf+0NhyhzZkMRLkEKEDNrcqZc6RIux9bxmxoO+zEa9qFM01VOXAw==", - "peer": true - }, - "rescript-1000": { - "version": "npm:rescript@10.0.0", - "resolved": "https://registry.npmjs.org/rescript/-/rescript-10.0.0.tgz", - "integrity": "sha512-LhNg/4+0j8NvoFeslgAeYLlzUwkq6kR6l6v8BnZ61VDTxopK2l96uT1lq5lv1aMxzMDynvE2qnX0zalre+6XxA==" - }, - "rescript-1010": { - "version": "npm:rescript@10.1.0", - "resolved": "https://registry.npmjs.org/rescript/-/rescript-10.1.0.tgz", - "integrity": "sha512-85KLtBECo+hYwObbifXk5xpxUCUFMWgCR1w7hDDGhhXIXqEpHQ2ds6o++YPxzXjLuU1/qA7Iovq2zUX4opejQw==" - }, - "rescript-1100": { - "version": "npm:rescript@11.0.0", - "resolved": "https://registry.npmjs.org/rescript/-/rescript-11.0.0.tgz", - "integrity": "sha512-uIUwDZZmDUb7ymGkBiiGioxMg8hXh1mze/2k/qhYQcZGgi7PrLHQIW9AksM7gb9WnpjCAvFsA8U2VgC0nA468w==" - }, - "rescript-1110": { - "version": "npm:rescript@11.1.0", - "resolved": "https://registry.npmjs.org/rescript/-/rescript-11.1.0.tgz", - "integrity": "sha512-9la2Dv+ACylQ77I8s4spPu1JnLZXbH5WgxcLHLLUBWgFFSiv0wXqgzWztrBIZqwFgVX5BYcwldUqUVcEzdCyHg==" - }, - "rescript-820": { - "version": "npm:bs-platform@8.2.0", - "resolved": "https://registry.npmjs.org/bs-platform/-/bs-platform-8.2.0.tgz", - "integrity": "sha512-quvmUac/ZxGDsT7L5+6RNXrLPvLHkWFownacaqlwVoyAm770bPyupTRU49ALPGk3HpjfD5eE+lpGdOSPtuwJiA==" - }, - "rescript-902": { - "version": "npm:bs-platform@9.0.2", - "resolved": "https://registry.npmjs.org/bs-platform/-/bs-platform-9.0.2.tgz", - "integrity": "sha512-Ye9JqJ4Oa7mcjjoOVRYI8Uc2Cf8N7jQLWDcdUplY7996d/YErSR7WitmV7XnSwr4EvdrbwjEsg1NxNjUQv3ChA==" - }, - "rescript-912": { - "version": "npm:rescript@9.1.2", - "resolved": "https://registry.npmjs.org/rescript/-/rescript-9.1.2.tgz", - "integrity": "sha512-4wHvTDv3nyYnAPJHcg1RGG8z7u3HDiBf6RN3P/dITDv859Qo35aKOzJWQtfBzbAs0EKNafLqei3TnUqiAv6BwQ==" - }, - "scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "peer": true, - "requires": { - "loose-envify": "^1.1.0" - } - } - } -} diff --git a/apps/docs/compilers/package.json b/apps/docs/compilers/package.json deleted file mode 100644 index 1cf8ac044..000000000 --- a/apps/docs/compilers/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "compilers", - "version": "1.0.0", - "private": true, - "license": "MIT", - "main": "index.js", - "dependencies": { - "@rescript/core": "^1.3.0", - "@rescript/react": "^0.12.0", - "rescript-1000": "npm:rescript@10.0.0", - "rescript-1010": "npm:rescript@10.1.0", - "rescript-1100": "npm:rescript@11.0.0", - "rescript-1110": "npm:rescript@11.1.0", - "rescript-820": "npm:bs-platform@8.2.0", - "rescript-902": "npm:bs-platform@9.0.2", - "rescript-912": "npm:rescript@9.1.2" - } -} diff --git a/apps/docs/compilers/rescript.json b/apps/docs/compilers/rescript.json deleted file mode 100644 index f135476c7..000000000 --- a/apps/docs/compilers/rescript.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "dummy", - "jsx": { "version": 4 }, - "sources": { - "dir": "dummy", - "subdirs": true - }, - "bs-dependencies": ["@rescript/core", "@rescript/react"], - "bsc-flags": ["-open RescriptCore"] -} diff --git a/apps/docs/cypress.config.mjs b/apps/docs/cypress.config.mjs index 613b3f9db..fbe629e46 100644 --- a/apps/docs/cypress.config.mjs +++ b/apps/docs/cypress.config.mjs @@ -7,7 +7,7 @@ export default defineConfig({ openMode: 0, }, e2e: { - baseUrl: "http://localhost:8080", + baseUrl: "http://localhost:5173", specPattern: "e2e/**/*.cy.jsx", excludeSpecPattern: "e2e/homepage/**", supportFile: "cypress/support/e2e.js", diff --git a/apps/docs/e2e/bindings/Cypress.res b/apps/docs/e2e/bindings/Cypress.res index a191c7679..02c77791d 100644 --- a/apps/docs/e2e/bindings/Cypress.res +++ b/apps/docs/e2e/bindings/Cypress.res @@ -86,7 +86,6 @@ external interceptDeferredRequest: (routeMatcher, request => promise) => c @val @scope("cy") external onBeforeLoad: (@as("window:before:load") _, window => unit) => unit = "on" @val @scope("cy") external spy: (console, @as("error") _) => spy = "spy" -@get external callCount: spy => int = "callCount" @val @scope("Cypress") external automate: (@as("remote:debugger:protocol") _, automation) => promise = "automation" @val @scope("Cypress") external baseUrl: @as("baseUrl") _ => string = "config" diff --git a/apps/docs/e2e/homepage/HomepageRouteCss.cy.res b/apps/docs/e2e/homepage/HomepageRouteCss.cy.res index 1b3b679d4..222cdc73d 100644 --- a/apps/docs/e2e/homepage/HomepageRouteCss.cy.res +++ b/apps/docs/e2e/homepage/HomepageRouteCss.cy.res @@ -129,6 +129,13 @@ it("desktop navigation keeps its responsive logo across route stylesheets", () = it("documentation styles are prefetched only after navigation intent", () => { visit("/") + // The prerendered link is visible before its focus handler is attached. Wait + // for the client router and a React state update before focusing it. + cyWindow()->shouldPropertyExist("__reactRouterDataRouter")->ignore + get(`button[aria-label="Show community photo 2"]`) + ->click + ->shouldAttribute("aria-pressed", "true") + ->ignore let prefetch = `link[rel="prefetch"][as="style"][href*="/content-"]` get(prefetch)->should("not.exist")->ignore containsInRegex("a", /^Get started$/)->focusElement->ignore diff --git a/apps/docs/functions/ogimage/[[path]]/index.png.res b/apps/docs/functions/ogimage/[[path]]/index.png.res index 7b5d6f480..8191170d8 100644 --- a/apps/docs/functions/ogimage/[[path]]/index.png.res +++ b/apps/docs/functions/ogimage/[[path]]/index.png.res @@ -252,6 +252,7 @@ let renderImage = async (~assets, ~requestUrl: URLAPI.url, ~targetUrl: URLAPI.ur } } +@live let onRequest = async ({request, params, env}: context) => { let requestUrl = URL.make(~url=request.url) diff --git a/apps/docs/package.json b/apps/docs/package.json index 761b69db1..d781bd29e 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -30,20 +30,18 @@ "prepare": "yarn build:res && yarn build:scripts && yarn check:algolia-public-env && yarn build:update-index", "preview": "yarn build && static-server build/client", "serve:build": "static-server --host 127.0.0.1 --port 4173 build/client", - "reanalyze": "rescript-tools reanalyze -all-cmt .", + "reanalyze": "cd ../.. && yarn reanalyze", "test": "yarn build:res && yarn build:scripts && yarn build:generate-llms && node scripts/test.mjs", "test:e2e": "yarn build && bash ../../.github/scripts/homepage-e2e.sh", "cy:open": "yarn build:res && cypress open --e2e --browser electron", "cy:run": "yarn build:res && cypress run", "cy:e2e": "yarn build:res && cypress run --browser chrome", "vitest": "vitest", - "vitest:visual": "VISUAL_TESTS=1 vitest", - "vitest:update": "VISUAL_TESTS=1 VISUAL_BASELINE_UPDATE=1 vitest --run --browser.headless --update" + "vitest:visual": "VISUAL_TESTS=1 vitest" }, "dependencies": { "@cloudflare/pages-plugin-vercel-og": "^0.1.2", "@docsearch/react": "^4.6.2", - "@headlessui/react": "^2.2.9", "@mdx-js/mdx": "^3.1.1", "@node-cli/static-server": "^3.1.10", "@react-router/node": "^8.3.1", @@ -58,12 +56,10 @@ "isbot": "^5.1.37", "mdast-util-from-markdown": "^2.0.3", "mdast-util-to-string": "^4.0.0", - "mdast-util-toc": "^7.1.0", "react": "^19.3.0", "react-dom": "^19.3.0", "react-router": "^8.3.1", "rehype-slug": "^6.0.0", - "rehype-stringify": "^10.0.1", "remark": "^15.0.1", "remark-comment": "^1.0.0", "remark-frontmatter": "^5.0.0", @@ -86,7 +82,6 @@ "@vitest/browser-playwright": "^5.0.0", "auto-image-converter": "^2.2.0", "babel-plugin-react-compiler": "^1.0.0", - "chokidar": "^4.0.3", "cypress": "^15.13.1", "cypress-real-events": "^1.15.1", "dotenv": "^16.6.1", diff --git a/apps/docs/scripts/__tests__/BlogFeed_.test.res b/apps/docs/scripts/__tests__/BlogFeed_.test.res index 94a489811..e1680ff7d 100644 --- a/apps/docs/scripts/__tests__/BlogFeed_.test.res +++ b/apps/docs/scripts/__tests__/BlogFeed_.test.res @@ -15,6 +15,11 @@ let blogDirectory = async () => { let directory = await temporaryDirectory("blog-feed-") let blog = join([directory, "markdown-pages", "blog"]) await mkdir(join([blog, "archived"]), {recursive: true}) + let _ = await Promise.all( + ["community", "docs/manual", "docs/guides", "docs/react", "syntax-lookup"]->Array.map(prefix => + mkdir(join([directory, "markdown-pages", prefix]), {recursive: true}) + ), + ) await write(join([blog, "z-old.mdx"]), fixture("Old post", "2019-11-28")) await write(join([blog, "a-new.mdx"]), fixture("New post", "2026-04-28")) await write(join([blog, "archived", "middle.mdx"]), fixture("Archived post", "2020-01-01")) @@ -50,9 +55,13 @@ test("RSS links match article routes, including archived posts", async () => { "https://rescript-lang.org/blog/archived/middle", "https://rescript-lang.org/blog/z-old", ]) - let routes = MdxFile.scanPaths(~dir=join([directory, "markdown-pages", "blog"]), ~alias="blog") + let routes = DocsRouteConfig.loadRoutes( + ~options={ + contentDirectory: join([directory, "markdown-pages"]), + }, + ).routes->DocsRouteConfig.concretePaths items->Array.forEach(item => { - let route = item.href->String.replace("https://rescript-lang.org/", "") + let route = item.href->String.replace("https://rescript-lang.org", "") expect(routes->Array.includes(route))->toBe(true) }) }) @@ -80,11 +89,11 @@ test("RSS dates use midnight UTC and XML serialization leaves dates unchanged", test("the current blog feed contains ten recent posts with existing routes", async () => { let items = BlogApi.RssFeed.getLatest() - let routes = MdxFile.scanPaths(~dir="markdown-pages/blog", ~alias="blog") + let routes = DocsRouteConfig.loadRoutes().routes->DocsRouteConfig.concretePaths expect(items->Array.length)->toBe(10) items->Array.forEach(item => { expect( - routes->Array.includes(item.href->String.replace("https://rescript-lang.org/", "")), + routes->Array.includes(item.href->String.replace("https://rescript-lang.org", "")), )->toBe(true) }) }) diff --git a/apps/docs/scripts/extract-syntax.mjs b/apps/docs/scripts/extract-syntax.mjs deleted file mode 100644 index f83926a4a..000000000 --- a/apps/docs/scripts/extract-syntax.mjs +++ /dev/null @@ -1,60 +0,0 @@ -import { globSync } from "tinyglobby"; -import path from "path"; -import fs from "fs"; -import { URL } from "url"; - -import { defaultProcessor } from "./markdown.js"; - -const pathname = new URL(".", import.meta.url).pathname; -const __dirname = - process.platform !== "win32" ? pathname : pathname.substring(1); - -const processFile = (filepath) => { - const content = fs.readFileSync(filepath, "utf8"); - const { - data: { matter }, - } = defaultProcessor.processSync(content); - - const syntaxPath = path.resolve("./misc_docs/syntax"); - const relFilePath = path.relative(syntaxPath, filepath); - const parsedPath = path.parse(relFilePath); - - if ( - matter.id && - matter.keywords && - matter.name && - matter.summary && - matter.category - ) { - return { - file: parsedPath.name, - id: matter.id, - keywords: matter.keywords, - name: matter.name, - summary: matter.summary, - category: matter.category, - }; - } - - console.error("Metadata missing in " + parsedPath.name + ".mdx"); - return null; -}; - -const extractSyntax = async (version) => { - const SYNTAX_MD_DIR = path.join(__dirname, "../misc_docs/syntax"); - const SYNTAX_INDEX_FILE = path.join( - __dirname, - "../index_data/syntax_index.json", - ); - const syntaxFiles = globSync(["*.md", "*.mdx"], { - cwd: SYNTAX_MD_DIR, - absolute: true, - }); - const syntaxIndex = syntaxFiles - .map(processFile) - .filter(Boolean) - .sort((a, b) => a.name.localeCompare(b.name)); - fs.writeFileSync(SYNTAX_INDEX_FILE, JSON.stringify(syntaxIndex), "utf8"); -}; - -extractSyntax(); diff --git a/apps/docs/scripts/figma-fetch.js b/apps/docs/scripts/figma-fetch.js deleted file mode 100644 index 829354ef6..000000000 --- a/apps/docs/scripts/figma-fetch.js +++ /dev/null @@ -1,210 +0,0 @@ -/* - * This script fetches all color styles from a Figma team/document. - * - * Dependencies: - * - * - node-fetch - * - * Due to a limitation in the Figma /styles endpoint, we need to use a - * document for actually using the colors in a color grid. That's why - * we're both fetching from /styles and /files below. - * - * For now, you need to input the page and team IDs, as well as the file keys. - * The team ID is in the Figma URL of your team, and the file key is the long - * string in the full URL of a Figma file. The page ID is visible in the JSON - * payload when you call /files - */ -// Inspect the /files JSON response, or the URL of the Figma page: -// https://www.figma.com/file//Some-Name?node-id= -const PAGE_ID = "312:2"; -// Go to a team URL and get the ID: -// https://www.figma.com/files/team//Team-Name -const TEAM_ID = "977136127691894995"; -// Get this from the URL of a single file: -// https://www.figma.com/file//Some-Name?node-id=182%3A0 -const FILE_KEY = "FsQT67sVviqPXhYoTbpfIb"; - -const fetch = require("node-fetch"); -const fs = require("fs"); -const { promisify } = require("util"); -const path = require("path"); - -const writeFile = promisify(fs.writeFile); - -const personalToken = process.env.FIGMA_PERSONAL_TOKEN; - -if (!personalToken) { - console.error("Please pass FIGMA_PERSONAL_TOKEN to this script and re-run"); - process.exit(1); -} - -const figmaBase = "https://api.figma.com/"; - -const rgbToHex = (r, g, b) => { - const color = - "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); - - if (color.length > 7) { - return color.slice(0, 7); - } - return color; -}; - -const slugify = (str) => str.toLowerCase().replace(/\s+/, "-"); - -const doFetch = (url) => - fetch(`${figmaBase}v1${url}`, { - headers: { - "X-Figma-Token": personalToken, - }, - }) - .then((res) => { - if (!res.ok) { - throw new Error(`Status: ${res.status}`); - } - - return res.json(); - }) - .then((json) => { - if (json.error || (json.status && json.status !== 200)) { - throw new Error(json.error || `Status ${json.status}: ${json.err}`); - } - - return json; - }); - -const fetchStyles = async (teamId) => { - const json = await doFetch(`/teams/${teamId}/styles?page_size=99`); - - return json.meta.styles; -}; - -const fetchFile = async (key) => await doFetch(`/files/${key}`); - -// eslint-disable-next-line -const fetchStyle = async (key) => await doFetch(`/styles/${key}`); - -// Retrieves all elements from groups from a given root -const flattenTree = (root) => { - let ret = []; - - let { children = [] } = root; - - for (let i = 0; i < children.length; i++) { - const el = children[i]; - const type = el.type; - - if (type === "GROUP") { - let sub = flattenTree(el); - ret = ret.concat(sub); - } else { - ret.push(el); - } - } - - return ret; -}; - -/** - * Fetches all color styles from the Figma doc and returns an object - * array with this shape: - * - * ```json - * { - * "key": "206f4e4753e2e8f2e7ac24744bd1843ac206ead1", - * "file_key": "bMb57SxaX0ugGmWMmi7KVzIP", - * "node_id": "189:85", - * "style_type": "FILL", - * "thumbnail_url": "", - * "name": "Green 10", - * "description": "Desc", - * "created_at": "2019-02-16T16:00:39.126Z", - * "updated_at": "2019-02-16T16:00:39.126Z", - * "user": { - * "id": "575212366706412863", - * "handle": "Johan Brook", - * "img_url": "" - * }, - * "sort_position": "=O", - * "color": "#ebfff7" - * } - ``` - */ -const fetchAllColorStyles = async () => { - const styles = await fetchStyles(TEAM_ID); - const file = await fetchFile(FILE_KEY); - - const root = file.document.children[0]; - - if (!root) { - return []; - } - - const canvas = root.children.find((page) => { - return page.id === PAGE_ID; - }); - - const isRectOrVector = (c) => c.type === "RECTANGLE" || c.type === "VECTOR"; - - console.log(JSON.stringify(canvas, null, 2)); - - const allElements = flattenTree(canvas); - - return allElements - .filter( - (c) => isRectOrVector(c) && c.styles != null && c.styles.fill != null, - ) - .map((c) => { - const { r, g, b, a } = c.fills[0].color; - const { opacity = a } = c.fills[0]; - const nodeId = c.styles.fill; - - let color; - if (opacity < 1) { - color = `rgba(${(r * 255).toFixed(0)}, ${(g * 255).toFixed(0)}, ${(b * 255).toFixed(0)}, ${opacity.toFixed(2)})`; - } else { - color = rgbToHex(r * 256, g * 256, b * 256); - } - - return { - // Cross reference to the array of styles, since Figma doesn't - // give us the HEX color codes in their /styles endpoint .. :( - ...styles.find((s) => s.node_id === nodeId), - color, - }; - }) - .filter((c) => c.name != null); -}; - -/** - * Calls Figma's API and saves to a `colors.js` file in the project root. - */ -const writeColorsFromFigma = async () => { - const styles = await fetchAllColorStyles(); - - if (!styles) { - throw new Error("No styles found"); - } - - const colors = styles - .sort((a, b) => (a.sort_position < b.sort_position ? -1 : 1)) - .map((s) => { - return ( - (s.description ? ` /** ${s.description} */\n` : "") + - ` '${slugify(s.name)}': '${s.color}',` - ); - }) - .join("\n"); - - const fileContents = `/* eslint-disable */ -/* Updated at ${new Date().toUTCString()}*/ -module.exports = { -${colors} -}`; - - await writeFile(path.resolve(__dirname + "/../colors.js"), fileContents); - - console.log(`Wrote ${styles.length} colors to colors.js`); -}; - -writeColorsFromFigma().catch(console.error); diff --git a/apps/docs/scripts/markdown.js b/apps/docs/scripts/markdown.js deleted file mode 100644 index 5e13cd52f..000000000 --- a/apps/docs/scripts/markdown.js +++ /dev/null @@ -1,72 +0,0 @@ -import { unified } from "unified"; -import remarkParse from "remark-parse"; -import remarkGfm from "remark-gfm"; -import remarkFrontmatter from "remark-frontmatter"; -import remarkRehype from "remark-rehype"; -import rehypeSlug from "rehype-slug"; -import rehypeStringify from "rehype-stringify"; -import { matter } from "vfile-matter"; - -const remarkVfileMatter = (options) => (tree, file) => { - matter(file); -}; - -const remarkCodeblocks = (options) => (tree, file) => { - const { children } = tree; - const codeblocks = {}; - - const formatter = (value) => { - // Strip newlines and weird spacing - return value - .replace(/\n/g, " ") - .replace(/\s+/g, " ") - .replace(/\(\s+/g, "(") - .replace(/\s+\)/g, ")"); - }; - - children.forEach((child) => { - if (child.type === "code" && child.value) { - const { meta, lang } = child; - if (meta === "sig" && lang === "re") { - if (codeblocks[lang] == null) { - codeblocks[lang] = []; - } - codeblocks[lang].push(formatter(child.value)); - } - } - }); - - Object.assign(file.data, { codeblocks }); -}; - -const rehypeHeaders = (options) => (tree, file) => { - const headers = []; - let mainHeader; - tree.children.forEach((child) => { - if (child.tagName === "h1") { - if (child.children.length > 0) { - mainHeader = child.children.map((element) => element.value).join(""); - } - } - if (child.tagName === "h2") { - if (child.children.length > 0) { - const id = child.properties.id || ""; - const name = child.children.map((element) => element.value).join(""); - headers.push({ name, href: id }); - } - } - }); - - Object.assign(file.data, { headers, mainHeader }); -}; - -export const defaultProcessor = unified() - .use(remarkParse) - .use(remarkGfm) - .use(remarkFrontmatter, [{ type: "yaml", marker: "-" }]) - .use(remarkVfileMatter) - .use(remarkCodeblocks) - .use(remarkRehype) - .use(rehypeSlug) - .use(rehypeHeaders) - .use(rehypeStringify); diff --git a/apps/docs/scripts/sync-redirects.mjs b/apps/docs/scripts/sync-redirects.mjs deleted file mode 100644 index 356897b1c..000000000 --- a/apps/docs/scripts/sync-redirects.mjs +++ /dev/null @@ -1,21 +0,0 @@ -import * as path from "node:path"; -import * as fs from "node:fs"; - -import nextConfig from "../next.config.mjs"; - -const redirectsConfig = await nextConfig.redirects(); - -/** - * @param {{ - * source: string, - * destination: string, - * permanent: boolean, - * }} config - * @return {string} - */ -function lineFormat({ source, destination, permanent }) { - return `${source} ${destination} ${permanent ? 308 : 307}`; -} - -const redirects = redirectsConfig.map(lineFormat).join("\n"); -const redirectsFile = path.join(import.meta.dirname, "../public/_redirects"); diff --git a/apps/docs/scripts/watch-tests.mjs b/apps/docs/scripts/watch-tests.mjs deleted file mode 100644 index 950970394..000000000 --- a/apps/docs/scripts/watch-tests.mjs +++ /dev/null @@ -1,49 +0,0 @@ -import chokidar from "chokidar"; -// We switch from 'exec' to 'spawn' and 'execSync' is not needed -import { spawn } from "child_process"; - -// 1. Define the folder to watch -const folderToWatch = "markdown-pages"; - -// 2. Define the command and arguments to run -// The command is 'node' and the arguments are the script and its options -const command = "node"; -const args = ["scripts/test-hrefs.mjs"]; - -console.log(`\nšŸ‘€ Watching for changes in: ${folderToWatch}`); -console.log(`\nāž”ļø Command to run on change: ${command} ${args.join(" ")}\n`); - -// 3. Initialize the watcher -chokidar - .watch(folderToWatch, { - ignored: /(^|[\/\\])\../, - persistent: true, - }) - .on("change", (path) => { - console.log(`\n--- šŸ“ File changed: ${path} ---`); - - // Use spawn instead of exec to run the script - const child = spawn(command, args, { - // *** KEY CHANGE: 'inherit' is used to connect the child's stdout/stderr - // to the parent process's (your watcher script's) stdout/stderr. *** - stdio: "inherit", - shell: true, // Use 'shell: true' if the script is simple, or if it needs shell features - }); - - // The 'close' event fires when the process exits - child.on("close", (code) => { - if (code !== 0) { - // Log a custom error message if the script fails - console.log(`\nāŒ Script finished with exit code ${code}`); - } else { - // Log a success message - console.log(`\nāœ… Script execution complete.`); - } - console.log(`\nWatching again...\n`); - }); - - // Handle process errors (e.g., command not found) - child.on("error", (err) => { - console.error(`\nšŸ’£ Failed to start child process: ${err}`); - }); - }); diff --git a/apps/docs/src/bindings/DocsRouteConfig.res b/apps/docs/src/bindings/DocsRouteConfig.res new file mode 100644 index 000000000..797a4eda8 --- /dev/null +++ b/apps/docs/src/bindings/DocsRouteConfig.res @@ -0,0 +1,9 @@ +type route +type config = {routes: array} +type options = {contentDirectory?: string} + +@module("../../route-config.mjs") +external loadRoutes: (~options: options=?) => config = "loadRoutes" + +@module("../../generate-route-types.mjs") +external concretePaths: array => array = "concretePaths" diff --git a/apps/docs/src/bindings/Env.res b/apps/docs/src/bindings/Env.res index 29e21321d..b55591838 100644 --- a/apps/docs/src/bindings/Env.res +++ b/apps/docs/src/bindings/Env.res @@ -16,31 +16,8 @@ external algoliaAppIdRaw: option = "import.meta.env.VITE_ALGOLIA_APP_ID" external algoliaIndexNameRaw: option = "import.meta.env.VITE_ALGOLIA_INDEX_NAME" external algoliaSearchApiKeyRaw: option = "import.meta.env.VITE_ALGOLIA_SEARCH_API_KEY" -let algoliaMissingPublicVars = AlgoliaConfig.missingPublicVars( - ~appId=algoliaAppIdRaw, - ~indexName=algoliaIndexNameRaw, - ~searchApiKey=algoliaSearchApiKeyRaw, -) - let algoliaPublicConfig = AlgoliaConfig.publicConfigFrom( ~appId=algoliaAppIdRaw, ~indexName=algoliaIndexNameRaw, ~searchApiKey=algoliaSearchApiKeyRaw, ) - -let algolia_app_id = switch algoliaPublicConfig { -| Some(config) => config.appId -| None => "" -} - -let algolia_index_name = switch algoliaPublicConfig { -| Some(config) => config.indexName -| None => "" -} - -let algolia_search_api_key = switch algoliaPublicConfig { -| Some(config) => config.searchApiKey -| None => "" -} - -let algolia_read_api_key = algolia_search_api_key diff --git a/apps/docs/src/bindings/HeadlessUI.res b/apps/docs/src/bindings/HeadlessUI.res deleted file mode 100644 index e54bd7148..000000000 --- a/apps/docs/src/bindings/HeadlessUI.res +++ /dev/null @@ -1,77 +0,0 @@ -module Menu = { - type state = {@as("open") open_: bool} - - @module("@headlessui/react") @react.component - external make: ( - ~_as: [#div]=?, - ~className: string=?, - ~children: state => React.element, - ) => React.element = "Menu" - - module Button = { - @module("@headlessui/react") @scope("Menu") @react.component - external make: (~className: string=?, ~children: React.element) => React.element = "Button" - } - - module Items = { - @module("@headlessui/react") @scope("Menu") @react.component - external make: (~static: bool=?, ~children: React.element) => React.element = "Items" - } - - module Item = { - type state = {active: bool} - @module("@headlessui/react") @scope("Menu") @react.component - external make: (~children: state => React.element) => React.element = "Item" - } -} - -module Listbox = { - type state = {@as("open") open_: bool} - - @module("@headlessui/react") @react.component - external make: ( - ~value: 'state=?, - ~onChange: 'state => unit=?, - ~className: string=?, - ~children: state => React.element, - ) => React.element = "Listbox" - - module Button = { - @module("@headlessui/react") @scope("Listbox") @react.component - external make: (~className: string=?, ~children: React.element=?) => React.element = "Button" - } - - module Options = { - @module("@headlessui/react") @scope("Listbox") @react.component - external make: ( - ~static: bool=?, - ~className: string=?, - ~children: React.element, - ) => React.element = "Options" - } - - module Option = { - type state = {selected: bool, active: bool} - @module("@headlessui/react") @scope("Listbox") @react.component - external make: ( - ~children: state => React.element, - ~value: 'item, - ~disabled: bool=?, - ) => React.element = "Option" - } -} - -module Transition = { - @module("@headlessui/react") @react.component - external make: ( - ~appear: bool=?, - ~show: bool, - ~enter: string=?, - ~enterFrom: string=?, - ~enterTo: string=?, - ~leave: string=?, - ~leaveFrom: string=?, - ~leaveTo: string=?, - ~children: React.element, - ) => React.element = "Transition" -} diff --git a/apps/docs/src/bindings/Jsdom.res b/apps/docs/src/bindings/Jsdom.res deleted file mode 100644 index 43f12c740..000000000 --- a/apps/docs/src/bindings/Jsdom.res +++ /dev/null @@ -1,5 +0,0 @@ -type window = {document: WebAPI.DOMAPI.document} -type t = {window: WebAPI.DOMAPI.window} - -@module("jsdom") @new -external make: string => t = "JSDOM" diff --git a/apps/docs/src/bindings/Mdast.res b/apps/docs/src/bindings/Mdast.res index c9fd2c016..de3d531a6 100644 --- a/apps/docs/src/bindings/Mdast.res +++ b/apps/docs/src/bindings/Mdast.res @@ -8,44 +8,3 @@ external toString: {..} => string = "toString" @module("unist-util-visit") external visit: (tree, string, {..} => unit) => unit = "visit" - -@unboxed -type listType = - | @as("list") List - | @as("listItem") ListItem - | @as("paragraph") Paragraph - | @as("link") Link - -type rec listContent = { - @as("type") _type: listType, - url?: Path.t, - title?: string, - value: string, - children?: array, -} - -type result = { - index?: int, - endIndex?: int, - map: listContent, -} - -type tocOptions = {maxDepth?: int} - -@module("mdast-util-toc") -external toc: (tree, tocOptions) => result = "toc" - -let rec reduceHeaders = (list: listContent, links: dict) => { - if list._type == Link { - let child = list.children->Option.flatMap(children => children[0]) - switch (list.url, child) { - | (Some(url), Some(child)) => links->Dict.set(child.value, url) - | (_, _) => () - } - } else { - switch list.children { - | Some(children) => children->Array.forEach(child => reduceHeaders(child, links)) - | None => () - } - } -} diff --git a/apps/docs/src/bindings/Remark.res b/apps/docs/src/bindings/Remark.res deleted file mode 100644 index 44314c8e4..000000000 --- a/apps/docs/src/bindings/Remark.res +++ /dev/null @@ -1,15 +0,0 @@ -type io -type processor -type plugin = processor => io - -@unboxed -type pluginOrOption = Plugin(plugin) - -@unboxed -type rec remarkPlugin = - | Plugin(processor => io) - | WithOptions(array) - -@module("remark-comment") external comment: plugin = "default" -@module("remark-gfm") external gfm: plugin = "default" -@module("remark-frontmatter") external frontmatter: plugin = "default" diff --git a/apps/docs/src/common/Constants.res b/apps/docs/src/common/Constants.res index 9183573f6..79d3a924c 100644 --- a/apps/docs/src/common/Constants.res +++ b/apps/docs/src/common/Constants.res @@ -1,56 +1,8 @@ -type env = { - @as("VITE_VERSION_LATEST") - latestVersion: string, - @as("VITE_VERSION_NEXT") - nextVersion: string, -} +external latestVersion: string = "import.meta.env.VITE_VERSION_LATEST" -@scope("import.meta") external env: env = "env" +let docSearchVersionTokens = [latestVersion->Semver.tryGetMajorString, "latest"] -type versionMapping = { - latest: string, - next: string, -} - -let versions = { - latest: env.latestVersion, - next: env.nextVersion, -} - -let latestVersion = (versions.latest, versions.latest->Semver.tryGetMajorString) - -let docSearchVersionTokens = { - let (_, majorVersion) = latestVersion - [majorVersion, "latest"] -} - -// This is used for the version dropdown in the manual layouts -let allManualVersions = [ - latestVersion, - ("v10.0.0", "v9.1 - v10.1"), - ("v9.0.0", "v8.2 - v9.0"), - ("v8.0.0", "v6.0 - v8.2"), -] - -let nextVersion = - versions.latest === versions.next - ? None - : Some(versions.next, versions.next->Semver.tryGetMajorString) - -let stdlibVersions = - versions.latest === "v11.0.0" ? [latestVersion] : [("v11.0.0", "v11"), latestVersion] - -let latestReactVersion = "latest" -let allReactVersions = [ - ("latest", latestReactVersion), - ("v0.11.0", "v0.11.0"), - ("v0.10.0", "v0.10.0"), -] - -let dropdownLabelNext = "--- Next ---" -let dropdownLabelReleased = "--- Released ---" - -// Used for the DocsOverview and collapsible navigation +// Used for the DocsOverview let languageManual = [ ("Overview", "/docs/manual/introduction"), ("Language Features", "/docs/manual/overview"), diff --git a/apps/docs/src/common/DateStr.res b/apps/docs/src/common/DateStr.res index 2eaf948ba..8a2cddffb 100644 --- a/apps/docs/src/common/DateStr.res +++ b/apps/docs/src/common/DateStr.res @@ -3,7 +3,6 @@ type t = string // Used to prevent issues with webkit based date representations let parse = (dateStr: string): Date.t => dateStr->String.replaceRegExp(/-/g, "/")->Date.fromString -let fromDate = date => Date.toString(date) let toDate = dateStr => parse(dateStr) // ISO date-only strings represent midnight UTC, independent of the build timezone. diff --git a/apps/docs/src/common/DateStr.resi b/apps/docs/src/common/DateStr.resi index 0c44564f4..2f958b052 100644 --- a/apps/docs/src/common/DateStr.resi +++ b/apps/docs/src/common/DateStr.resi @@ -3,6 +3,5 @@ type t external fromString: string => t = "%identity" -let fromDate: Date.t => t let toDate: t => Date.t let toUTCDate: t => Date.t diff --git a/apps/docs/src/common/EnableCollapsibleNavbar.res b/apps/docs/src/common/EnableCollapsibleNavbar.res deleted file mode 100644 index 2a841e8cf..000000000 --- a/apps/docs/src/common/EnableCollapsibleNavbar.res +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Hides the navbar when scrolling down on mobile, and causes it to reappear when scrolling back up. - */ -@jsx.component -let make = (~children, ~isEnabled) => { - let scrollDir = Hooks.useScrollDirection() - if isEnabled { -
"group nav-appear" - - | Down(_) => "group nav-disappear" - }} - > - children -
- } else { - children - } -} diff --git a/apps/docs/src/common/Hooks.res b/apps/docs/src/common/Hooks.res index 99b79381f..1e8ae16ee 100644 --- a/apps/docs/src/common/Hooks.res +++ b/apps/docs/src/common/Hooks.res @@ -45,31 +45,9 @@ let useScrollDirection = (~topMargin=80, ~threshold=20) => { }) } - // let onScroll = Util.debounce(onScroll, 50) - WebAPI.Window.addEventListener(window, Scroll, onScroll) Some(() => WebAPI.Window.removeEventListener(window, Scroll, onScroll)) }, [topMargin, threshold]) scrollDir } - -type mediaQueryListEvent = {matches: bool} - -let useMediaQuery = (query: string) => { - let (matches, setMatches) = React.useState(() => { - false - }) - - React.useEffect(() => { - let mediaQueryList = WebAPI.Window.matchMedia(window, query) - setMatches(_ => mediaQueryList.matches) - - let listener = (e: mediaQueryListEvent) => setMatches(_ => e.matches) - - WebAPI.MediaQueryList.addEventListener(mediaQueryList, Change, listener) - Some(() => WebAPI.MediaQueryList.removeEventListener(mediaQueryList, Change, listener)) - }, [query]) - - matches -} diff --git a/apps/docs/src/common/Url.res b/apps/docs/src/common/Url.res index 7f56de3fc..c799fea51 100644 --- a/apps/docs/src/common/Url.res +++ b/apps/docs/src/common/Url.res @@ -1,9 +1,3 @@ -type t = { - fullpath: array, - base: array, - pagepath: array, -} - type breadcrumb = { name: string, href: string, @@ -15,30 +9,6 @@ let prettyString = (str: string) => { str->camelCase->capitalize } -let parse = (route: string): t => { - let routePath = route->String.split("/")->Array.filter(s => s !== "") - - { - fullpath: routePath, - base: routePath, - pagepath: [], - } -} - -@unboxed -type storageKey = - | @as("manual_version") Manual - | @as("react_version") React - | @as("playground_version") Playground - -let getVersionFromStorage = (key: storageKey) => { - try { - WebAPI.Storage.getItem(window.localStorage, (key :> string))->Null.toOption - } catch { - | JsExn(_) => None - } -} - let normalizePath = string => { string->String.replaceRegExp(/\/$/, "")->String.toLocaleLowerCase } diff --git a/apps/docs/src/common/Url.resi b/apps/docs/src/common/Url.resi index e0ce5d1b3..708c5a322 100644 --- a/apps/docs/src/common/Url.resi +++ b/apps/docs/src/common/Url.resi @@ -1,27 +1,10 @@ -type t = { - fullpath: array, - base: array, - pagepath: array, -} - -// Moved here from the original sidebar URL helper. type breadcrumb = { name: string, href: string, } -let parse: string => t - let prettyString: string => string -@unboxed -type storageKey = - | @as("manual_version") Manual - | @as("react_version") React - | @as("playground_version") Playground - -let getVersionFromStorage: storageKey => option - let normalizePath: string => string let normalizeAnchor: string => string diff --git a/apps/docs/src/common/Util.res b/apps/docs/src/common/Util.res index aabbf2221..4e2f476b4 100644 --- a/apps/docs/src/common/Util.res +++ b/apps/docs/src/common/Util.res @@ -15,8 +15,6 @@ module String = { ->String.split(" ") ->Array.map(str => str->String.length > 2 ? str->String.capitalize : str) ->Array.join(" ") - - let leadingSlash = str => str->String.startsWith("/") ? str : "/" ++ str } module Url = { @@ -42,34 +40,6 @@ module Url = { } } - let getRootPath = path => { - if path->Stdlib.String.includes("docs/manual") { - "/docs/manual" - } else if path->Stdlib.String.includes("docs/react") { - "/docs/react" - } else { - "" - } - } - - let createRelativePath = (currentPath, href) => { - if ( - href->Stdlib.String.includes("docs/manual") || - href->Stdlib.String.includes("docs/react") || - href->Stdlib.String.includes("community") || - href->Stdlib.String.includes("blog") || - href->Stdlib.String.includes("try") || - href->Stdlib.String.includes("/llms/") || - href->Stdlib.String.startsWith("#") - ) { - href - } else { - let rootPath = getRootPath(currentPath) - let href = href->Stdlib.String.replace("docs/manual", "") - (rootPath ++ href->String.leadingSlash)->Stdlib.String.replaceAll("//", "/") - } - } - let makeOpenGraphImageUrl = url => { `${baseUrl()}ogimage/index.png?url=${encodeURIComponent(url)}` } @@ -88,17 +58,3 @@ module Date = { dateTimeFormat("en-US", {"month": "short", "day": "numeric", "year": "numeric"})->format(date) } } - -/** - * é˜²ęŠ– - * @param fn the func to debounce - * @param delay milliseconds - * @returns new debounced function - */ -let debounce = (fn: unit => unit, delay: int) => { - let timer = ref(None) - () => { - timer.contents->Option.forEach(clearTimeout) - timer := Some(setTimeout(~handler=fn, ~timeout=delay)) - } -} diff --git a/apps/docs/src/common/Util.resi b/apps/docs/src/common/Util.resi index c395ff634..021aac8c0 100644 --- a/apps/docs/src/common/Util.resi +++ b/apps/docs/src/common/Util.resi @@ -1,8 +1,6 @@ module Url: { let isAbsolute: string => bool let makeAbsoluteUrl: string => string - let getRootPath: string => string - let createRelativePath: (string, string) => string let makeOpenGraphImageUrl: string => string } @@ -10,11 +8,8 @@ module String: { let camelCase: string => string let capitalize: string => string let capitalizeSentence: string => string - let leadingSlash: string => string } module Date: { let toDayMonthYear: Date.t => string } - -let debounce: (unit => unit, int) => unit => unit diff --git a/apps/docs/src/components/ApiIntro.res b/apps/docs/src/components/ApiIntro.res deleted file mode 100644 index f74f3fc88..000000000 --- a/apps/docs/src/components/ApiIntro.res +++ /dev/null @@ -1,2 +0,0 @@ -@react.component -let make = (~children) =>
children
diff --git a/apps/docs/src/components/ApiIntro.resi b/apps/docs/src/components/ApiIntro.resi deleted file mode 100644 index 940ed3d87..000000000 --- a/apps/docs/src/components/ApiIntro.resi +++ /dev/null @@ -1,2 +0,0 @@ -@react.component -let make: (~children: React.element) => React.element diff --git a/apps/docs/src/components/ApiMarkdown.res b/apps/docs/src/components/ApiMarkdown.res deleted file mode 100644 index e517f4fa9..000000000 --- a/apps/docs/src/components/ApiMarkdown.res +++ /dev/null @@ -1,39 +0,0 @@ -open Markdown -/* -module H1 = { - @react.component - let make = (~children) => -

children

-}*/ - -module H2 = { - // We will currently hide the headline, to keep the structure, - // but having an Elm like documentation - @react.component - let make = (~id, ~children, ~title) => <> -
- children - -} - -type aliasH2 = Markdown.H2.props => React.element -external asMarkdownH2: 'a => aliasH2 = "%identity" - -let default = { - MarkdownComponents.intro: Intro.make, - p: P.make, - li: Li.make, - h1: H1.make, - h2: H2.make->asMarkdownH2, - h3: H3.make, - h4: H4.make, - h5: H5.make, - ul: Ul.make, - ol: Ol.make, - a: A.make, - thead: Thead.make, - th: Th.make, - td: Td.make, - pre: Pre.make, - code: Code.make, -} diff --git a/apps/docs/src/components/ApiMarkdown.resi b/apps/docs/src/components/ApiMarkdown.resi deleted file mode 100644 index 1129e877b..000000000 --- a/apps/docs/src/components/ApiMarkdown.resi +++ /dev/null @@ -1 +0,0 @@ -let default: MarkdownComponents.t diff --git a/apps/docs/src/components/CodeMirrorSetup.js b/apps/docs/src/components/CodeMirrorSetup.js deleted file mode 100644 index 6e7ef254e..000000000 --- a/apps/docs/src/components/CodeMirrorSetup.js +++ /dev/null @@ -1,19 +0,0 @@ -// CodeMirror setup and mode imports -import CodeMirror from "codemirror"; - -import "codemirror/lib/codemirror.css"; - -// Import required modes and addons -import "codemirror/mode/javascript/javascript"; -import "../../plugins/cm-rescript-mode.js"; -import "../../plugins/cm-reason-mode.js"; - -// Import vim keymap if needed -import "codemirror/keymap/vim"; - -// Make sure CodeMirror is available globally -if (typeof window !== "undefined") { - window.CodeMirror = CodeMirror; -} - -export default CodeMirror; diff --git a/apps/docs/src/components/CommunityContent.res b/apps/docs/src/components/CommunityContent.res index cc79fe3d7..9e2f601c5 100644 --- a/apps/docs/src/components/CommunityContent.res +++ b/apps/docs/src/components/CommunityContent.res @@ -59,5 +59,3 @@ let make = () => {
} - -let default = make diff --git a/apps/docs/src/components/Image.res b/apps/docs/src/components/Image.res deleted file mode 100644 index e99f9818d..000000000 --- a/apps/docs/src/components/Image.res +++ /dev/null @@ -1,10 +0,0 @@ -@react.component -let default = ( - ~src: string, - ~size=#large, - ~withShadow=false, - ~caption: option=?, - ~className: option=?, -) => { - -} diff --git a/apps/docs/src/components/Intro.res b/apps/docs/src/components/Intro.res deleted file mode 100644 index 04c24c397..000000000 --- a/apps/docs/src/components/Intro.res +++ /dev/null @@ -1,2 +0,0 @@ -@react.component -let default = (~children) => children diff --git a/apps/docs/src/components/Markdown.resi b/apps/docs/src/components/Markdown.resi index 51ac8b922..c85bcf5ed 100644 --- a/apps/docs/src/components/Markdown.resi +++ b/apps/docs/src/components/Markdown.resi @@ -30,14 +30,6 @@ module UrlBox: { let make: (~text: string, ~href: string, ~children: React.element) => React.element } -// Used for creating invisible, hoverable anchors for url linking -module Anchor: { - /* let idFormat = (id: string): string => id */ - - @react.component - let make: (~id: string, ~title: string=?) => React.element -} - module H1: { @react.component let make: (~id: string=?, ~title: string=?, ~children: React.element) => React.element diff --git a/apps/docs/src/components/Navigation.res b/apps/docs/src/components/Navigation.res deleted file mode 100644 index 4502e767d..000000000 --- a/apps/docs/src/components/Navigation.res +++ /dev/null @@ -1,283 +0,0 @@ -module Link = ReactRouter.Link - -let link = "no-underline block hover:cursor-pointer hover:text-fire-30 mb-px" -let activeLink = "font-medium text-fire-30 border-b border-fire" - -let linkOrActiveLink = (~target: Path.t, ~route: Path.t) => target === route ? activeLink : link - -let linkOrActiveLinkSubroute = (~target: Path.t, ~route: Path.t) => - String.startsWith((route :> string), (target :> string)) ? activeLink : link - -let isActiveLink = (~includes: string, ~excludes: option=?, ~route: Path.t) => { - let route = (route :> string) - // includes means we want the lnk to be active if it contains the expected text - let includes = route->String.includes(includes) - // excludes allows us to not have links be active even if they do have the includes text - let excludes = switch excludes { - | Some(excludes) => route->String.includes(excludes) - | None => false - } - includes && !excludes ? activeLink : link -} - -let isDocRoute = (~route: Path.t) => { - let route = (route :> string) - route->String.includes("/docs/") || route->String.includes("/syntax-lookup") || route == "/docs" -} - -let isDocRouteActive = (~route: Path.t) => isDocRoute(~route) ? activeLink : link - -module MobileNav = { - @react.component - let make = (~route: Path.t) => { - let base = "font-normal mx-4 py-5 text-gray-40 border-b border-gray-80" - let extLink = "block hover:cursor-pointer hover:text-white text-gray-60" - - } -} - -/* isOverlayOpen: if the mobile sidebar is toggled open */ -@react.component -let make = (~fixed=true, ~isOverlayOpen: bool, ~setOverlayOpen: (bool => bool) => unit) => { - let location = ReactRouter.useLocation() - let route = location.pathname - - // TODO: post RR7 - I am not sure we need this, but it was fidgety to get this working right so I am leaving it for now - let (_isLocked, toggleScrollLock) = ScrollLockContext.useScrollLock() - - let toggleOverlay = () => { - setOverlayOpen(prev => !prev) - toggleScrollLock(prev => !prev) - } - - let fixedNavClassName = fixed ? "fixed top-0" : "relative" - - <> - - -} - -let make = React.memo(make) diff --git a/apps/docs/src/components/Navigation.resi b/apps/docs/src/components/Navigation.resi deleted file mode 100644 index 4c1f8c476..000000000 --- a/apps/docs/src/components/Navigation.resi +++ /dev/null @@ -1,6 +0,0 @@ -@react.component -let make: ( - ~fixed: bool=?, - ~isOverlayOpen: bool, - ~setOverlayOpen: (bool => bool) => unit, -) => React.element diff --git a/apps/docs/src/components/SidebarNav.resi b/apps/docs/src/components/SidebarNav.resi index c9e6782e9..8dd1efe40 100644 --- a/apps/docs/src/components/SidebarNav.resi +++ b/apps/docs/src/components/SidebarNav.resi @@ -3,14 +3,6 @@ module NavItem: { name: string, href: string, } - @react.component - let make: ( - ~getActiveToc: t => option=?, - ~isHidden: bool=?, - ~isItemActive: t => bool=?, - ~items: array, - ~onClick: unit => unit, - ) => React.element } module Category: { diff --git a/apps/docs/src/components/VersionSelect.res b/apps/docs/src/components/VersionSelect.res index 36664c742..df8b957c4 100644 --- a/apps/docs/src/components/VersionSelect.res +++ b/apps/docs/src/components/VersionSelect.res @@ -12,12 +12,6 @@ let olderVersions = [ {label: "v6.0 - v8.1", link: "https://v11.rescript-lang.org/docs/manual/v8.0.0/introduction"}, ] -module SectionHeader = { - @react.component - let make = (~value) => - -} - // This is the current version let version = DocsVersion.current diff --git a/apps/docs/src/components/VersionSelect.resi b/apps/docs/src/components/VersionSelect.resi index 2b3c727a8..3d9d5083c 100644 --- a/apps/docs/src/components/VersionSelect.resi +++ b/apps/docs/src/components/VersionSelect.resi @@ -1,6 +1,2 @@ -module SectionHeader: { - @react.component - let make: (~value: string) => Jsx.element -} @react.component let make: unit => Jsx.element diff --git a/apps/docs/src/components/Video.res b/apps/docs/src/components/Video.res deleted file mode 100644 index 45583c6bb..000000000 --- a/apps/docs/src/components/Video.res +++ /dev/null @@ -1,4 +0,0 @@ -@react.component -let default = (~src: string, ~caption: option=?) => { - -} diff --git a/apps/docs/src/data/BlogApi.res b/apps/docs/src/data/BlogApi.res index 8aa5cd2ca..0a378a74c 100644 --- a/apps/docs/src/data/BlogApi.res +++ b/apps/docs/src/data/BlogApi.res @@ -51,49 +51,6 @@ let getAllPosts = () => { ) } -let getLivePosts = () => { - let postsDirectory = Node.Path.join2(Node.Process.cwd(), "markdown-pages/blog") - - let livePosts = mdxFiles(postsDirectory)->Array.map(path => { - let {frontmatter} = - Node.Path.join2(postsDirectory, path)->Node.Fs.readFileSync->MarkdownParser.parseSync - switch BlogFrontmatter.decode(frontmatter) { - | Error(msg) => JsError.throwWithMessage(msg) - | Ok(d) => { - path, - frontmatter: d, - archived: false, - } - } - }) - - livePosts->Array.toSorted((a, b) => - String.compare(Node.Path.basename(b.path), Node.Path.basename(a.path)) - ) -} - -let getArchivedPosts = () => { - let postsDirectory = Node.Path.join2(Node.Process.cwd(), "markdown-pages/blog") - let archivedPostsDirectory = Node.Path.join2(postsDirectory, "archived") - - let archivedPosts = mdxFiles(archivedPostsDirectory)->Array.map(path => { - let {frontmatter} = - Node.Path.join2(archivedPostsDirectory, path)->Node.Fs.readFileSync->MarkdownParser.parseSync - switch BlogFrontmatter.decode(frontmatter) { - | Error(msg) => JsError.throwWithMessage(msg) - | Ok(d) => { - path: Node.Path.join2("archived", path), - frontmatter: d, - archived: true, - } - } - }) - - archivedPosts->Array.toSorted((a, b) => - String.compare(Node.Path.basename(b.path), Node.Path.basename(a.path)) - ) -} - module RssFeed = { type item = { title: string, diff --git a/apps/docs/src/data/BlogApi.resi b/apps/docs/src/data/BlogApi.resi index 3c47faadc..bbada4f90 100644 --- a/apps/docs/src/data/BlogApi.resi +++ b/apps/docs/src/data/BlogApi.resi @@ -4,9 +4,6 @@ type post = { frontmatter: BlogFrontmatter.t, } -let getAllPosts: unit => array -let getLivePosts: unit => array -let getArchivedPosts: unit => array let blogPathToSlug: string => string module RssFeed: { diff --git a/apps/docs/src/ffi/react-codemirror-hooks.js b/apps/docs/src/ffi/react-codemirror-hooks.js deleted file mode 100644 index 9b5f14093..000000000 --- a/apps/docs/src/ffi/react-codemirror-hooks.js +++ /dev/null @@ -1,162 +0,0 @@ -// -// OBSOLETE! -// -// This file is reimplemented in common/CodeMirrorBase.re -// We keep this around for reference in case we find some -// bugs in the new implementation -// -import CodeMirror from "codemirror"; -import "codemirror/lib/codemirror.css"; -import { useEffect, useRef } from "react"; - -if (typeof window !== "undefined" && typeof window.navigator !== "undefined") { - require("codemirror/mode/javascript/javascript"); - require("../plugins/cm-reason-mode"); -} - -function makeErrorMarker(cm) { - const wrapper = cm.getWrapperElement(); - - const msg = document.createElement("div"); - msg.className = "z-10 absolute px-2 rounded bg-fire-15"; - msg.innerHTML = "Some Error Message"; - msg.style.display = "none"; - - const showMsg = () => { - msg.style.display = "block"; - }; - - const hideMsg = () => { - msg.style.display = "none"; - }; - - const marker = document.createElement("div"); - marker.className = - "text-center text-fire font-bold bg-fire-15 rounded w-4 ml-2 hover:cursor-pointer"; - marker.innerHTML = "!"; - marker.onmouseover = function markerMouseOver() { - const wrapperPos = wrapper.getBoundingClientRect(); - const pos = marker.getBoundingClientRect(); - - msg.style.left = pos.x - wrapperPos.x + pos.width + "px"; - msg.style.top = pos.y - wrapperPos.y + "px"; - - console.log("wrapperPos", wrapperPos); - console.log("markerPos", pos); - console.log(msg.style.left); - - showMsg(); - }; - - marker.onmouseleave = function markerMouseLeave() { - hideMsg(); - }; - - wrapper.appendChild(msg); - - return marker; -} - -const ERROR_GUTTER_ID = "errors"; - -function clearMarks(cm) { - var state = cm.state.errors; - - for (var i = 0; i < state.marked.length; ++i) { - state.marked[i].clear(); - } - - state.marked.length = 0; -} - -function updateErrors(cm, errors) { - // Clear out all previous errors first - clearMarks(cm); - cm.clearGutter(ERROR_GUTTER_ID); - - const state = cm.state.errors; - - errors.forEach((e) => { - const marker = makeErrorMarker(cm); - cm.setGutterMarker(e.row, ERROR_GUTTER_ID, marker); - - const from = { line: e.row, ch: e.column }; - const to = { line: e.endRow, ch: e.endColumn }; - - state.marked.push(cm.markText(from, to, { className: "bg-fire-15" })); - console.log("marked", state.marked); - }); - - //cm.setGutterMarker(0, ERROR_GUTTER_ID, marker); -} - -export default function CodeMirrorReact(props) { - const options = props.options || {}; - - const errors = props.errors || []; - const value = props.value || ""; - - const inputElement = useRef(); - const cmRef = useRef(); - - useEffect(() => { - const cm = CodeMirror.fromTextArea(inputElement.current, { - theme: "material", - gutters: [ERROR_GUTTER_ID, "CodeMirror-linenumbers"], - ...options, - }); - - cm.getScrollerElement().style.minHeight = props.minHeight; - cm.refresh(); - - // For some reason, injecting value with the options doesn't work - // so we need to set the initial value imperatively - cm.setValue(value); - - // Set up event handlers - const onChange = props.onChange; - if (onChange != null) { - cm.on("change", (instance, obj) => { - onChange(instance.getValue()); - }); - } - - cmRef.current = cm; - - // Set state - cm.state.errors = { marked: [] }; - - return () => { - console.log("cleanup", options.mode); - // This will destroy the CM instance - cm.toTextArea(); - }; - }, []); - - useEffect(() => { - const cm = cmRef.current; - - if (cm != null) { - cm.setValue(value); - } - }, [value]); - - useEffect(() => { - const cm = cmRef.current; - - if (cm != null) { - //console.log("setting errors"); - //console.log(errors); - - cm.operation(() => { - updateErrors(cm, errors); - }); - } - }, [errors]); - - return ( -
-