Skip to content

Commit 5ab1f6b

Browse files
authored
Merge pull request #501 from unpkg/mjackson/unpkg-x-preview-cards-0105fc
Add X/Open Graph preview cards
2 parents 7618e32 + 5baf7df commit 5ab1f6b

9 files changed

Lines changed: 265 additions & 3 deletions

File tree

‎package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
},
99
"scripts": {
1010
"build": "pnpm -r run build",
11+
"build:social-cards": "bun scripts/build-social-cards.ts",
1112
"clean": "git clean -fdX .",
1213
"clean:reports": "node -e \"fs.rmSync('.reports', { recursive: true, force: true }); fs.mkdirSync('.reports', { recursive: true });\"",
1314
"deploy:app": "pnpm --filter unpkg-app run deploy",

‎packages/unpkg-app/src/components/document.tsx‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export function Document({
1616
subtitle?: string;
1717
wwwOrigin?: string;
1818
}): VNode {
19+
let fullTitle = subtitle == null ? title : `${title} • ${subtitle}`;
20+
1921
let importMap: ImportMap = {
2022
imports: {
2123
preact: new URL("/preact@10.25.4/dist/preact.module.js", wwwOrigin).href,
@@ -31,6 +33,17 @@ export function Document({
3133
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
3234
<meta name="description" content={description} />
3335
<meta name="color-scheme" content="light dark" />
36+
37+
<meta property="og:type" content="website" />
38+
<meta property="og:site_name" content="UNPKG" />
39+
<meta property="og:title" content={fullTitle} />
40+
<meta property="og:description" content={description} />
41+
<meta property="og:image" content={new URL("/social-card.png", wwwOrigin).href} />
42+
<meta property="og:image:width" content="1200" />
43+
<meta property="og:image:height" content="630" />
44+
<meta property="og:image:alt" content={description} />
45+
<meta name="twitter:card" content="summary_large_image" />
46+
<meta name="twitter:site" content="@unpkg" />
3447
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
3548
<meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)" />
3649

@@ -42,7 +55,7 @@ export function Document({
4255
<script type="importmap" dangerouslySetInnerHTML={{ __html: JSON.stringify(importMap) }} />
4356
<script type="module" src={useAsset("assets/scripts.ts")} defer></script>
4457

45-
<title>{subtitle == null ? title : `${title} • ${subtitle}`}</title>
58+
<title>{fullTitle}</title>
4659

4760
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-140352188-1"></script>
4861
<script
50.6 KB
Loading

‎packages/unpkg-esm/src/components/home-page.tsx‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export function createHomePage(env: Env): string {
2424
return (
2525
"<!DOCTYPE html>" +
2626
render(
27-
<Document>
27+
<Document origin={esmOrigin}>
2828
<Header />
2929
<main>
3030
<div class="content">
@@ -89,14 +89,25 @@ export function createHomePage(env: Env): string {
8989
);
9090
}
9191

92-
function Document({ children }: { children: ComponentChildren }): VNode {
92+
function Document({ children, origin }: { children: ComponentChildren; origin: string }): VNode {
9393
return (
9494
<html lang="en">
9595
<head>
9696
<meta charSet="utf-8" />
9797
<meta name="viewport" content="width=device-width, initial-scale=1" />
9898
<meta name="description" content="Browser-ready npm package imports from UNPKG." />
9999
<meta name="color-scheme" content="light dark" />
100+
101+
<meta property="og:type" content="website" />
102+
<meta property="og:site_name" content="UNPKG" />
103+
<meta property="og:title" content="UNPKG ESM" />
104+
<meta property="og:description" content="Browser-ready npm package imports from UNPKG." />
105+
<meta property="og:image" content={new URL("/social-card.png", origin).href} />
106+
<meta property="og:image:width" content="1200" />
107+
<meta property="og:image:height" content="630" />
108+
<meta property="og:image:alt" content="Browser-ready npm package imports from UNPKG." />
109+
<meta name="twitter:card" content="summary_large_image" />
110+
<meta name="twitter:site" content="@unpkg" />
100111
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
101112
<meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)" />
102113
<link rel="icon" type="image/png" href="/favicon.png" />
38.6 KB
Loading

‎packages/unpkg-www/src/components/document.tsx‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ export function Document({
2929
<meta name="viewport" content="width=device-width, initial-scale=1" />
3030
<meta name="description" content={description} />
3131
<meta name="color-scheme" content="light dark" />
32+
33+
<meta property="og:type" content="website" />
34+
<meta property="og:site_name" content="UNPKG" />
35+
<meta property="og:title" content={title} />
36+
<meta property="og:description" content={description} />
37+
<meta property="og:image" content={new URL("/social-card.png", origin).href} />
38+
<meta property="og:image:width" content="1200" />
39+
<meta property="og:image:height" content="630" />
40+
<meta property="og:image:alt" content={description} />
41+
<meta name="twitter:card" content="summary_large_image" />
42+
<meta name="twitter:site" content="@unpkg" />
3243
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
3344
<meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)" />
3445

‎pnpm-lock.yaml‎

Lines changed: 130 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎scripts/build-social-cards.ts‎

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import * as path from "node:path";
2+
3+
import { Resvg } from "@resvg/resvg-js";
4+
5+
// Generates the 1200x630 social card images (og:image / twitter:card) served at
6+
// /social-card.png by the www and esm workers (app pages reference the www card).
7+
// Run with `pnpm run build:social-cards` from the repo root after changing any of
8+
// the variants below.
9+
10+
const SANS = "Helvetica Neue, Helvetica, Arial, sans-serif";
11+
const MONO = "Menlo, Monaco, Consolas, monospace";
12+
13+
const BLUE = "#5da9ff";
14+
const GRAY = "#9ca3af";
15+
const DIM = "#6b7280";
16+
17+
interface Variant {
18+
file: string;
19+
title: string;
20+
tagline: string;
21+
url: Array<[text: string, color: string]>;
22+
}
23+
24+
const variants: Variant[] = [
25+
{
26+
file: "packages/unpkg-www/public/social-card.png",
27+
title: "UNPKG",
28+
tagline: "The CDN for everything on npm",
29+
url: [
30+
["unpkg.com/", GRAY],
31+
[":package", BLUE],
32+
["@", DIM],
33+
[":version", BLUE],
34+
["/", DIM],
35+
[":file", BLUE],
36+
],
37+
},
38+
{
39+
file: "packages/unpkg-esm/public/social-card.png",
40+
title: "UNPKG ESM",
41+
tagline: "Browser-ready ES modules for every npm package",
42+
url: [
43+
["esm.unpkg.com/", GRAY],
44+
[":package", BLUE],
45+
["@", DIM],
46+
[":version", BLUE],
47+
["/", DIM],
48+
[":subpath", BLUE],
49+
],
50+
},
51+
];
52+
53+
function createSvg(variant: Variant): string {
54+
let urlSpans = variant.url
55+
.map(([text, color]) => `<tspan fill="${color}">${text.replace(/&/g, "&amp;").replace(/</g, "&lt;")}</tspan>`)
56+
.join("");
57+
58+
// Menlo advances ~0.603em per character, so size the chip to fit the URL text
59+
let urlChars = variant.url.reduce((n, [text]) => n + text.length, 0);
60+
let chipWidth = 64 + Math.round(urlChars * 30 * 0.603);
61+
62+
return `<svg width="1200" height="630" viewBox="0 0 1200 630" xmlns="http://www.w3.org/2000/svg">
63+
<rect width="1200" height="630" fill="#000000"/>
64+
65+
<!-- giant cropped U watermark bleeding off the right edge -->
66+
<text x="1265" y="660" text-anchor="end" font-family="${SANS}" font-weight="bold" font-size="860"
67+
fill="#101010">U</text>
68+
69+
<!-- wordmark -->
70+
<text x="80" y="330" font-family="${SANS}" font-weight="bold" font-size="148" letter-spacing="-4"
71+
fill="#ffffff">${variant.title}</text>
72+
73+
<!-- tagline -->
74+
<text x="84" y="410" font-family="${SANS}" font-weight="normal" font-size="42"
75+
fill="#a3a3a3">${variant.tagline}</text>
76+
77+
<!-- URL pattern chip -->
78+
<rect x="80" y="472" rx="14" width="${chipWidth}" height="76" fill="#0d0d0d" stroke="#262626" stroke-width="2"/>
79+
<text x="112" y="521" font-family="${MONO}" font-size="30">${urlSpans}</text>
80+
</svg>`;
81+
}
82+
83+
let rootDir = path.resolve(import.meta.dirname, "..");
84+
85+
for (let variant of variants) {
86+
let resvg = new Resvg(createSvg(variant), {
87+
fitTo: { mode: "width", value: 1200 },
88+
font: { loadSystemFonts: true },
89+
});
90+
91+
let png = resvg.render().asPng();
92+
let file = path.join(rootDir, variant.file);
93+
await Bun.write(file, png);
94+
console.log(`Wrote ${variant.file} (${png.length} bytes)`);
95+
}

‎scripts/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"test": "bun test"
77
},
88
"dependencies": {
9+
"@resvg/resvg-js": "^2.6.2",
910
"@types/node": "^22.10.2",
1011
"chalk": "^5.4.1",
1112
"esbuild": "^0.24.2",

0 commit comments

Comments
 (0)