@pylonsync/functions 0.4.13 → 0.4.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ssr-runtime.d.ts +3 -1
- package/package.json +1 -1
- package/src/ssr-boundary-groups.test.ts +72 -0
- package/src/ssr-client-bundler.ts +12 -8
- package/src/ssr-runtime.ts +28 -3
package/dist/ssr-runtime.d.ts
CHANGED
|
@@ -344,7 +344,9 @@ export declare function isSafeRedirect(url: string, opts: {
|
|
|
344
344
|
/** Merge auto-discovered favicons (icon.* / apple-icon.* / favicon.ico)
|
|
345
345
|
* into a page's metadata. Explicit `metadata.icons.*` wins. */
|
|
346
346
|
export declare function applyAutoIcons(component: string, metadata: SsrMetadata | undefined): SsrMetadata | undefined;
|
|
347
|
-
export declare function applyAutoSocialImages(component: string, headers: Record<string, string> | undefined, metadata: SsrMetadata | undefined, requestUrl?: string
|
|
347
|
+
export declare function applyAutoSocialImages(component: string, headers: Record<string, string> | undefined, metadata: SsrMetadata | undefined, requestUrl?: string,
|
|
348
|
+
/** True for a not-found / error boundary — see findColocatedOgImageRoute. */
|
|
349
|
+
isBoundary?: boolean): SsrMetadata | undefined;
|
|
348
350
|
/**
|
|
349
351
|
* Drain a `renderToReadableStream` reader, injecting `headBlob` immediately
|
|
350
352
|
* before the first `</head>` (or, if the document has none, the blob is
|
package/package.json
CHANGED
|
@@ -181,3 +181,75 @@ describe("renderMetadata drives boundary heads too", () => {
|
|
|
181
181
|
expect(renderMetadata(require("react"), undefined)).toBeNull();
|
|
182
182
|
});
|
|
183
183
|
});
|
|
184
|
+
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// A boundary's dynamic og:image URL.
|
|
187
|
+
//
|
|
188
|
+
// Regression: the URL was composed from the REQUEST path while the file was
|
|
189
|
+
// resolved from the boundary's directory. A 404 therefore advertised a card
|
|
190
|
+
// under whatever path failed. Usually that 404s in turn — a broken card is
|
|
191
|
+
// worse than none, since a crawler now has something to fail on — but at a
|
|
192
|
+
// depth where a dynamic route really does define one, it returned 200 and
|
|
193
|
+
// served an unrelated page's card. Silent wrong content.
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
import { applyAutoSocialImages } from "./ssr-runtime";
|
|
197
|
+
|
|
198
|
+
describe("boundary og:image points at the boundary's own card", () => {
|
|
199
|
+
// Compare the PATH only. Absolutizing against the request origin needs a
|
|
200
|
+
// trusted host, which is a separate concern from which route the card lives
|
|
201
|
+
// at — and the path is the thing that regressed.
|
|
202
|
+
const ogPath = (m: any) => {
|
|
203
|
+
const u = m?.openGraph?.image as string | undefined;
|
|
204
|
+
if (!u) return u;
|
|
205
|
+
return u.startsWith("http") ? new URL(u).pathname : u;
|
|
206
|
+
};
|
|
207
|
+
const headers = { host: "example.com" };
|
|
208
|
+
|
|
209
|
+
test("a root boundary advertises the root card whatever URL failed", () => {
|
|
210
|
+
const root = tree([
|
|
211
|
+
"app/opengraph-image.tsx",
|
|
212
|
+
"app/not-found.tsx",
|
|
213
|
+
"app/[orgSlug]/[eventSlug]/opengraph-image.tsx",
|
|
214
|
+
"app/[orgSlug]/[eventSlug]/page.tsx",
|
|
215
|
+
]);
|
|
216
|
+
const prev = process.cwd();
|
|
217
|
+
process.chdir(root);
|
|
218
|
+
try {
|
|
219
|
+
for (const url of [
|
|
220
|
+
"/a/b/c/d/e",
|
|
221
|
+
"/nope-does-not-exist",
|
|
222
|
+
// The dangerous one: a real card EXISTS at this depth, so the old
|
|
223
|
+
// behavior returned 200 with another event's image.
|
|
224
|
+
"/no-such-org/no-such-event",
|
|
225
|
+
]) {
|
|
226
|
+
const md = applyAutoSocialImages("app/not-found", headers, undefined, url, true);
|
|
227
|
+
expect(ogPath(md), `for ${url}`).toBe("/opengraph-image");
|
|
228
|
+
}
|
|
229
|
+
} finally {
|
|
230
|
+
process.chdir(prev);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("a page still advertises the card at its own path", () => {
|
|
235
|
+
// The request path IS the route path for a page — including a tail a
|
|
236
|
+
// catch-all consumed, which is why that shortcut exists.
|
|
237
|
+
const root = tree([
|
|
238
|
+
"app/[orgSlug]/[eventSlug]/opengraph-image.tsx",
|
|
239
|
+
"app/[orgSlug]/[eventSlug]/page.tsx",
|
|
240
|
+
]);
|
|
241
|
+
const prev = process.cwd();
|
|
242
|
+
process.chdir(root);
|
|
243
|
+
try {
|
|
244
|
+
const md = applyAutoSocialImages(
|
|
245
|
+
"app/[orgSlug]/[eventSlug]/page",
|
|
246
|
+
headers,
|
|
247
|
+
undefined,
|
|
248
|
+
"/acme/summit",
|
|
249
|
+
);
|
|
250
|
+
expect(ogPath(md)).toBe("/acme/summit/opengraph-image");
|
|
251
|
+
} finally {
|
|
252
|
+
process.chdir(prev);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
});
|
|
@@ -1621,11 +1621,17 @@ async function _doBuildInner(
|
|
|
1621
1621
|
// against; the build itself is fine.
|
|
1622
1622
|
}
|
|
1623
1623
|
|
|
1624
|
-
// Scan a built JS file for
|
|
1625
|
-
//
|
|
1626
|
-
//
|
|
1627
|
-
//
|
|
1628
|
-
//
|
|
1624
|
+
// Scan a built JS file for STATIC `import` literals pointing at
|
|
1625
|
+
// `./chunks/<file>.js` and return them resolved to outdir-relative paths.
|
|
1626
|
+
// Bun's minified output uses simple double quotes for module specifiers,
|
|
1627
|
+
// so a `matchAll` covers both `import X from "Y"` and bare `import "Y"`.
|
|
1628
|
+
//
|
|
1629
|
+
// DYNAMIC `import("Y")` is deliberately NOT matched. This list becomes the
|
|
1630
|
+
// route's modulepreload set, and a chunk the author deferred behind
|
|
1631
|
+
// `import()` must not be downloaded eagerly anyway — that would split the
|
|
1632
|
+
// code out of the entry (so it isn't parsed on load) while still shipping
|
|
1633
|
+
// every byte, which is the expensive half of the cost with none of the
|
|
1634
|
+
// saving. The trailing `(` is what distinguishes the two forms.
|
|
1629
1635
|
function scanChunkImports(jsAbsPath: string): string[] {
|
|
1630
1636
|
let src: string;
|
|
1631
1637
|
try {
|
|
@@ -1634,9 +1640,7 @@ async function _doBuildInner(
|
|
|
1634
1640
|
return [];
|
|
1635
1641
|
}
|
|
1636
1642
|
const found = new Set<string>();
|
|
1637
|
-
const matches = src.matchAll(
|
|
1638
|
-
/(?:from\s*|import\s*\(?\s*)["']([^"']+)["']/g,
|
|
1639
|
-
);
|
|
1643
|
+
const matches = src.matchAll(/(?:from\s*|import\s*)["']([^"']+)["']/g);
|
|
1640
1644
|
for (const m of matches) {
|
|
1641
1645
|
const spec = m[1];
|
|
1642
1646
|
if (spec.startsWith("./chunks/") || spec.startsWith("chunks/")) {
|
package/src/ssr-runtime.ts
CHANGED
|
@@ -899,6 +899,17 @@ const OG_IMAGE_CODE_EXTS = [".tsx", ".ts", ".jsx", ".js"];
|
|
|
899
899
|
function findColocatedOgImageRoute(
|
|
900
900
|
componentPath: string,
|
|
901
901
|
url: string,
|
|
902
|
+
/**
|
|
903
|
+
* True when `componentPath` is a not-found / error boundary rather than a
|
|
904
|
+
* page. A page's URL depth matches its directory depth, so when the image
|
|
905
|
+
* sits in the page's own directory the request path IS the route path —
|
|
906
|
+
* including the tail a catch-all consumed, which is why that shortcut
|
|
907
|
+
* exists. A boundary is dispatched at whatever URL failed, so the same
|
|
908
|
+
* shortcut advertised a card under the 404'd path: mostly a 404 of its own,
|
|
909
|
+
* but at a depth where a dynamic route really does define one, a 200
|
|
910
|
+
* serving an unrelated page's card.
|
|
911
|
+
*/
|
|
912
|
+
isBoundary = false,
|
|
902
913
|
): string | null {
|
|
903
914
|
const fs = require("node:fs");
|
|
904
915
|
const path = require("node:path");
|
|
@@ -918,7 +929,9 @@ function findColocatedOgImageRoute(
|
|
|
918
929
|
for (const ext of OG_IMAGE_CODE_EXTS) {
|
|
919
930
|
if (fs.existsSync(path.join(cwd, dir, `opengraph-image${ext}`))) {
|
|
920
931
|
const prefix =
|
|
921
|
-
dir === pageDir
|
|
932
|
+
dir === pageDir && !isBoundary
|
|
933
|
+
? urlSegs
|
|
934
|
+
: urlSegs.slice(0, nonGroupDepth(dir));
|
|
922
935
|
return "/" + [...prefix, "opengraph-image"].join("/");
|
|
923
936
|
}
|
|
924
937
|
}
|
|
@@ -1287,6 +1300,8 @@ export function applyAutoSocialImages(
|
|
|
1287
1300
|
headers: Record<string, string> | undefined,
|
|
1288
1301
|
metadata: SsrMetadata | undefined,
|
|
1289
1302
|
requestUrl?: string,
|
|
1303
|
+
/** True for a not-found / error boundary — see findColocatedOgImageRoute. */
|
|
1304
|
+
isBoundary = false,
|
|
1290
1305
|
): SsrMetadata | undefined {
|
|
1291
1306
|
// A relative image is the single most common way a share card breaks:
|
|
1292
1307
|
// the page ships `og:image="/og.png"`, Facebook and Slack resolve it,
|
|
@@ -1310,7 +1325,7 @@ export function applyAutoSocialImages(
|
|
|
1310
1325
|
const ogRoute =
|
|
1311
1326
|
hasOg || ogFile || requestUrl == null
|
|
1312
1327
|
? null
|
|
1313
|
-
: findColocatedOgImageRoute(component, requestUrl);
|
|
1328
|
+
: findColocatedOgImageRoute(component, requestUrl, isBoundary);
|
|
1314
1329
|
|
|
1315
1330
|
if (!ogFile && !twFile && !ogRoute) return metadata;
|
|
1316
1331
|
|
|
@@ -2290,6 +2305,7 @@ async function tryRenderBoundary(
|
|
|
2290
2305
|
compProps?.headers,
|
|
2291
2306
|
boundaryMeta,
|
|
2292
2307
|
compProps?.url,
|
|
2308
|
+
true,
|
|
2293
2309
|
);
|
|
2294
2310
|
boundaryMeta = applyAutoIcons(rel, boundaryMeta);
|
|
2295
2311
|
const boundaryMetaFragment = renderMetadata(React, boundaryMeta);
|
|
@@ -3120,7 +3136,16 @@ export async function handleRenderRoute(
|
|
|
3120
3136
|
// File conventions: auto-wire <meta og:image>/<twitter:image> from a
|
|
3121
3137
|
// colocated opengraph-image.* / twitter-image.*, and <link rel="icon">
|
|
3122
3138
|
// from icon.* / apple-icon.* / favicon.ico — unless the page set them.
|
|
3123
|
-
|
|
3139
|
+
// The host dispatches `app/not-found` / `app/error` BY NAME for an
|
|
3140
|
+
// unmatched URL, so this path renders boundaries too.
|
|
3141
|
+
const componentIsBoundary = /(^|\/)(not-found|error)$/.test(msg.component);
|
|
3142
|
+
metadata = applyAutoSocialImages(
|
|
3143
|
+
msg.component,
|
|
3144
|
+
msg.headers,
|
|
3145
|
+
metadata,
|
|
3146
|
+
msg.url,
|
|
3147
|
+
componentIsBoundary,
|
|
3148
|
+
);
|
|
3124
3149
|
metadata = applyAutoIcons(msg.component, metadata);
|
|
3125
3150
|
const metaFragment = renderMetadata(React, metadata);
|
|
3126
3151
|
|