@moku-labs/web 1.4.1 → 1.5.0
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/browser.d.mts +19 -0
- package/dist/browser.mjs +83 -22
- package/dist/index.cjs +106 -30
- package/dist/index.d.cts +19 -0
- package/dist/index.d.mts +19 -0
- package/dist/index.mjs +106 -30
- package/package.json +1 -1
package/dist/browser.d.mts
CHANGED
|
@@ -1138,6 +1138,25 @@ type Api$1 = {
|
|
|
1138
1138
|
* ```
|
|
1139
1139
|
*/
|
|
1140
1140
|
render(route: ResolvedRoute, data: unknown): string;
|
|
1141
|
+
/**
|
|
1142
|
+
* Compose the SITE-LEVEL `<head>` Open Graph / Twitter block for a bare-path redirect or
|
|
1143
|
+
* landing page that has no route identity (e.g. the apex-domain `/` redirect a
|
|
1144
|
+
* `localeRedirects` build emits). Returns `""` UNLESS `defaultOgImage` is configured, so
|
|
1145
|
+
* apps that opt out keep a bare redirect. Pulled synchronously by `build`.
|
|
1146
|
+
*
|
|
1147
|
+
* @param input - The landing URL (resolved to an absolute canonical) plus an optional locale.
|
|
1148
|
+
* @param input.url - The page's URL or path (absolutized via `site.canonical`) → `og:url`.
|
|
1149
|
+
* @param input.locale - Optional locale whose `og:locale` is emitted (e.g. the default locale).
|
|
1150
|
+
* @returns The serialized inner HTML of the site-level head block, or `""` when disabled.
|
|
1151
|
+
* @example
|
|
1152
|
+
* ```ts
|
|
1153
|
+
* api.siteHead({ url: "/en/", locale: "en" });
|
|
1154
|
+
* ```
|
|
1155
|
+
*/
|
|
1156
|
+
siteHead(input: {
|
|
1157
|
+
url: string;
|
|
1158
|
+
locale?: string;
|
|
1159
|
+
}): string;
|
|
1141
1160
|
};
|
|
1142
1161
|
declare namespace types_d_exports$6 {
|
|
1143
1162
|
export { COMPONENT_HOOK_NAMES, ComponentContext, ComponentDef, ComponentHooks, ComponentInstance, ExtractApi, PageData, ResolvedSpaConfig, SpaApi, SpaConfig, SpaContext, SpaDataReader, SpaEmitFunction, SpaEvents, SpaKernel, SpaKernelDeps, SpaRequire, SpaState };
|
package/dist/browser.mjs
CHANGED
|
@@ -2514,6 +2514,42 @@ function composeHead(input) {
|
|
|
2514
2514
|
}), ...head.elements ?? []]);
|
|
2515
2515
|
}
|
|
2516
2516
|
/**
|
|
2517
|
+
* Compose the SITE-LEVEL Open Graph / Twitter block for a bare-path redirect or landing
|
|
2518
|
+
* page that has no per-route head of its own. Returns `[]` UNLESS a `defaultOgImage` is
|
|
2519
|
+
* configured — so apps that opt out keep a bare redirect (no behavior change). The site
|
|
2520
|
+
* name + description become the card's title/description (`og:type=website`); `url` is the
|
|
2521
|
+
* canonical the page points at. A bare article/tag alias gets this site card as a fallback;
|
|
2522
|
+
* crawlers that honor the page's `rel=canonical` still resolve the per-route card.
|
|
2523
|
+
*
|
|
2524
|
+
* @param input - The site slice, head defaults, landing URL, and optional `og:locale`.
|
|
2525
|
+
* @returns The ordered site-level head element set, or `[]` when no default image is set.
|
|
2526
|
+
* @example composeSiteHead({ site, defaults, url: "https://blog.dev/en/", ogLocale: "en_US" })
|
|
2527
|
+
*/
|
|
2528
|
+
function composeSiteHead(input) {
|
|
2529
|
+
const { site, defaults, url, ogLocale } = input;
|
|
2530
|
+
const image = defaults.defaultOgImage;
|
|
2531
|
+
if (image === void 0) return [];
|
|
2532
|
+
const absoluteImage = resolveImage(image, site);
|
|
2533
|
+
const name = site.name();
|
|
2534
|
+
const description = site.description();
|
|
2535
|
+
const elements = [
|
|
2536
|
+
meta("description", description),
|
|
2537
|
+
og("og:type", "website"),
|
|
2538
|
+
og("og:site_name", name),
|
|
2539
|
+
og("og:title", name),
|
|
2540
|
+
og("og:description", description),
|
|
2541
|
+
og("og:url", url),
|
|
2542
|
+
og("og:image", absoluteImage),
|
|
2543
|
+
twitter("twitter:card", defaults.twitterCard),
|
|
2544
|
+
twitter("twitter:title", name),
|
|
2545
|
+
twitter("twitter:description", description),
|
|
2546
|
+
twitter("twitter:image", absoluteImage)
|
|
2547
|
+
];
|
|
2548
|
+
if (defaults.twitterHandle) elements.push(twitter("twitter:site", defaults.twitterHandle));
|
|
2549
|
+
if (ogLocale) elements.push(og("og:locale", ogLocale));
|
|
2550
|
+
return elements;
|
|
2551
|
+
}
|
|
2552
|
+
/**
|
|
2517
2553
|
* HTML-escape a value for safe insertion into an attribute or text node. `&` is
|
|
2518
2554
|
* escaped first so already-escaped entities are not double-escaped.
|
|
2519
2555
|
*
|
|
@@ -2602,28 +2638,53 @@ function readDefaults(state) {
|
|
|
2602
2638
|
* ```
|
|
2603
2639
|
*/
|
|
2604
2640
|
function createApi$1(ctx) {
|
|
2605
|
-
return {
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
render(route, data) {
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2641
|
+
return {
|
|
2642
|
+
/**
|
|
2643
|
+
* Compose the final `<head>` inner HTML for a route (pulled by `build`).
|
|
2644
|
+
*
|
|
2645
|
+
* @param route - The resolved route descriptor (incl. its `.head()` HeadConfig).
|
|
2646
|
+
* @param data - The page data object passed to the route's loader/render.
|
|
2647
|
+
* @returns The serialized inner HTML of `<head>`.
|
|
2648
|
+
* @example
|
|
2649
|
+
* ```ts
|
|
2650
|
+
* api.render(route, { title: "Post" });
|
|
2651
|
+
* ```
|
|
2652
|
+
*/
|
|
2653
|
+
render(route, data) {
|
|
2654
|
+
return serializeHead(composeHead({
|
|
2655
|
+
route,
|
|
2656
|
+
data,
|
|
2657
|
+
defaults: readDefaults(ctx.state),
|
|
2658
|
+
site: ctx.require(sitePlugin),
|
|
2659
|
+
i18n: ctx.require(i18nPlugin),
|
|
2660
|
+
router: ctx.require(routerPlugin)
|
|
2661
|
+
}));
|
|
2662
|
+
},
|
|
2663
|
+
/**
|
|
2664
|
+
* Compose the site-level OG/Twitter block for a bare-path redirect/landing page. Resolves
|
|
2665
|
+
* `site`/`i18n` via `ctx.require`, absolutizes `url` against the site base, and emits an
|
|
2666
|
+
* `og:locale` for `locale` when supplied. Returns `""` when no `defaultOgImage` is configured.
|
|
2667
|
+
*
|
|
2668
|
+
* @param input - The landing URL/path plus an optional locale (for `og:locale`).
|
|
2669
|
+
* @param input.url - The page's URL or path (absolutized via `site.canonical`) → `og:url`.
|
|
2670
|
+
* @param input.locale - Optional locale whose `og:locale` is emitted.
|
|
2671
|
+
* @returns The serialized inner HTML of the site-level head block, or `""` when disabled.
|
|
2672
|
+
* @example
|
|
2673
|
+
* ```ts
|
|
2674
|
+
* api.siteHead({ url: "/en/", locale: "en" });
|
|
2675
|
+
* ```
|
|
2676
|
+
*/
|
|
2677
|
+
siteHead(input) {
|
|
2678
|
+
const site = ctx.require(sitePlugin);
|
|
2679
|
+
const ogLocale = input.locale === void 0 ? void 0 : ctx.require(i18nPlugin).ogLocale(input.locale);
|
|
2680
|
+
return serializeHead(composeSiteHead({
|
|
2681
|
+
site,
|
|
2682
|
+
defaults: readDefaults(ctx.state),
|
|
2683
|
+
url: site.canonical(input.url),
|
|
2684
|
+
...ogLocale === void 0 ? {} : { ogLocale }
|
|
2685
|
+
}));
|
|
2686
|
+
}
|
|
2687
|
+
};
|
|
2627
2688
|
}
|
|
2628
2689
|
//#endregion
|
|
2629
2690
|
//#region src/plugins/head/config.ts
|
package/dist/index.cjs
CHANGED
|
@@ -3072,6 +3072,42 @@ function composeHead(input) {
|
|
|
3072
3072
|
}), ...head.elements ?? []]);
|
|
3073
3073
|
}
|
|
3074
3074
|
/**
|
|
3075
|
+
* Compose the SITE-LEVEL Open Graph / Twitter block for a bare-path redirect or landing
|
|
3076
|
+
* page that has no per-route head of its own. Returns `[]` UNLESS a `defaultOgImage` is
|
|
3077
|
+
* configured — so apps that opt out keep a bare redirect (no behavior change). The site
|
|
3078
|
+
* name + description become the card's title/description (`og:type=website`); `url` is the
|
|
3079
|
+
* canonical the page points at. A bare article/tag alias gets this site card as a fallback;
|
|
3080
|
+
* crawlers that honor the page's `rel=canonical` still resolve the per-route card.
|
|
3081
|
+
*
|
|
3082
|
+
* @param input - The site slice, head defaults, landing URL, and optional `og:locale`.
|
|
3083
|
+
* @returns The ordered site-level head element set, or `[]` when no default image is set.
|
|
3084
|
+
* @example composeSiteHead({ site, defaults, url: "https://blog.dev/en/", ogLocale: "en_US" })
|
|
3085
|
+
*/
|
|
3086
|
+
function composeSiteHead(input) {
|
|
3087
|
+
const { site, defaults, url, ogLocale } = input;
|
|
3088
|
+
const image = defaults.defaultOgImage;
|
|
3089
|
+
if (image === void 0) return [];
|
|
3090
|
+
const absoluteImage = resolveImage(image, site);
|
|
3091
|
+
const name = site.name();
|
|
3092
|
+
const description = site.description();
|
|
3093
|
+
const elements = [
|
|
3094
|
+
meta("description", description),
|
|
3095
|
+
og("og:type", "website"),
|
|
3096
|
+
og("og:site_name", name),
|
|
3097
|
+
og("og:title", name),
|
|
3098
|
+
og("og:description", description),
|
|
3099
|
+
og("og:url", url),
|
|
3100
|
+
og("og:image", absoluteImage),
|
|
3101
|
+
twitter("twitter:card", defaults.twitterCard),
|
|
3102
|
+
twitter("twitter:title", name),
|
|
3103
|
+
twitter("twitter:description", description),
|
|
3104
|
+
twitter("twitter:image", absoluteImage)
|
|
3105
|
+
];
|
|
3106
|
+
if (defaults.twitterHandle) elements.push(twitter("twitter:site", defaults.twitterHandle));
|
|
3107
|
+
if (ogLocale) elements.push(og("og:locale", ogLocale));
|
|
3108
|
+
return elements;
|
|
3109
|
+
}
|
|
3110
|
+
/**
|
|
3075
3111
|
* HTML-escape a value for safe insertion into an attribute or text node. `&` is
|
|
3076
3112
|
* escaped first so already-escaped entities are not double-escaped.
|
|
3077
3113
|
*
|
|
@@ -3160,28 +3196,53 @@ function readDefaults(state) {
|
|
|
3160
3196
|
* ```
|
|
3161
3197
|
*/
|
|
3162
3198
|
function createApi$4(ctx) {
|
|
3163
|
-
return {
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
render(route, data) {
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3199
|
+
return {
|
|
3200
|
+
/**
|
|
3201
|
+
* Compose the final `<head>` inner HTML for a route (pulled by `build`).
|
|
3202
|
+
*
|
|
3203
|
+
* @param route - The resolved route descriptor (incl. its `.head()` HeadConfig).
|
|
3204
|
+
* @param data - The page data object passed to the route's loader/render.
|
|
3205
|
+
* @returns The serialized inner HTML of `<head>`.
|
|
3206
|
+
* @example
|
|
3207
|
+
* ```ts
|
|
3208
|
+
* api.render(route, { title: "Post" });
|
|
3209
|
+
* ```
|
|
3210
|
+
*/
|
|
3211
|
+
render(route, data) {
|
|
3212
|
+
return serializeHead(composeHead({
|
|
3213
|
+
route,
|
|
3214
|
+
data,
|
|
3215
|
+
defaults: readDefaults(ctx.state),
|
|
3216
|
+
site: ctx.require(sitePlugin),
|
|
3217
|
+
i18n: ctx.require(i18nPlugin),
|
|
3218
|
+
router: ctx.require(routerPlugin)
|
|
3219
|
+
}));
|
|
3220
|
+
},
|
|
3221
|
+
/**
|
|
3222
|
+
* Compose the site-level OG/Twitter block for a bare-path redirect/landing page. Resolves
|
|
3223
|
+
* `site`/`i18n` via `ctx.require`, absolutizes `url` against the site base, and emits an
|
|
3224
|
+
* `og:locale` for `locale` when supplied. Returns `""` when no `defaultOgImage` is configured.
|
|
3225
|
+
*
|
|
3226
|
+
* @param input - The landing URL/path plus an optional locale (for `og:locale`).
|
|
3227
|
+
* @param input.url - The page's URL or path (absolutized via `site.canonical`) → `og:url`.
|
|
3228
|
+
* @param input.locale - Optional locale whose `og:locale` is emitted.
|
|
3229
|
+
* @returns The serialized inner HTML of the site-level head block, or `""` when disabled.
|
|
3230
|
+
* @example
|
|
3231
|
+
* ```ts
|
|
3232
|
+
* api.siteHead({ url: "/en/", locale: "en" });
|
|
3233
|
+
* ```
|
|
3234
|
+
*/
|
|
3235
|
+
siteHead(input) {
|
|
3236
|
+
const site = ctx.require(sitePlugin);
|
|
3237
|
+
const ogLocale = input.locale === void 0 ? void 0 : ctx.require(i18nPlugin).ogLocale(input.locale);
|
|
3238
|
+
return serializeHead(composeSiteHead({
|
|
3239
|
+
site,
|
|
3240
|
+
defaults: readDefaults(ctx.state),
|
|
3241
|
+
url: site.canonical(input.url),
|
|
3242
|
+
...ogLocale === void 0 ? {} : { ogLocale }
|
|
3243
|
+
}));
|
|
3244
|
+
}
|
|
3245
|
+
};
|
|
3185
3246
|
}
|
|
3186
3247
|
//#endregion
|
|
3187
3248
|
//#region src/plugins/head/config.ts
|
|
@@ -3754,19 +3815,26 @@ async function processImages(ctx, options = {}) {
|
|
|
3754
3815
|
* bare path that points at the default-locale-prefixed URL. Deliberately does NOT
|
|
3755
3816
|
* emit a Cloudflare `_redirects` catch-all (an SSG infinite-loop trap). Gated by
|
|
3756
3817
|
* `config.localeRedirects` (false/unset disables).
|
|
3818
|
+
*
|
|
3819
|
+
* When `head.defaultOgImage` is configured, each redirect page ALSO carries the
|
|
3820
|
+
* site-level Open Graph / Twitter block (`head.siteHead`) so a social crawler that
|
|
3821
|
+
* fetches the apex domain (or any locale-less alias) — and does not follow the
|
|
3822
|
+
* meta-refresh — still gets a branded preview card. No image configured ⇒ bare redirect.
|
|
3757
3823
|
*/
|
|
3758
3824
|
/**
|
|
3759
|
-
* Render a redirect HTML page: a `0;url` refresh meta + a canonical link to `target
|
|
3825
|
+
* Render a redirect HTML page: a `0;url` refresh meta + a canonical link to `target`,
|
|
3826
|
+
* with an optional site-level OG/Twitter block injected at the end of `<head>`.
|
|
3760
3827
|
*
|
|
3761
3828
|
* @param target - The default-locale-prefixed URL to redirect to.
|
|
3829
|
+
* @param headExtra - Extra `<head>` inner HTML (the site-level OG block), or `""` for none.
|
|
3762
3830
|
* @returns The complete redirect HTML document string.
|
|
3763
3831
|
* @example
|
|
3764
3832
|
* ```ts
|
|
3765
|
-
* redirectHtml("/en/about/");
|
|
3833
|
+
* redirectHtml("/en/about/", '<meta property="og:image" content="…">');
|
|
3766
3834
|
* ```
|
|
3767
3835
|
*/
|
|
3768
|
-
function redirectHtml(target) {
|
|
3769
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=${target}"><link rel="canonical" href="${target}"
|
|
3836
|
+
function redirectHtml(target, headExtra = "") {
|
|
3837
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=${target}"><link rel="canonical" href="${target}">${headExtra}</head><body><a href="${target}">Redirecting…</a></body></html>`;
|
|
3770
3838
|
}
|
|
3771
3839
|
/**
|
|
3772
3840
|
* Correlate manifest definitions to compiled `TypedRoute` entries by pattern (the
|
|
@@ -3858,16 +3926,17 @@ async function expandRedirects(definition, entry, defaultLocale, ctx) {
|
|
|
3858
3926
|
* @param job.file - The redirect page's output path, relative to `outDir`.
|
|
3859
3927
|
* @param job.target - The absolute default-locale URL the page redirects to.
|
|
3860
3928
|
* @param outDir - The build output directory the file is resolved against.
|
|
3929
|
+
* @param headExtra - The site-level OG block to inject into `<head>`, or `""` for none.
|
|
3861
3930
|
* @returns Resolves once the redirect HTML page is written.
|
|
3862
3931
|
* @example
|
|
3863
3932
|
* ```ts
|
|
3864
|
-
* await writeRedirectFile({ file: "about/index.html", target: "/en/about/" }, "dist");
|
|
3933
|
+
* await writeRedirectFile({ file: "about/index.html", target: "/en/about/" }, "dist", "");
|
|
3865
3934
|
* ```
|
|
3866
3935
|
*/
|
|
3867
|
-
async function writeRedirectFile(job, outDir) {
|
|
3936
|
+
async function writeRedirectFile(job, outDir, headExtra = "") {
|
|
3868
3937
|
const filePath = node_path$1.default.join(outDir, job.file);
|
|
3869
3938
|
await (0, node_fs_promises.mkdir)(node_path$1.default.dirname(filePath), { recursive: true });
|
|
3870
|
-
await (0, node_fs_promises.writeFile)(filePath, redirectHtml(job.target), "utf8");
|
|
3939
|
+
await (0, node_fs_promises.writeFile)(filePath, redirectHtml(job.target, headExtra), "utf8");
|
|
3871
3940
|
}
|
|
3872
3941
|
/**
|
|
3873
3942
|
* Emits one bare-path redirect HTML page per locale-prefixed route path, each a
|
|
@@ -3890,7 +3959,14 @@ async function generateLocaleRedirects(ctx) {
|
|
|
3890
3959
|
const router = ctx.require(routerPlugin);
|
|
3891
3960
|
const defaultLocale = ctx.require(i18nPlugin).defaultLocale();
|
|
3892
3961
|
const jobs = (await Promise.all(pairRoutes(router).map(([definition, entry]) => expandRedirects(definition, entry, defaultLocale, ctx)))).flat();
|
|
3893
|
-
|
|
3962
|
+
const head = ctx.has("head") ? ctx.require(headPlugin) : void 0;
|
|
3963
|
+
await Promise.all(jobs.map((job) => {
|
|
3964
|
+
const headExtra = head ? head.siteHead({
|
|
3965
|
+
url: job.target,
|
|
3966
|
+
locale: defaultLocale
|
|
3967
|
+
}) : "";
|
|
3968
|
+
return writeRedirectFile(job, ctx.config.outDir, headExtra);
|
|
3969
|
+
}));
|
|
3894
3970
|
ctx.log.debug("build:locale-redirects", { written: jobs.length });
|
|
3895
3971
|
return { written: jobs.length };
|
|
3896
3972
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -1138,6 +1138,25 @@ type Api$4 = {
|
|
|
1138
1138
|
* ```
|
|
1139
1139
|
*/
|
|
1140
1140
|
render(route: ResolvedRoute, data: unknown): string;
|
|
1141
|
+
/**
|
|
1142
|
+
* Compose the SITE-LEVEL `<head>` Open Graph / Twitter block for a bare-path redirect or
|
|
1143
|
+
* landing page that has no route identity (e.g. the apex-domain `/` redirect a
|
|
1144
|
+
* `localeRedirects` build emits). Returns `""` UNLESS `defaultOgImage` is configured, so
|
|
1145
|
+
* apps that opt out keep a bare redirect. Pulled synchronously by `build`.
|
|
1146
|
+
*
|
|
1147
|
+
* @param input - The landing URL (resolved to an absolute canonical) plus an optional locale.
|
|
1148
|
+
* @param input.url - The page's URL or path (absolutized via `site.canonical`) → `og:url`.
|
|
1149
|
+
* @param input.locale - Optional locale whose `og:locale` is emitted (e.g. the default locale).
|
|
1150
|
+
* @returns The serialized inner HTML of the site-level head block, or `""` when disabled.
|
|
1151
|
+
* @example
|
|
1152
|
+
* ```ts
|
|
1153
|
+
* api.siteHead({ url: "/en/", locale: "en" });
|
|
1154
|
+
* ```
|
|
1155
|
+
*/
|
|
1156
|
+
siteHead(input: {
|
|
1157
|
+
url: string;
|
|
1158
|
+
locale?: string;
|
|
1159
|
+
}): string;
|
|
1141
1160
|
};
|
|
1142
1161
|
declare namespace types_d_exports$9 {
|
|
1143
1162
|
export { COMPONENT_HOOK_NAMES, ComponentContext, ComponentDef, ComponentHooks, ComponentInstance, ExtractApi$1 as ExtractApi, PageData, ResolvedSpaConfig, SpaApi, SpaConfig, SpaContext, SpaDataReader, SpaEmitFunction, SpaEvents, SpaKernel, SpaKernelDeps, SpaRequire, SpaState };
|
package/dist/index.d.mts
CHANGED
|
@@ -1138,6 +1138,25 @@ type Api$4 = {
|
|
|
1138
1138
|
* ```
|
|
1139
1139
|
*/
|
|
1140
1140
|
render(route: ResolvedRoute, data: unknown): string;
|
|
1141
|
+
/**
|
|
1142
|
+
* Compose the SITE-LEVEL `<head>` Open Graph / Twitter block for a bare-path redirect or
|
|
1143
|
+
* landing page that has no route identity (e.g. the apex-domain `/` redirect a
|
|
1144
|
+
* `localeRedirects` build emits). Returns `""` UNLESS `defaultOgImage` is configured, so
|
|
1145
|
+
* apps that opt out keep a bare redirect. Pulled synchronously by `build`.
|
|
1146
|
+
*
|
|
1147
|
+
* @param input - The landing URL (resolved to an absolute canonical) plus an optional locale.
|
|
1148
|
+
* @param input.url - The page's URL or path (absolutized via `site.canonical`) → `og:url`.
|
|
1149
|
+
* @param input.locale - Optional locale whose `og:locale` is emitted (e.g. the default locale).
|
|
1150
|
+
* @returns The serialized inner HTML of the site-level head block, or `""` when disabled.
|
|
1151
|
+
* @example
|
|
1152
|
+
* ```ts
|
|
1153
|
+
* api.siteHead({ url: "/en/", locale: "en" });
|
|
1154
|
+
* ```
|
|
1155
|
+
*/
|
|
1156
|
+
siteHead(input: {
|
|
1157
|
+
url: string;
|
|
1158
|
+
locale?: string;
|
|
1159
|
+
}): string;
|
|
1141
1160
|
};
|
|
1142
1161
|
declare namespace types_d_exports$9 {
|
|
1143
1162
|
export { COMPONENT_HOOK_NAMES, ComponentContext, ComponentDef, ComponentHooks, ComponentInstance, ExtractApi$1 as ExtractApi, PageData, ResolvedSpaConfig, SpaApi, SpaConfig, SpaContext, SpaDataReader, SpaEmitFunction, SpaEvents, SpaKernel, SpaKernelDeps, SpaRequire, SpaState };
|
package/dist/index.mjs
CHANGED
|
@@ -3059,6 +3059,42 @@ function composeHead(input) {
|
|
|
3059
3059
|
}), ...head.elements ?? []]);
|
|
3060
3060
|
}
|
|
3061
3061
|
/**
|
|
3062
|
+
* Compose the SITE-LEVEL Open Graph / Twitter block for a bare-path redirect or landing
|
|
3063
|
+
* page that has no per-route head of its own. Returns `[]` UNLESS a `defaultOgImage` is
|
|
3064
|
+
* configured — so apps that opt out keep a bare redirect (no behavior change). The site
|
|
3065
|
+
* name + description become the card's title/description (`og:type=website`); `url` is the
|
|
3066
|
+
* canonical the page points at. A bare article/tag alias gets this site card as a fallback;
|
|
3067
|
+
* crawlers that honor the page's `rel=canonical` still resolve the per-route card.
|
|
3068
|
+
*
|
|
3069
|
+
* @param input - The site slice, head defaults, landing URL, and optional `og:locale`.
|
|
3070
|
+
* @returns The ordered site-level head element set, or `[]` when no default image is set.
|
|
3071
|
+
* @example composeSiteHead({ site, defaults, url: "https://blog.dev/en/", ogLocale: "en_US" })
|
|
3072
|
+
*/
|
|
3073
|
+
function composeSiteHead(input) {
|
|
3074
|
+
const { site, defaults, url, ogLocale } = input;
|
|
3075
|
+
const image = defaults.defaultOgImage;
|
|
3076
|
+
if (image === void 0) return [];
|
|
3077
|
+
const absoluteImage = resolveImage(image, site);
|
|
3078
|
+
const name = site.name();
|
|
3079
|
+
const description = site.description();
|
|
3080
|
+
const elements = [
|
|
3081
|
+
meta("description", description),
|
|
3082
|
+
og("og:type", "website"),
|
|
3083
|
+
og("og:site_name", name),
|
|
3084
|
+
og("og:title", name),
|
|
3085
|
+
og("og:description", description),
|
|
3086
|
+
og("og:url", url),
|
|
3087
|
+
og("og:image", absoluteImage),
|
|
3088
|
+
twitter("twitter:card", defaults.twitterCard),
|
|
3089
|
+
twitter("twitter:title", name),
|
|
3090
|
+
twitter("twitter:description", description),
|
|
3091
|
+
twitter("twitter:image", absoluteImage)
|
|
3092
|
+
];
|
|
3093
|
+
if (defaults.twitterHandle) elements.push(twitter("twitter:site", defaults.twitterHandle));
|
|
3094
|
+
if (ogLocale) elements.push(og("og:locale", ogLocale));
|
|
3095
|
+
return elements;
|
|
3096
|
+
}
|
|
3097
|
+
/**
|
|
3062
3098
|
* HTML-escape a value for safe insertion into an attribute or text node. `&` is
|
|
3063
3099
|
* escaped first so already-escaped entities are not double-escaped.
|
|
3064
3100
|
*
|
|
@@ -3147,28 +3183,53 @@ function readDefaults(state) {
|
|
|
3147
3183
|
* ```
|
|
3148
3184
|
*/
|
|
3149
3185
|
function createApi$4(ctx) {
|
|
3150
|
-
return {
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
render(route, data) {
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3186
|
+
return {
|
|
3187
|
+
/**
|
|
3188
|
+
* Compose the final `<head>` inner HTML for a route (pulled by `build`).
|
|
3189
|
+
*
|
|
3190
|
+
* @param route - The resolved route descriptor (incl. its `.head()` HeadConfig).
|
|
3191
|
+
* @param data - The page data object passed to the route's loader/render.
|
|
3192
|
+
* @returns The serialized inner HTML of `<head>`.
|
|
3193
|
+
* @example
|
|
3194
|
+
* ```ts
|
|
3195
|
+
* api.render(route, { title: "Post" });
|
|
3196
|
+
* ```
|
|
3197
|
+
*/
|
|
3198
|
+
render(route, data) {
|
|
3199
|
+
return serializeHead(composeHead({
|
|
3200
|
+
route,
|
|
3201
|
+
data,
|
|
3202
|
+
defaults: readDefaults(ctx.state),
|
|
3203
|
+
site: ctx.require(sitePlugin),
|
|
3204
|
+
i18n: ctx.require(i18nPlugin),
|
|
3205
|
+
router: ctx.require(routerPlugin)
|
|
3206
|
+
}));
|
|
3207
|
+
},
|
|
3208
|
+
/**
|
|
3209
|
+
* Compose the site-level OG/Twitter block for a bare-path redirect/landing page. Resolves
|
|
3210
|
+
* `site`/`i18n` via `ctx.require`, absolutizes `url` against the site base, and emits an
|
|
3211
|
+
* `og:locale` for `locale` when supplied. Returns `""` when no `defaultOgImage` is configured.
|
|
3212
|
+
*
|
|
3213
|
+
* @param input - The landing URL/path plus an optional locale (for `og:locale`).
|
|
3214
|
+
* @param input.url - The page's URL or path (absolutized via `site.canonical`) → `og:url`.
|
|
3215
|
+
* @param input.locale - Optional locale whose `og:locale` is emitted.
|
|
3216
|
+
* @returns The serialized inner HTML of the site-level head block, or `""` when disabled.
|
|
3217
|
+
* @example
|
|
3218
|
+
* ```ts
|
|
3219
|
+
* api.siteHead({ url: "/en/", locale: "en" });
|
|
3220
|
+
* ```
|
|
3221
|
+
*/
|
|
3222
|
+
siteHead(input) {
|
|
3223
|
+
const site = ctx.require(sitePlugin);
|
|
3224
|
+
const ogLocale = input.locale === void 0 ? void 0 : ctx.require(i18nPlugin).ogLocale(input.locale);
|
|
3225
|
+
return serializeHead(composeSiteHead({
|
|
3226
|
+
site,
|
|
3227
|
+
defaults: readDefaults(ctx.state),
|
|
3228
|
+
url: site.canonical(input.url),
|
|
3229
|
+
...ogLocale === void 0 ? {} : { ogLocale }
|
|
3230
|
+
}));
|
|
3231
|
+
}
|
|
3232
|
+
};
|
|
3172
3233
|
}
|
|
3173
3234
|
//#endregion
|
|
3174
3235
|
//#region src/plugins/head/config.ts
|
|
@@ -3741,19 +3802,26 @@ async function processImages(ctx, options = {}) {
|
|
|
3741
3802
|
* bare path that points at the default-locale-prefixed URL. Deliberately does NOT
|
|
3742
3803
|
* emit a Cloudflare `_redirects` catch-all (an SSG infinite-loop trap). Gated by
|
|
3743
3804
|
* `config.localeRedirects` (false/unset disables).
|
|
3805
|
+
*
|
|
3806
|
+
* When `head.defaultOgImage` is configured, each redirect page ALSO carries the
|
|
3807
|
+
* site-level Open Graph / Twitter block (`head.siteHead`) so a social crawler that
|
|
3808
|
+
* fetches the apex domain (or any locale-less alias) — and does not follow the
|
|
3809
|
+
* meta-refresh — still gets a branded preview card. No image configured ⇒ bare redirect.
|
|
3744
3810
|
*/
|
|
3745
3811
|
/**
|
|
3746
|
-
* Render a redirect HTML page: a `0;url` refresh meta + a canonical link to `target
|
|
3812
|
+
* Render a redirect HTML page: a `0;url` refresh meta + a canonical link to `target`,
|
|
3813
|
+
* with an optional site-level OG/Twitter block injected at the end of `<head>`.
|
|
3747
3814
|
*
|
|
3748
3815
|
* @param target - The default-locale-prefixed URL to redirect to.
|
|
3816
|
+
* @param headExtra - Extra `<head>` inner HTML (the site-level OG block), or `""` for none.
|
|
3749
3817
|
* @returns The complete redirect HTML document string.
|
|
3750
3818
|
* @example
|
|
3751
3819
|
* ```ts
|
|
3752
|
-
* redirectHtml("/en/about/");
|
|
3820
|
+
* redirectHtml("/en/about/", '<meta property="og:image" content="…">');
|
|
3753
3821
|
* ```
|
|
3754
3822
|
*/
|
|
3755
|
-
function redirectHtml(target) {
|
|
3756
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=${target}"><link rel="canonical" href="${target}"
|
|
3823
|
+
function redirectHtml(target, headExtra = "") {
|
|
3824
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=${target}"><link rel="canonical" href="${target}">${headExtra}</head><body><a href="${target}">Redirecting…</a></body></html>`;
|
|
3757
3825
|
}
|
|
3758
3826
|
/**
|
|
3759
3827
|
* Correlate manifest definitions to compiled `TypedRoute` entries by pattern (the
|
|
@@ -3845,16 +3913,17 @@ async function expandRedirects(definition, entry, defaultLocale, ctx) {
|
|
|
3845
3913
|
* @param job.file - The redirect page's output path, relative to `outDir`.
|
|
3846
3914
|
* @param job.target - The absolute default-locale URL the page redirects to.
|
|
3847
3915
|
* @param outDir - The build output directory the file is resolved against.
|
|
3916
|
+
* @param headExtra - The site-level OG block to inject into `<head>`, or `""` for none.
|
|
3848
3917
|
* @returns Resolves once the redirect HTML page is written.
|
|
3849
3918
|
* @example
|
|
3850
3919
|
* ```ts
|
|
3851
|
-
* await writeRedirectFile({ file: "about/index.html", target: "/en/about/" }, "dist");
|
|
3920
|
+
* await writeRedirectFile({ file: "about/index.html", target: "/en/about/" }, "dist", "");
|
|
3852
3921
|
* ```
|
|
3853
3922
|
*/
|
|
3854
|
-
async function writeRedirectFile(job, outDir) {
|
|
3923
|
+
async function writeRedirectFile(job, outDir, headExtra = "") {
|
|
3855
3924
|
const filePath = path.join(outDir, job.file);
|
|
3856
3925
|
await mkdir(path.dirname(filePath), { recursive: true });
|
|
3857
|
-
await writeFile(filePath, redirectHtml(job.target), "utf8");
|
|
3926
|
+
await writeFile(filePath, redirectHtml(job.target, headExtra), "utf8");
|
|
3858
3927
|
}
|
|
3859
3928
|
/**
|
|
3860
3929
|
* Emits one bare-path redirect HTML page per locale-prefixed route path, each a
|
|
@@ -3877,7 +3946,14 @@ async function generateLocaleRedirects(ctx) {
|
|
|
3877
3946
|
const router = ctx.require(routerPlugin);
|
|
3878
3947
|
const defaultLocale = ctx.require(i18nPlugin).defaultLocale();
|
|
3879
3948
|
const jobs = (await Promise.all(pairRoutes(router).map(([definition, entry]) => expandRedirects(definition, entry, defaultLocale, ctx)))).flat();
|
|
3880
|
-
|
|
3949
|
+
const head = ctx.has("head") ? ctx.require(headPlugin) : void 0;
|
|
3950
|
+
await Promise.all(jobs.map((job) => {
|
|
3951
|
+
const headExtra = head ? head.siteHead({
|
|
3952
|
+
url: job.target,
|
|
3953
|
+
locale: defaultLocale
|
|
3954
|
+
}) : "";
|
|
3955
|
+
return writeRedirectFile(job, ctx.config.outDir, headExtra);
|
|
3956
|
+
}));
|
|
3881
3957
|
ctx.log.debug("build:locale-redirects", { written: jobs.length });
|
|
3882
3958
|
return { written: jobs.length };
|
|
3883
3959
|
}
|
package/package.json
CHANGED