Skip to content

Commit 32fcf6f

Browse files
committed
patches for the vercel og library and routes
1 parent 55f371b commit 32fcf6f

File tree

7 files changed

+206
-1
lines changed

7 files changed

+206
-1
lines changed

examples/api/app/og/route.tsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { ImageResponse } from "next/og";
2+
3+
export const dynamic = "force-dynamic";
4+
5+
export async function GET() {
6+
try {
7+
return new ImageResponse(
8+
(
9+
<div
10+
style={{
11+
backgroundColor: "black",
12+
backgroundSize: "150px 150px",
13+
height: "100%",
14+
width: "100%",
15+
display: "flex",
16+
textAlign: "center",
17+
alignItems: "center",
18+
justifyContent: "center",
19+
flexDirection: "column",
20+
flexWrap: "nowrap",
21+
}}
22+
>
23+
<div
24+
style={{
25+
display: "flex",
26+
alignItems: "center",
27+
justifyContent: "center",
28+
justifyItems: "center",
29+
}}
30+
>
31+
<img
32+
alt="Vercel"
33+
height={200}
34+
src="data:image/svg+xml,%3Csvg width='116' height='100' fill='white' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M57.5 0L115 100H0L57.5 0z' /%3E%3C/svg%3E"
35+
style={{ margin: "0 30px" }}
36+
width={232}
37+
/>
38+
</div>
39+
<div
40+
style={{
41+
fontSize: 60,
42+
fontStyle: "normal",
43+
letterSpacing: "-0.025em",
44+
color: "white",
45+
marginTop: 30,
46+
padding: "0 120px",
47+
lineHeight: 1.4,
48+
whiteSpace: "pre-wrap",
49+
}}
50+
>
51+
'next/og'
52+
</div>
53+
</div>
54+
),
55+
{
56+
width: 1200,
57+
height: 630,
58+
}
59+
);
60+
} catch (e: any) {
61+
return new Response("Failed to generate the image", {
62+
status: 500,
63+
});
64+
}
65+
}

examples/api/e2e/base.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,9 @@ test("returns correct information about the request from a route handler", async
4242
const expectedURL = expect.stringMatching(/https?:\/\/localhost:(?!3000)\d+\/api\/request/);
4343
await expect(res.json()).resolves.toEqual({ nextUrl: expectedURL, url: expectedURL });
4444
});
45+
46+
test("generates an og image successfully", async ({ page }) => {
47+
const res = await page.request.get("/og");
48+
expect(res.status()).toEqual(200);
49+
expect(res.headers()["content-type"]).toEqual("image/png");
50+
});

packages/cloudflare/src/cli/build/bundle-server.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ export async function bundleServer(buildOpts: BuildOptions): Promise<void> {
3333
console.log(`\x1b[35m⚙️ Bundling the OpenNext server...\n\x1b[0m`);
3434

3535
patches.patchWranglerDeps(buildOpts);
36-
patches.updateWebpackChunksFile(buildOpts);
36+
await patches.updateWebpackChunksFile(buildOpts);
37+
await patches.patchVercelOgLibrary(buildOpts);
3738

3839
const outputPath = path.join(outputDir, "server-functions", "default");
3940
const packagePath = getPackagePath(buildOpts);

packages/cloudflare/src/cli/build/patches/ast/util.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { readFileSync } from "node:fs";
2+
13
import { type Edit, Lang, type NapiConfig, parse, type SgNode } from "@ast-grep/napi";
24
import yaml from "yaml";
35

@@ -53,6 +55,17 @@ export function applyRule(rule: string | RuleConfig, root: SgNode, { once = fals
5355
return { edits, matches };
5456
}
5557

58+
/**
59+
* Parse a file and obtain its root.
60+
*
61+
* @param path The file path
62+
* @param lang The language to parse. Defaults to TypeScript.
63+
* @returns The root for the file.
64+
*/
65+
export function parseFile(path: string, lang = Lang.TypeScript) {
66+
return parse(lang, readFileSync(path, { encoding: "utf-8" })).root();
67+
}
68+
5669
/**
5770
* Patches the code from by applying the rule.
5871
*
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from "./copy-package-cli-files.js";
22
export * from "./patch-cache.js";
33
export * from "./patch-require.js";
4+
export * from "./patch-vercel-og-library.js";
45
export * from "./update-webpack-chunks-file/index.js";
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
2+
import path from "node:path";
3+
4+
import { BuildOptions } from "@opennextjs/aws/build/helper.js";
5+
import mockFs from "mock-fs";
6+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
7+
8+
import { patchVercelOgLibrary } from "./patch-vercel-og-library";
9+
10+
const nodeModulesVercelOgDir = "node_modules/.pnpm/[email protected]/node_modules/next/dist/compiled/@vercel/og";
11+
const nextServerOgNftPath = "examples/api/.next/server/app/og/route.js.nft.json";
12+
const openNextFunctionDir = "examples/api/.open-next/server-functions/default/examples/api";
13+
const openNextOgRoutePath = path.join(openNextFunctionDir, ".next/server/app/og/route.js");
14+
const openNextVercelOgDir = path.join(openNextFunctionDir, "node_modules/next/dist/compiled/@vercel/og");
15+
16+
const buildOpts = {
17+
appBuildOutputPath: "examples/api",
18+
monorepoRoot: "",
19+
outputDir: "examples/api/.open-next",
20+
} as BuildOptions;
21+
22+
describe("patchVercelOgLibrary", () => {
23+
beforeAll(() => {
24+
mockFs();
25+
26+
mkdirSync(nodeModulesVercelOgDir, { recursive: true });
27+
mkdirSync(path.dirname(nextServerOgNftPath), { recursive: true });
28+
mkdirSync(path.dirname(openNextOgRoutePath), { recursive: true });
29+
mkdirSync(openNextVercelOgDir, { recursive: true });
30+
31+
writeFileSync(
32+
nextServerOgNftPath,
33+
JSON.stringify({ version: 1, files: [`../../../../../../${nodeModulesVercelOgDir}/index.node.js`] })
34+
);
35+
writeFileSync(
36+
path.join(nodeModulesVercelOgDir, "index.edge.js"),
37+
`var fallbackFont = fetch(new URL("./noto-sans-v27-latin-regular.ttf", import.meta.url)).then((res) => res.arrayBuffer());`
38+
);
39+
writeFileSync(openNextOgRoutePath, `e.exports=import("next/dist/compiled/@vercel/og/index.node.js")`);
40+
writeFileSync(path.join(openNextVercelOgDir, "index.node.js"), "");
41+
writeFileSync(path.join(openNextVercelOgDir, "noto-sans-v27-latin-regular.ttf"), "");
42+
});
43+
44+
afterAll(() => mockFs.restore());
45+
46+
it("should patch the open-next files correctly", () => {
47+
patchVercelOgLibrary(buildOpts);
48+
49+
expect(readdirSync(openNextVercelOgDir)).toMatchInlineSnapshot(`
50+
[
51+
"index.edge.js",
52+
"noto-sans-v27-latin-regular.ttf.bin",
53+
]
54+
`);
55+
56+
expect(readFileSync(path.join(openNextVercelOgDir, "index.edge.js"), { encoding: "utf-8" }))
57+
.toMatchInlineSnapshot(`
58+
"async function getFallbackFont() {
59+
return (await import("./noto-sans-v27-latin-regular.ttf.bin")).default
60+
}
61+
62+
var fallbackFont = getFallbackFont()"
63+
`);
64+
65+
expect(readFileSync(openNextOgRoutePath, { encoding: "utf-8" })).toMatchInlineSnapshot(
66+
`"e.exports=import("next/dist/compiled/@vercel/og/index.edge.js")"`
67+
);
68+
});
69+
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { copyFileSync, existsSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
2+
import path from "node:path";
3+
4+
import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
5+
import { getPackagePath } from "@opennextjs/aws/build/helper.js";
6+
import { globSync } from "glob";
7+
8+
import { parseFile } from "../ast/util.js";
9+
import { patchVercelOgFallbackFont, patchVercelOgImport } from "../ast/vercel-og.js";
10+
11+
type TraceInfo = { version: number; files: string[] };
12+
13+
export function patchVercelOgLibrary(buildOpts: BuildOptions) {
14+
const { appBuildOutputPath, outputDir } = buildOpts;
15+
16+
const packagePath = path.join(outputDir, "server-functions/default", getPackagePath(buildOpts));
17+
18+
for (const traceInfoPath of globSync(path.join(appBuildOutputPath, ".next/server/**/*.nft.json"))) {
19+
const traceInfo: TraceInfo = JSON.parse(readFileSync(traceInfoPath, { encoding: "utf8" }));
20+
const tracedNodePath = traceInfo.files.find((p) => p.endsWith("@vercel/og/index.node.js"));
21+
22+
if (!tracedNodePath) continue;
23+
24+
const outputDir = path.join(packagePath, "node_modules/next/dist/compiled/@vercel/og");
25+
const outputEdgePath = path.join(outputDir, "index.edge.js");
26+
27+
if (!existsSync(outputEdgePath)) {
28+
const tracedEdgePath = path.join(
29+
path.dirname(traceInfoPath),
30+
tracedNodePath.replace("index.node.js", "index.edge.js")
31+
);
32+
33+
copyFileSync(tracedEdgePath, outputEdgePath);
34+
rmSync(outputEdgePath.replace("index.edge.js", "index.node.js"));
35+
36+
const node = parseFile(outputEdgePath);
37+
const { edits, matches } = patchVercelOgFallbackFont(node);
38+
writeFileSync(outputEdgePath, node.commitEdits(edits));
39+
40+
const fontFileName = matches[0]!.getMatch("PATH")!.text();
41+
renameSync(path.join(outputDir, fontFileName), path.join(outputDir, `${fontFileName}.bin`));
42+
}
43+
44+
const routeFilePath = traceInfoPath.replace(appBuildOutputPath, packagePath).replace(".nft.json", "");
45+
46+
const node = parseFile(routeFilePath);
47+
const { edits } = patchVercelOgImport(node);
48+
writeFileSync(routeFilePath, node.commitEdits(edits));
49+
}
50+
}

0 commit comments

Comments
 (0)