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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ _scripts
# Local env files
.env.local

# Local git worktrees
.worktrees

# Vitest screenshots
!__tests__/__screenshots__/**/*
.vitest-attachments
56 changes: 56 additions & 0 deletions __tests__/MetaDescription_.test.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
open Vitest

test("returns the first sentence for a one-sentence description", async () => {
let result = MetaDescription.shortenForSocialPreview("JavaScript Made Simple for Humans and AI.")

expect(result)->toBe("JavaScript Made Simple for Humans and AI.")
})

test("includes the second sentence when both sentences are within 140 characters", async () => {
let result = MetaDescription.shortenForSocialPreview(
"JavaScript Made Simple for Humans and AI. ReScript is a strongly typed language that compiles to clean, efficient JavaScript that humans and AI tools can read and understand.",
)

expect(result)->toBe(
"JavaScript Made Simple for Humans and AI. ReScript is a strongly typed language that compiles to clean, efficient JavaScript that humans and AI tools can read and understand.",
)
})

test("returns only the first sentence when the first sentence exceeds 140 characters", async () => {
let result = MetaDescription.shortenForSocialPreview(
"This first sentence is intentionally long enough to cross the one hundred and forty character threshold before it reaches its final word in the sentence. Short follow up.",
)

expect(result)->toBe(
"This first sentence is intentionally long enough to cross the one hundred and forty character threshold before it reaches its final word in the sentence.",
)
})

test(
"returns only the first sentence when the second sentence exceeds 140 characters",
async () => {
let result = MetaDescription.shortenForSocialPreview(
"Short opening sentence. This second sentence is intentionally long enough to cross the one hundred and forty character threshold before it reaches its final word in the sentence.",
)

expect(result)->toBe("Short opening sentence.")
},
)

test("collapses line breaks and repeated spaces before evaluating the sentences", async () => {
let result = MetaDescription.shortenForSocialPreview(
"JavaScript Made Simple for Humans and AI.\n\nReScript is a strongly typed language that compiles to clean, efficient JavaScript that humans and AI tools can read and understand.",
)

expect(result)->toBe(
"JavaScript Made Simple for Humans and AI. ReScript is a strongly typed language that compiles to clean, efficient JavaScript that humans and AI tools can read and understand.",
)
})

test("ignores empty sentence fragments created by repeated punctuation", async () => {
let result = MetaDescription.shortenForSocialPreview(
"First sentence... Second sentence stays short.",
)

expect(result)->toBe("First sentence. Second sentence stays short.")
})
32 changes: 22 additions & 10 deletions functions/ogimage/[[path]]/index.png.res
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,24 @@ type context = {request: FetchAPI.request, params: {path: array<string>}}

let onRequest = async ({params}: context) => {
let title = params.path[0]->Option.getOr("ReScript")->decodeURIComponent
// let url = WebAPI.URL.make(~url=request.url)
// let title = url.searchParams->URLSearchParams.get("title")
let descripton = params.path[1]->Option.getOr("ReScript")->decodeURIComponent
let description = params.path[1]->Option.getOr("ReScript")->decodeURIComponent

// we want to split the title if it contains a |
let (title, subTitle) = {
let segments = title->String.split("|")
(segments[0]->Option.getOr(""), segments[1])
let (title, subTitle, description) = {
let titleSegments = title->String.split("|")
// if the description contains a `.` we want to split it up and use the first sentence as the subTitle
let descriptionSegments = description->String.split(".")

let (subTitle, description) = switch titleSegments[1] {
| Some(subTitle) => (Some(subTitle), description)
| None =>
switch descriptionSegments[1] {
| Some(description) => (descriptionSegments[0], description)
| None => (None, description)
}
}

(titleSegments[0]->Option.getOr(""), subTitle, description)
}

Cloudflare.imageResponse(
Expand Down Expand Up @@ -89,14 +99,16 @@ let onRequest = async ({params}: context) => {
<p
style={{
fontFamily: "text",
fontSize: "32px",
fontSize: "28px",
lineHeight: "36px",
opacity: "0.9",
maxWidth: "900px",
textWrap: "balance",
// extra space since X wants to overlay the text
maxWidth: "900px",
maxHeight: "108px",
textWrap: "pretty",
}}
>
{React.string(descripton)}
{React.string(description)}
</p>
</div>,
{
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,6 @@
"vite-plugin-page-reload": "^0.2.3",
"vitest": "^4.1.2",
"vitest-browser-react": "^2.2.0",
"wrangler": "^4.80.0"
"wrangler": "^4.85.0"
}
}
25 changes: 25 additions & 0 deletions src/common/MetaDescription.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
let maxSentenceLength = 140

let collapseWhitespace = value => value->String.trim->String.replaceAllRegExp(/\s+/g, " ")

let ensurePeriod = sentence =>
if sentence->String.endsWith(".") {
sentence
} else {
sentence ++ "."
}

let shortenForSocialPreview = description => {
let normalized = collapseWhitespace(description)
let sentences =
normalized->String.split(".")->Array.map(String.trim)->Array.filter(sentence => sentence != "")

switch (sentences->Array.get(0), sentences->Array.get(1)) {
| (Some(firstSentence), Some(secondSentence))
if String.length(firstSentence) <= maxSentenceLength &&
String.length(secondSentence) <= maxSentenceLength =>
ensurePeriod(firstSentence) ++ " " ++ ensurePeriod(secondSentence)
| (Some(firstSentence), _) => ensurePeriod(firstSentence)
| _ => normalized
}
}
28 changes: 22 additions & 6 deletions src/components/LandingPage.res
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,27 @@ module Intro = {
@react.component
let make = () => {
<section className="flex justify-center">
// 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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

smart!

<link
href="https://fonts.googleapis.com/css2?family=Red+Hat+Mono:wght@700&display=swap"
rel="stylesheet"
/>
<div className="max-w-1060 flex flex-col items-center px-5 sm:px-8 lg:box-content">
<h1 className="hl-title text-center max-w-212">
{React.string("Fast, Simple, Fully Typed JavaScript from the Future")}
{React.string("JavaScript Made Simple for Humans and AI")}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hope @ryyppy agrees with this one.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll wait until he can take a look at it before I merge it in.

</h1>
<h2 className="body-lg text-center text-gray-60 my-4 max-w-md">
{React.string(`ReScript is a robustly typed language that compiles to efficient
and human-readable JavaScript. It comes with a lightning fast
compiler toolchain that scales to any codebase size.`)}
<h2 className="red-hat-mono-bold hl-1 text-center text-gray-60 my-4 max-w-md">
{React.string(`Types > Vibes`)}
</h2>
<p className="body-lg text-center text-gray-60 mt-4 max-w-md">
{React.string(`ReScript is a strongly typed language that compiles to clean,
efficient JavaScript that humans and AI tools can read and understand.`)}
</p>
<p className="body-lg text-center text-gray-60 my-4 max-w-md">
{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.`)}
</p>
<div className="mt-4 mb-2">
<ReactRouter.Link to=#"/docs/manual/installation" prefetch=#viewport>
<Button> {React.string("Get started")} </Button>
Expand Down Expand Up @@ -683,7 +695,11 @@ let make = (~components=MarkdownComponents.default) => {
<>
<Meta
title="The ReScript Programming Language"
description="Fast, Simple, Fully Typed JavaScript from the Future"
description={`JavaScript Made Simple for Humans and AI. ReScript is a strongly typed language that compiles to clean,
efficient JavaScript that humans and AI tools can read and understand.
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.`}
keywords=["ReScript", "rescriptlang", "JavaScript", "JS", "TypeScript"]
/>
<div className="mt-4 xs:mt-16">
Expand Down
19 changes: 13 additions & 6 deletions src/components/Meta.res
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
/*
canonical: Set a canonical URL pointing to the original content.
*/
@react.component
let make = (
~siteName="ReScript Documentation",
~keywords: array<string>=[],
~description="The ReScript language and ecosystem docs",
/*
* canonical: Set a canonical URL pointing to the original content.
*/
~canonical=?,
~title=?,
~ogLocale="en_US",
~ogSiteName=siteName,
~ogDescription=description,
~ogDescription=?,
~ogTitle=?,
~ogImage=?,
) => {
let description = description->MetaDescription.shortenForSocialPreview

let title = switch title {
| None
| Some("") => siteName
Expand All @@ -32,6 +34,11 @@ let make = (
| Some(ogImage) => ogImage
}

let ogDescription = switch ogDescription {
| None => description
| Some(description) => description
}

<>
<title key="title"> {React.string(title)} </title>
<meta charSet="utf-8" />
Expand All @@ -58,8 +65,8 @@ let make = (
<meta key="og:image" property="og:image" content=ogImage />

/* Twitter Meta */
<meta key="twitter:title" name="twitter:title" content=title />
<meta key="twitter:description" name="twitter:description" content=description />
<meta key="twitter:title" name="twitter:title" content=ogTitle />
<meta key="twitter:description" name="twitter:description" content=ogDescription />
<meta key="twitter:site" name="twitter:site" content="@rescriptlang" />
<meta key="twitter:image" property="og:image" content=ogImage />
<meta key="twitter:creator" name="twitter:creator" content="@ReScriptAssoc" />
Expand Down
7 changes: 7 additions & 0 deletions styles/_fonts.css
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,10 @@ $ pyftsubset <path_to_font> --unicodes="U+0000-00FF, U+0131, U+0152-0153, U+02BB
U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
U+FFFD;
}

.red-hat-mono-bold {
font-family: "Red Hat Mono", monospace;
font-optical-sizing: auto;
font-weight: 700;
font-style: normal;
}
Loading
Loading