Skip to content

Commit 0848236

Browse files
authored
feat: add open graph image cloudflare function (#1190)
* add ogimage for blog * add text balance * remove console log * another style pass * make new image the default * split up sub headings * add image to api docs * remove duplicate meta components * fix formatting * handle docstrings better * update to latest wrangler
1 parent 30b32f3 commit 0848236

18 files changed

Lines changed: 256 additions & 79 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ jobs:
4141
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
4242
command: pages deploy out --project-name=rescript-lang-org
4343
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
44-
wranglerVersion: 4.61.1
45-
continue-on-error: true
44+
wranglerVersion: 4.63.0
4645
env:
4746
FORCE_COLOR: 0
4847
- name: Comment PR with deployment link

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ mdx-manifest.json
4646

4747
app/**/*.mjs
4848
app/**/*.jsx
49+
functions/**/*.mjs
50+
functions/**/*.jsx
4951
!_shims.mjs
5052
!_shims.jsx
5153

app/routes/ApiRoute.res

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,14 +140,28 @@ let default = () => {
140140
let {pathname} = ReactRouter.useLocation()
141141

142142
let segments = (pathname :> string)->String.split("/")
143-
let title = switch (segments[4], segments[5]) {
144-
| (Some(x), Some(y)) => `${x->String.capitalize}.${y->String.capitalize} | ReScript API`
145-
| (Some(x), None) => `${x->String.capitalize} | ReScript API`
146-
| _ => "ReScript API"
143+
144+
let _module = switch (segments[4], segments[5]) {
145+
| (Some(x), Some(y)) => Some(`${x->String.capitalize}.${y->String.capitalize}`)
146+
| (Some(x), None) => Some(`${x->String.capitalize}`)
147+
| _ => None
148+
}
149+
150+
let title = switch _module {
151+
| Some(_module) => _module ++ " | ReScript API"
152+
| None => "ReScript API"
147153
}
148154

155+
let docstrings =
156+
switch loaderData {
157+
| Ok(loaderData) => loaderData.module_.docstrings
158+
| Error(_) => []
159+
}
160+
->Array.at(0)
161+
->Option.flatMap(str => String.split(str, ".")[0])
162+
149163
<>
150-
<title> {React.string(title)} </title>
164+
<Meta title description=?docstrings />
151165
<ApiDocs {...loaderData} />
152166
</>
153167
}

app/routes/LandingPageRoute.res

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,5 @@
11
let default = () => {
22
<>
3-
<Meta
4-
title="The ReScript Programming Language"
5-
description="Fast, Simple, Fully Typed JavaScript from the Future"
6-
keywords=[
7-
"JavaScript",
8-
"JS",
9-
"programming language",
10-
"ReScript",
11-
"rescriptlang",
12-
"web development",
13-
]
14-
/>
153
<LandingPageLayout />
164
</>
175
}

app/routes/PackagesRoute.res

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,6 @@ let loader = async () => {
66

77
let default = () => {
88
let props = ReactRouter.useLoaderData()
9-
<>
10-
<Meta
11-
ogSiteName="ReScript Packages"
12-
title="Package Index | ReScript Documentation"
13-
description="Official and unofficial resources, libraries and bindings for ReScript"
14-
/>
15-
<Packages {...props} />
16-
</>
9+
10+
<Packages {...props} />
1711
}

functions/echo.jsx

Lines changed: 0 additions & 4 deletions
This file was deleted.

functions/ogimage.res

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
open WebAPI
2+
3+
%%raw("import React from 'react'")
4+
5+
let loadGoogleFont = async (family: string) => {
6+
let url = `https://fonts.googleapis.com/css2?family=${family}`
7+
let css = await (await fetch(url))->Response.text
8+
9+
// this function should fail if we can't load the font
10+
let resource =
11+
css->String.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/)->Option.getOrThrow
12+
let response = await fetch(resource[1]->Option.getOrThrow->Option.getOrThrow)
13+
await response->Response.arrayBuffer
14+
}
15+
16+
type context = {request: FetchAPI.request}
17+
18+
let onRequest = async ({request}: context) => {
19+
let url = WebAPI.URL.make(~url=request.url)
20+
let title = url.searchParams->URLSearchParams.get("title")
21+
let descripton = url.searchParams->URLSearchParams.get("description")
22+
23+
// we want to split the title if it contains a |
24+
let (title, subTitle) = {
25+
let segments = title->String.split("|")
26+
(segments[0]->Option.getOr(""), segments[1])
27+
}
28+
29+
Cloudflare.imageResponse(
30+
<div
31+
style={{
32+
width: "1200px",
33+
height: "630px",
34+
background: "#0B0D22",
35+
backgroundImage: "linear-gradient(45deg, #0B0D22 70%, #14162c)",
36+
color: "#efefef",
37+
display: "flex",
38+
flexDirection: "column",
39+
justifyContent: "center",
40+
alignItems: "flex-start",
41+
textAlign: "left",
42+
position: "relative",
43+
padding: "60px",
44+
boxSizing: "border-box",
45+
}}
46+
>
47+
<img
48+
src="https://rescript-lang.org/brand/rescript-logo.svg"
49+
style={{
50+
maxWidth: "300px",
51+
objectFit: "contain",
52+
marginBottom: "10px",
53+
}}
54+
/>
55+
<h1
56+
style={{
57+
fontSize: "64px",
58+
fontWeight: "600",
59+
marginBottom: "20px",
60+
maxWidth: "996px",
61+
fontFamily: "heading",
62+
textWrap: "balance",
63+
}}
64+
>
65+
{React.string(title)}
66+
</h1>
67+
{switch subTitle {
68+
| Some(subTitle) =>
69+
<h2
70+
style={{
71+
fontSize: "40px",
72+
fontWeight: "600",
73+
marginBottom: "20px",
74+
maxWidth: "996px",
75+
fontFamily: "heading",
76+
textWrap: "balance",
77+
}}
78+
>
79+
{React.string(subTitle)}
80+
</h2>
81+
| None => React.null
82+
}}
83+
<hr
84+
style={{
85+
borderTop: "2px solid #efefef",
86+
width: "100%",
87+
}}
88+
/>
89+
<p
90+
style={{
91+
fontFamily: "text",
92+
fontSize: "32px",
93+
opacity: "0.9",
94+
maxWidth: "900px",
95+
textWrap: "balance",
96+
}}
97+
>
98+
{React.string(descripton)}
99+
</p>
100+
</div>,
101+
{
102+
height: 630,
103+
width: 1200,
104+
fonts: [
105+
{
106+
data: await loadGoogleFont("Inter:opsz,wght@14..32,600&display=swap"),
107+
name: "heading",
108+
style: #normal,
109+
weight: 600,
110+
},
111+
{
112+
data: await loadGoogleFont("Inter:opsz,wght@14..32,400&display=swap"),
113+
name: "text",
114+
style: #normal,
115+
weight: 400,
116+
},
117+
],
118+
},
119+
)
120+
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"@babel/generator": "^7.24.7",
3535
"@babel/parser": "^7.24.7",
3636
"@babel/traverse": "^7.24.7",
37+
"@cloudflare/pages-plugin-vercel-og": "^0.1.2",
3738
"@codemirror/commands": "^6.9.0",
3839
"@codemirror/lang-javascript": "^6.2.4",
3940
"@codemirror/language": "^6.11.3",
@@ -99,6 +100,6 @@
99100
"vite-plugin-devtools-json": "^1.0.0",
100101
"vite-plugin-env-compatible": "^2.0.1",
101102
"vite-plugin-page-reload": "^0.2.2",
102-
"wrangler": "^4.61.1"
103+
"wrangler": "^4.63.0"
103104
}
104105
}

rescript.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
{
2020
"dir": "scripts",
2121
"subdirs": true
22+
},
23+
{
24+
"dir": "functions",
25+
"subdirs": true
2226
}
2327
],
2428
"package-specs": {

src/BlogArticle.res

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,15 +119,15 @@ let make = (props: props) => {
119119
}
120120
: React.null
121121

122-
let {date, author, co_authors, title, description, articleImg, previewImg} = frontmatter
122+
let {date, author, co_authors, title, description, articleImg} = frontmatter
123123

124124
<MainLayout>
125125
<div className="w-full">
126126
<Meta
127127
siteName="ReScript Blog"
128128
title={title ++ " | ReScript Blog"}
129129
description=?{description->Nullable.toOption}
130-
ogImage={previewImg->Nullable.toOption->Option.getOr(Blog.defaultPreviewImg)}
130+
ogImage={Util.Url.makeOpenGraphImageUrl(title, description->Nullable.getOr(""))}
131131
/>
132132
<div className="mb-10 md:mb-20">
133133
<BlogHeader

0 commit comments

Comments
 (0)