From 67be5cfbcf864049e2ff7cbefbe5d5a1b9772de5 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Fri, 18 Sep 2026 07:36:34 -0400 Subject: [PATCH 1/2] refactor: split homepage component boundaries Extract each homepage section into a focused module, remove the unused MarkdownComponents dependency, and avoid eagerly creating static JSX at module initialization. --- apps/docs/app/routes/LandingPage.res | 709 +----------------- apps/docs/app/routes/LandingPage.resi | 2 +- .../routes/LandingPageCuratedResources.res | 147 ++++ .../routes/LandingPageCuratedResources.resi | 2 + apps/docs/app/routes/LandingPageIntro.res | 32 + apps/docs/app/routes/LandingPageIntro.resi | 2 + .../routes/LandingPageMainSellingPoints.res | 122 +++ .../routes/LandingPageMainSellingPoints.resi | 2 + .../routes/LandingPageOtherSellingPoints.res | 61 ++ .../routes/LandingPageOtherSellingPoints.resi | 2 + .../docs/app/routes/LandingPagePlayground.res | 88 +++ .../app/routes/LandingPagePlayground.resi | 2 + .../app/routes/LandingPageQuickInstall.res | 142 ++++ .../app/routes/LandingPageQuickInstall.resi | 2 + apps/docs/app/routes/LandingPageTrustedBy.res | 30 + .../docs/app/routes/LandingPageTrustedBy.resi | 2 + 16 files changed, 645 insertions(+), 702 deletions(-) create mode 100644 apps/docs/app/routes/LandingPageCuratedResources.res create mode 100644 apps/docs/app/routes/LandingPageCuratedResources.resi create mode 100644 apps/docs/app/routes/LandingPageIntro.res create mode 100644 apps/docs/app/routes/LandingPageIntro.resi create mode 100644 apps/docs/app/routes/LandingPageMainSellingPoints.res create mode 100644 apps/docs/app/routes/LandingPageMainSellingPoints.resi create mode 100644 apps/docs/app/routes/LandingPageOtherSellingPoints.res create mode 100644 apps/docs/app/routes/LandingPageOtherSellingPoints.resi create mode 100644 apps/docs/app/routes/LandingPagePlayground.res create mode 100644 apps/docs/app/routes/LandingPagePlayground.resi create mode 100644 apps/docs/app/routes/LandingPageQuickInstall.res create mode 100644 apps/docs/app/routes/LandingPageQuickInstall.resi create mode 100644 apps/docs/app/routes/LandingPageTrustedBy.res create mode 100644 apps/docs/app/routes/LandingPageTrustedBy.resi diff --git a/apps/docs/app/routes/LandingPage.res b/apps/docs/app/routes/LandingPage.res index aab1d4bbc..288608963 100644 --- a/apps/docs/app/routes/LandingPage.res +++ b/apps/docs/app/routes/LandingPage.res @@ -1,689 +1,5 @@ -module Intro = { - @react.component - let make = () => { -
- // We only need this font on the homepage, so we load it here instead of globally to save some bandwidth for users who navigate to other pages directly - -
-

- {React.string("JavaScript Made Simple for Humans and AI")} -

-

- {React.string(`Types > Vibes`)} -

-

- {React.string(`ReScript is a strongly typed language that compiles to clean, - efficient JavaScript that humans and AI tools can read and understand.`)} -

-

- {React.string(`Its fast compiler and static type system keep feedback loops tight, - so you can move quickly with AI assistance while maintaining - confidence as your codebase grows.`)} -

- // Keep the CTA link block-level so the moved top/bottom margins still affect layout after removing its wrapper. - - - -
-
- } -} - -module PlaygroundHero = { - type example = { - res: string, - js: string, - } - - let examples = [ - { - res: `module Button = { - @react.component - let make = (~count) => { - let times = switch count { - | 1 => "once" - | 2 => "twice" - | n => n->Int.toString ++ " times" - } - let text = \`Click me $\{times\}\` - - - } -}`, - js: `import * as JsxRuntime from "react/jsx-runtime"; - -function Playground$Button(props) { - let count = props.count; - let times = count !== 1 ? ( - count !== 2 ? count.toString() + " times" : "twice" - ) : "once"; - let text = "Click me " + times; - return JsxRuntime.jsx("button", { - children: text - }); -} - -let Button = { - make: Playground$Button -}; - -export { - Button, -}`, - }, - ] - - @react.component - let make = () => { - let (example, _setExample) = React.useState(_ => examples->Array.getUnsafe(0)) - - //Playground Section & Background -
-
- // `mx-auto` preserves the removed centering wrapper's max-width behavior for the playground frame. - // Playground widget -
- //Left Side (ReScript) -
-
- {React.string("Write in ReScript")} -
-
-              {HighlightJs.renderHLJS(~darkmode=true, ~code=example.res, ~lang="res", ())}
-            
-
- //Right Side (JavaScript) -
-
- {React.string("Compile to JavaScript")} -
-
-              {HighlightJs.renderHLJS(~darkmode=true, ~code=example.js, ~lang="js", ())}
-            
-
-
- - /* ---Link to Playground--- */ - - {React.string("Edit this example in Playground")} - - // -
- - -
-
- - -
-
-
- } -} - -module QuickInstall = { - module CopyButton = { - let copyToClipboard: string => bool = %raw(` - function(str) { - try { - const el = document.createElement('textarea'); - el.value = str; - el.setAttribute('readonly', ''); - el.style.position = 'absolute'; - el.style.left = '-9999px'; - document.body.appendChild(el); - const selected = - document.getSelection().rangeCount > 0 ? document.getSelection().getRangeAt(0) : false; - el.select(); - document.execCommand('copy'); - document.body.removeChild(el); - if (selected) { - document.getSelection().removeAllRanges(); - document.getSelection().addRange(selected); - } - return true; - } catch(e) { - return false; - } - } - `) - - type state = - | Init - | Copied - | Failed - - @react.component - let make = (~code) => { - let (state, setState) = React.useState(_ => Init) - - let buttonRef = React.useRef(Nullable.null) - - let onClick = evt => { - ReactEvent.Mouse.preventDefault(evt) - if copyToClipboard(code) { - setState(_ => Copied) - } else { - setState(_ => Failed) - } - } - - React.useEffect(() => { - switch state { - | Copied => - let buttonEl = Nullable.toOption(buttonRef.current)->Option.getOrThrow - - // Note on this imperative DOM nonsense: - // For Tailwind transitions to behave correctly, we need to first paint the DOM element in the tree, - // and in the next tick, add the opacity-100 class, so the transition animation actually takes place. - // If we don't do that, the banner will essentially pop up without any animation - let bannerEl = WebAPI.Document.createElement(document, "div") - bannerEl.className = "foobar opacity-0 absolute top-0 mt-4 -mr-1 px-2 rounded right-0 - bg-turtle text-gray-80-tr body-sm - transition-all duration-500 ease-in-out " - let textNode = WebAPI.Document.createTextNode(document, "Copied!") - - WebAPI.Element.appendChild(bannerEl, textNode)->ignore - WebAPI.Element.appendChild(buttonEl, bannerEl)->ignore - - let nextFrameId = WebAPI.Window.requestAnimationFrame(window, _ => { - WebAPI.DOMTokenList.toggle(bannerEl.classList, ~token="opacity-0")->ignore - WebAPI.DOMTokenList.toggle(bannerEl.classList, ~token="opacity-100")->ignore - }) - - let timeoutId = setTimeout(~handler=() => { - buttonEl->WebAPI.Element.removeChild(bannerEl)->ignore - setState(_ => Init) - }, ~timeout=2000) - - Some( - () => { - cancelAnimationFrame(nextFrameId) - clearTimeout(timeoutId) - }, - ) - | _ => None - } - }, [state]) - - - } - } - - module Instructions = { - let copyBox = text => { -
- {React.string(text)} - -
- } - @react.component - let make = (~className: string="") => { -
-

{React.string("Quick Install")}

-
- {React.string( - "You can quickly add ReScript to your existing JavaScript codebase via npm / yarn:", - )} -
- {copyBox("npm install rescript")} -
- {React.string("Or generate a new project from the official template with npx:")} -
- {copyBox("npx create-rescript-app")} -
- } - } - - @react.component - let make = () => { -
-
- //---Textblock on the left side--- - // Keep the width clamp on the paragraph itself now that the wrapper is gone. -

- - {React.string(`Leverage the full power`)} - - {React.string(` of JavaScript in a robustly typed language without the fear of \`any\` types.`)} -

- //spacing between columns -
-

- {React.string(`ReScript is used to ship and maintain mission-critical products with good UI and UX.`)} -

- // The alignment classes move onto the instructions root so the column still hugs the lower edge without an extra wrapper. - -
-
-
- } -} - -// Main unique selling points -module MainUSP = { - module Item = { - type polygonDirection = Up | Down - - @react.component - let make = ( - ~caption: string, - ~title: React.element, - ~media: React.element=React.string("Placeholder"), - ~polygonDirection: polygonDirection=Down, - ~paragraph: React.element, - ) => { - let polyPointsLg = switch polygonDirection { - | Down => "80,0 85,100 100,100 100,0" - | Up => "85,0 80,100 100,100 100,0" - } - - let polyPointsMobile = switch polygonDirection { - | Down => "0,100 100,100 100,70 0,80" - | Up => "0,100 100,100 100,78 0,72" - } - - let polyColor = switch polygonDirection { - | Up => "text-fire" - | Down => "text-fire-30" - } - -
- // Content -
-
-
{React.string(caption)}
-

title

-
paragraph
-
- //image (right) -
-
- media -
- -
-
- // Mobile SVG - - - - // Tablet / Desktop SVG - - - -
- } - } - - let item1 = - - - } - paragraph={<> -

- {React.string(`ReScript cares about a consistent and fast - feedback loop for any codebase size. Refactor code, pull complex changes, - or switch to feature branches as you please. No sluggish CI builds, stale - caches, wrong type hints, or memory hungry language servers that slow you - down.`)} -

-

- // ReactRouter.Link.to)}> - // - // -

- } - /> - - let item2 = - - {React.string("Type Better")} - } - media={} - polygonDirection=Up - paragraph={React.string(`Every ReScript app is fully typed and provides - reliable type information for any given value in your program. We - prioritize simpler types over complex types for the sake of - clarity and easy debugability. No \`any\`, no magic types, no surprise - \`undefined\`. - `)} - /> - - let item3 = - - {React.string("The familiar JS ecosystem")} - {React.string(" at your fingertips")} - } - media={} - paragraph={React.string(`Use any library from JavaScript, export ReScript - libraries to JavaScript, automatically generate TypeScript types. It's - like you've never left the good parts of JavaScript at all.`)} - /> - - @react.component - let make = () => { -
- item1 - item2 - item3 -
- } -} - -module OtherSellingPoints = { - @react.component - let make = () => { -
- //defines the grid -
- //Large Item -
- -

- {React.string(`A community of programmers who value getting things done`)} -

-

- {React.string(`No language can be popular without a solid - community. A great type system isn't useful if library authors - abuse it. Performance doesn't show if all the libraries are slow. - Join the ReScript community — A group of companies and individuals - who deeply care about simplicity, speed and practicality.`)} -

- // The forum link becomes inline-block so the moved top margin still participates in the card flow. - - - -
- // 2 small items - // Item 2 -
- -

- {React.string(`Tooling that just works out of the box`)} -

-

- {React.string(`A builtin pretty printer, memory friendly - VSCode & Vim plugins, a stable type system and compiler that doesn't require lots - of extra configuration. ReScript brings all the tools you need to - build reliable JavaScript, Node and ReactJS applications.`)} -

-
- // Item 3 -
- -

- {React.string(`Easy to adopt — without any lock-in`)} -

-

- {React.string(`ReScript was made with gradual adoption in mind. If - you ever want to go back to plain JavaScript, just remove all - source files and keep its clean JavaScript output. Tell - your coworkers that your project will keep functioning with or - without ReScript!`)} -

-
- //
- -
- } -} - -module TrustedBy = { - @react.component - let make = () => { - let ourUsersSourcePath = "apps/docs/src/data/OurUsers.res" - -
-

- {React.string("Trusted by our users")} -

-
- {OurUsers.companies - ->Array.map(company => - switch company { - | Logo({name, path, url}) => - - - - } - ) - ->React.array} -
- - - - // The grid image now owns the alignment classes directly because the wrapper only clipped a single child. - -
- } -} - -module CuratedResources = { - type card = { - imgSrc: string, - title: React.element, - descr: string, - href: string, - } - - let cards = [ - { - imgSrc: "/ic_manual@2x.avif", - title: React.string("Language Manual"), - descr: "Look up the basics: Reference for all our language features", - href: "/docs/manual/introduction", - }, - { - imgSrc: "/ic_rescript_react@2x.avif", - title: React.string("ReScript + React"), - descr: "First Class bindings for ReactJS used by production users all over the world.", - href: "/docs/react/introduction", - }, - { - imgSrc: "/ic_manual@2x.avif", - title: React.string("Gradually Adopt ReScript"), - descr: "Learn how to start using ReScript in your current projects. Try before you buy!", - href: "/docs/manual/installation#integrate-into-an-existing-js-project", - }, - { - imgSrc: "/ic_gentype@2x.avif", - title: React.string("TypeScript Integration"), - descr: "Learn how to integrate ReScript in your existing TypeScript codebases.", - href: "/docs/manual/typescript-integration", - }, - ] - - let templates = [ - { - imgSrc: "/nextjs_starter_logo.svg", - title: <> - {React.string("ReScript & ")} - {React.string("NextJS")} - , - descr: "Get started with our NextJS starter template.", - href: "https://github.com/rescript-lang/create-rescript-app/blob/master/templates/rescript-template-nextjs/README.md", - }, - { - imgSrc: "/vitejs_starter_logo.svg", - title: <> - {React.string("ReScript & ")} - {React.string("ViteJS")} - , - descr: "Get started with ViteJS and ReScript.", - href: "https://github.com/rescript-lang/create-rescript-app/blob/master/templates/rescript-template-vite/README.md", - }, - { - imgSrc: "/nodejs_starter_logo.svg", - title: <> - {React.string("ReScript & ")} - - {React.string("NodeJS")} - - , - descr: "Get started with ReScript targeting the Node platform.", - href: "/", - }, - ] - - @react.component - let make = () => { -
- //headline container -
-
- {React.string("Get up and running with ReScript")} -
-

{React.string("Curated resources")}

-
-
-
- {React.string("Guides and Docs")} -
-
-
- - //divider - - //container for guides -
-
- {cards - ->Array.mapWithIndex((card, i) => - - -
{card.title}
-
{React.string(card.descr)}
-
- ) - ->React.array} -
- //Container for templates -
-
- {React.string("Templates")} -
-
-
-
- {templates - ->Array.mapWithIndex((card, i) => - - -
{card.title}
-
{React.string(card.descr)}
-
- ) - ->React.array} -
-
-
- } -} - @react.component -let make = (~components=MarkdownComponents.default) => { +let make = () => { <> { confidence as your codebase grows.`} keywords=["ReScript", "rescriptlang", "JavaScript", "JS", "TypeScript"] /> - // Keep the inherited typography and stacking context on the absolute shell while removing the inert wrapper chain around the page content.
- // Enable this when v13 is released - // - // {React.string("ReScript 12 is out! Read the ")} - // - // {React.string("announcement blog post")} - // - // {React.string(".")} - //
- +
- - - - - - + + + + + +