@rsc-kit/core 0.30.13 → 0.30.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/appAssets.d.ts +25 -4
- package/dist/appAssets.js +86 -30
- package/dist/appAssets.js.map +1 -1
- package/dist/metadata.d.ts +4 -0
- package/dist/metadata.js.map +1 -1
- package/dist/shareImages.d.ts +30 -0
- package/dist/shareImages.js +50 -0
- package/dist/shareImages.js.map +1 -0
- package/dist/vite.d.ts +24 -0
- package/dist/vite.js +127 -21
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
package/dist/appAssets.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ShareImages, ShareKind } from "./shareImages.js";
|
|
1
2
|
/** One found file, and where it will be served from. */
|
|
2
3
|
export interface AppAsset {
|
|
3
4
|
/** The file's name in the app directory. */
|
|
@@ -15,8 +16,11 @@ export interface AppAssets {
|
|
|
15
16
|
appleSplash: (AppAsset & {
|
|
16
17
|
media: string | null;
|
|
17
18
|
})[];
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Share images by the folder they sit in, "" being app/ itself. The route
|
|
21
|
+
* that shows one is found by walking up from its own folder.
|
|
22
|
+
*/
|
|
23
|
+
shareImages: Record<string, Partial<Record<ShareKind, AppAsset>>>;
|
|
20
24
|
}
|
|
21
25
|
/**
|
|
22
26
|
* Where these are served from.
|
|
@@ -27,13 +31,30 @@ export interface AppAssets {
|
|
|
27
31
|
* for at `/favicon.ico` whatever any markup says.
|
|
28
32
|
*/
|
|
29
33
|
export declare const ASSET_BASE = "/_app";
|
|
34
|
+
/**
|
|
35
|
+
* A file's path as a url path: each segment encoded, the slashes kept.
|
|
36
|
+
*
|
|
37
|
+
* A folder under app/ may be `[team]`, and a bracket is not legal in a url's
|
|
38
|
+
* path - a strict scraper refuses the whole address, and a card with no image
|
|
39
|
+
* is all it says. Everything a plain file name is made of passes through
|
|
40
|
+
* untouched, so `icon-192.png` is `icon-192.png`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function assetHref(file: string): string;
|
|
43
|
+
/** The file path a url path names, which is what the build writes and the dev server reads. */
|
|
44
|
+
export declare function assetFile(href: string): string;
|
|
30
45
|
/** What the app declared by putting a file where it could be found. */
|
|
31
46
|
export declare function appAssets(appDir: string): AppAssets;
|
|
47
|
+
/** The urls only, which is all a page being rendered needs of them. */
|
|
48
|
+
export declare function shareImageUrls(assets: AppAssets): ShareImages;
|
|
32
49
|
export declare function typeOf(file: string): string;
|
|
33
|
-
/** The head tags these produce, as plain data the generated entry turns into elements. */
|
|
34
50
|
/** Every asset found, favicon first: what the build copies and the dev server answers. */
|
|
35
51
|
export declare function allAppAssets(assets: AppAssets): AppAsset[];
|
|
36
|
-
|
|
52
|
+
/**
|
|
53
|
+
* The head tags these produce, as plain data the generated entry turns into
|
|
54
|
+
* elements - the same on every page. The share images are not here: which one
|
|
55
|
+
* a page shows depends on where the page is, and that is decided as it renders.
|
|
56
|
+
*/
|
|
57
|
+
export declare function headTags(assets: AppAssets): {
|
|
37
58
|
tag: "link" | "meta";
|
|
38
59
|
props: Record<string, string>;
|
|
39
60
|
}[];
|
package/dist/appAssets.js
CHANGED
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
// opengraph-image.png <meta property="og:image">
|
|
14
14
|
// twitter-image.png <meta name="twitter:image">
|
|
15
15
|
//
|
|
16
|
+
// The two share images may also sit in any folder beneath app/, and are then
|
|
17
|
+
// the card for the routes under that folder - the nearest one wins. See
|
|
18
|
+
// shareImages.ts for the rule and appAssets() for the scan.
|
|
19
|
+
//
|
|
16
20
|
// The same bargain as every other file in this directory: put it where it
|
|
17
21
|
// belongs and it works, with nothing to register. Next established these names
|
|
18
22
|
// and there is nothing to gain by inventing different ones — an app moving
|
|
@@ -22,6 +26,7 @@
|
|
|
22
26
|
// this verbatim"; these are read as well as served — an icon's size decides
|
|
23
27
|
// what goes in the manifest, and its presence decides what goes in the head.
|
|
24
28
|
import { existsSync, readdirSync } from "node:fs";
|
|
29
|
+
import { join } from "node:path";
|
|
25
30
|
import { isAppleSplash, splashMedia } from "./appleSplash.js";
|
|
26
31
|
const IMAGE = /\.(png|svg|jpg|jpeg|webp|gif|avif)$/i;
|
|
27
32
|
/**
|
|
@@ -33,11 +38,32 @@ const IMAGE = /\.(png|svg|jpg|jpeg|webp|gif|avif)$/i;
|
|
|
33
38
|
* for at `/favicon.ico` whatever any markup says.
|
|
34
39
|
*/
|
|
35
40
|
export const ASSET_BASE = "/_app";
|
|
41
|
+
/**
|
|
42
|
+
* A file's path as a url path: each segment encoded, the slashes kept.
|
|
43
|
+
*
|
|
44
|
+
* A folder under app/ may be `[team]`, and a bracket is not legal in a url's
|
|
45
|
+
* path - a strict scraper refuses the whole address, and a card with no image
|
|
46
|
+
* is all it says. Everything a plain file name is made of passes through
|
|
47
|
+
* untouched, so `icon-192.png` is `icon-192.png`.
|
|
48
|
+
*/
|
|
49
|
+
export function assetHref(file) {
|
|
50
|
+
return `${ASSET_BASE}/${file.split("/").map(encodeURIComponent).join("/")}`;
|
|
51
|
+
}
|
|
52
|
+
/** The file path a url path names, which is what the build writes and the dev server reads. */
|
|
53
|
+
export function assetFile(href) {
|
|
54
|
+
try {
|
|
55
|
+
return decodeURIComponent(href);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// A stray % is not an encoding. Left as it came, it matches nothing.
|
|
59
|
+
return href;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
36
62
|
function assetsIn(dir, match) {
|
|
37
63
|
return readdirSync(dir)
|
|
38
64
|
.filter(match)
|
|
39
65
|
.sort()
|
|
40
|
-
.map((file) => ({ file, href:
|
|
66
|
+
.map((file) => ({ file, href: assetHref(file) }));
|
|
41
67
|
}
|
|
42
68
|
/** What the app declared by putting a file where it could be found. */
|
|
43
69
|
export function appAssets(appDir) {
|
|
@@ -48,8 +74,7 @@ export function appAssets(appDir) {
|
|
|
48
74
|
appleIcon: null,
|
|
49
75
|
appleSplash: [],
|
|
50
76
|
screenshots: [],
|
|
51
|
-
|
|
52
|
-
twitter: null,
|
|
77
|
+
shareImages: {},
|
|
53
78
|
};
|
|
54
79
|
}
|
|
55
80
|
const names = readdirSync(appDir);
|
|
@@ -68,10 +93,59 @@ export function appAssets(appDir) {
|
|
|
68
93
|
...asset,
|
|
69
94
|
media: splashMedia(asset.file),
|
|
70
95
|
})),
|
|
71
|
-
|
|
72
|
-
twitter: assetsIn(appDir, (name) => /^twitter-image[-\w]*/.test(name) && IMAGE.test(name))[0] ?? null,
|
|
96
|
+
shareImages: shareImagesIn(appDir),
|
|
73
97
|
};
|
|
74
98
|
}
|
|
99
|
+
const SHARE_FILES = {
|
|
100
|
+
openGraph: /^opengraph-image[-\w]*/,
|
|
101
|
+
twitter: /^twitter-image[-\w]*/,
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Folders that are not part of the url tree, so a file in one answers no route:
|
|
105
|
+
* private (`_x`), a parallel slot (`@x`), an interception (`(.)x`), dependencies
|
|
106
|
+
* and anything hidden.
|
|
107
|
+
*/
|
|
108
|
+
function isRouteFolder(name) {
|
|
109
|
+
return !/^(?:[._@]|\(\.{1,3}\))/.test(name) && name !== "node_modules";
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Every share image under app/, by the folder it is in.
|
|
113
|
+
*
|
|
114
|
+
* A file in a folder is served from a path that keeps the folder, because two
|
|
115
|
+
* folders may each have an opengraph-image.png and one flat /_app/ name would
|
|
116
|
+
* make the second silently overwrite the first.
|
|
117
|
+
*/
|
|
118
|
+
function shareImagesIn(appDir) {
|
|
119
|
+
const found = {};
|
|
120
|
+
const walk = (folder) => {
|
|
121
|
+
const dir = join(appDir, folder);
|
|
122
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
123
|
+
for (const kind of Object.keys(SHARE_FILES)) {
|
|
124
|
+
const name = entries
|
|
125
|
+
.filter((e) => e.isFile() && SHARE_FILES[kind].test(e.name) && IMAGE.test(e.name))
|
|
126
|
+
.map((e) => e.name)
|
|
127
|
+
.sort()[0];
|
|
128
|
+
if (name) {
|
|
129
|
+
const file = folder === "" ? name : `${folder}/${name}`;
|
|
130
|
+
(found[folder] ??= {})[kind] = { file, href: assetHref(file) };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
if (entry.isDirectory() && isRouteFolder(entry.name)) {
|
|
135
|
+
walk(folder === "" ? entry.name : `${folder}/${entry.name}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
walk("");
|
|
140
|
+
return found;
|
|
141
|
+
}
|
|
142
|
+
/** The urls only, which is all a page being rendered needs of them. */
|
|
143
|
+
export function shareImageUrls(assets) {
|
|
144
|
+
return Object.fromEntries(Object.entries(assets.shareImages).map(([folder, kinds]) => [
|
|
145
|
+
folder,
|
|
146
|
+
Object.fromEntries(Object.entries(kinds).map(([kind, asset]) => [kind, asset.href])),
|
|
147
|
+
]));
|
|
148
|
+
}
|
|
75
149
|
export function typeOf(file) {
|
|
76
150
|
const extension = file.slice(file.lastIndexOf(".") + 1).toLowerCase();
|
|
77
151
|
return extension === "svg"
|
|
@@ -82,7 +156,6 @@ export function typeOf(file) {
|
|
|
82
156
|
? "image/x-icon"
|
|
83
157
|
: `image/${extension}`;
|
|
84
158
|
}
|
|
85
|
-
/** The head tags these produce, as plain data the generated entry turns into elements. */
|
|
86
159
|
/** Every asset found, favicon first: what the build copies and the dev server answers. */
|
|
87
160
|
export function allAppAssets(assets) {
|
|
88
161
|
return [
|
|
@@ -91,11 +164,15 @@ export function allAppAssets(assets) {
|
|
|
91
164
|
...(assets.appleIcon ? [assets.appleIcon] : []),
|
|
92
165
|
...assets.appleSplash,
|
|
93
166
|
...assets.screenshots,
|
|
94
|
-
...(assets.
|
|
95
|
-
...(assets.twitter ? [assets.twitter] : []),
|
|
167
|
+
...Object.values(assets.shareImages).flatMap((kinds) => Object.values(kinds)),
|
|
96
168
|
];
|
|
97
169
|
}
|
|
98
|
-
|
|
170
|
+
/**
|
|
171
|
+
* The head tags these produce, as plain data the generated entry turns into
|
|
172
|
+
* elements - the same on every page. The share images are not here: which one
|
|
173
|
+
* a page shows depends on where the page is, and that is decided as it renders.
|
|
174
|
+
*/
|
|
175
|
+
export function headTags(assets) {
|
|
99
176
|
const tags = [];
|
|
100
177
|
if (assets.favicon) {
|
|
101
178
|
tags.push({
|
|
@@ -134,27 +211,6 @@ export function headTags(assets, origin) {
|
|
|
134
211
|
tags.push({ tag: "meta", props: { name: "mobile-web-app-capable", content: "yes" } });
|
|
135
212
|
tags.push({ tag: "meta", props: { name: "apple-mobile-web-app-capable", content: "yes" } });
|
|
136
213
|
}
|
|
137
|
-
// Absolute when the app said where it lives. The og spec asks for an absolute
|
|
138
|
-
// url and several crawlers still mean it — a relative one is read by some and
|
|
139
|
-
// ignored by others, which is the worst of both. Relative is what is left
|
|
140
|
-
// when nothing said, and is better than omitting the tag.
|
|
141
|
-
const absolute = (href) => origin ? new URL(href, origin).href : href;
|
|
142
|
-
if (assets.openGraph) {
|
|
143
|
-
tags.push({
|
|
144
|
-
tag: "meta",
|
|
145
|
-
props: { property: "og:image", content: absolute(assets.openGraph.href) },
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
if (assets.twitter) {
|
|
149
|
-
tags.push({
|
|
150
|
-
tag: "meta",
|
|
151
|
-
props: { name: "twitter:image", content: absolute(assets.twitter.href) },
|
|
152
|
-
});
|
|
153
|
-
tags.push({
|
|
154
|
-
tag: "meta",
|
|
155
|
-
props: { name: "twitter:card", content: "summary_large_image" },
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
214
|
return tags;
|
|
159
215
|
}
|
|
160
216
|
//# sourceMappingURL=appAssets.js.map
|
package/dist/appAssets.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appAssets.js","sourceRoot":"","sources":["../src/appAssets.ts"],"names":[],"mappings":"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","sourcesContent":["// Files that describe the app, found by name rather than configured.\n//\n// src/app/\n// favicon.ico <link rel=\"icon\">\n// icon.png <link rel=\"icon\">, and the manifest's icons\n// icon-192.png …as many as you like; sizes read from the name\n// apple-icon.png <link rel=\"apple-touch-icon\">\n// screenshot-wide-1280x720.png the manifest's screenshots, for Chrome's\n// screenshot-narrow-750x1334.png richer install sheet; size and form\n// factor read from the name\n// apple-splash-1179x2556.png <link rel=\"apple-touch-startup-image\" media=…>\n// …one per device and orientation; see appleSplash.ts\n// opengraph-image.png <meta property=\"og:image\">\n// twitter-image.png <meta name=\"twitter:image\">\n//\n// The same bargain as every other file in this directory: put it where it\n// belongs and it works, with nothing to register. Next established these names\n// and there is nothing to gain by inventing different ones — an app moving\n// between the two should not have to rename its favicon.\n//\n// They live in `app/` rather than `public/` on purpose. `public/` is \"serve\n// this verbatim\"; these are read as well as served — an icon's size decides\n// what goes in the manifest, and its presence decides what goes in the head.\n\nimport { existsSync, readdirSync } from \"node:fs\";\nimport { isAppleSplash, splashMedia } from \"./appleSplash.js\";\n\n/** One found file, and where it will be served from. */\nexport interface AppAsset {\n /** The file's name in the app directory. */\n file: string;\n /** The url it is served at. */\n href: string;\n}\n\nexport interface AppAssets {\n favicon: AppAsset | null;\n icons: AppAsset[];\n appleIcon: AppAsset | null;\n /** For the manifest's screenshots: wide for desktop, narrow for a phone. */\n screenshots: AppAsset[];\n /** iOS launch screens, each with the media query its size maps to. */\n appleSplash: (AppAsset & { media: string | null })[];\n openGraph: AppAsset | null;\n twitter: AppAsset | null;\n}\n\nconst IMAGE = /\\.(png|svg|jpg|jpeg|webp|gif|avif)$/i;\n\n/**\n * Where these are served from.\n *\n * Their own directory rather than the output root, so an app that happens to\n * have a `public/icon.png` as well is not silently overwritten by one of\n * these, in either direction. The exception is the favicon, which browsers ask\n * for at `/favicon.ico` whatever any markup says.\n */\nexport const ASSET_BASE = \"/_app\";\n\nfunction assetsIn(dir: string, match: (name: string) => boolean): AppAsset[] {\n return readdirSync(dir)\n .filter(match)\n .sort()\n .map((file) => ({ file, href: `${ASSET_BASE}/${file}` }));\n}\n\n/** What the app declared by putting a file where it could be found. */\nexport function appAssets(appDir: string): AppAssets {\n if (!existsSync(appDir)) {\n return {\n favicon: null,\n icons: [],\n appleIcon: null,\n appleSplash: [],\n screenshots: [],\n openGraph: null,\n twitter: null,\n };\n }\n\n const names = readdirSync(appDir);\n const has = (name: string) => names.includes(name);\n\n return {\n // Served where a browser looks for it regardless of markup: a request for\n // /favicon.ico goes out whether or not a link tag exists, and answering it\n // from somewhere else means answering a 404 to the request that matters.\n favicon: has(\"favicon.ico\")\n ? { file: \"favicon.ico\", href: \"/favicon.ico\" }\n : null,\n icons: assetsIn(\n appDir,\n (name) => /^icon[-\\w]*/.test(name) && IMAGE.test(name),\n ),\n appleIcon:\n assetsIn(\n appDir,\n (name) => /^apple-icon[-\\w]*/.test(name) && IMAGE.test(name),\n )[0] ?? null,\n screenshots: assetsIn(appDir, (name) =>\n /^screenshot-(wide|narrow)[-\\w]*\\.(png|jpe?g|webp)$/i.test(name),\n ),\n appleSplash: assetsIn(appDir, isAppleSplash).map((asset) => ({\n ...asset,\n media: splashMedia(asset.file),\n })),\n openGraph:\n assetsIn(\n appDir,\n (name) => /^opengraph-image[-\\w]*/.test(name) && IMAGE.test(name),\n )[0] ?? null,\n twitter:\n assetsIn(\n appDir,\n (name) => /^twitter-image[-\\w]*/.test(name) && IMAGE.test(name),\n )[0] ?? null,\n };\n}\n\nexport function typeOf(file: string): string {\n const extension = file.slice(file.lastIndexOf(\".\") + 1).toLowerCase();\n\n return extension === \"svg\"\n ? \"image/svg+xml\"\n : extension === \"jpg\" || extension === \"jpeg\"\n ? \"image/jpeg\"\n : extension === \"ico\"\n ? \"image/x-icon\"\n : `image/${extension}`;\n}\n\n/** The head tags these produce, as plain data the generated entry turns into elements. */\n/** Every asset found, favicon first: what the build copies and the dev server answers. */\nexport function allAppAssets(assets: AppAssets): AppAsset[] {\n return [\n ...(assets.favicon ? [assets.favicon] : []),\n ...assets.icons,\n ...(assets.appleIcon ? [assets.appleIcon] : []),\n ...assets.appleSplash,\n ...assets.screenshots,\n ...(assets.openGraph ? [assets.openGraph] : []),\n ...(assets.twitter ? [assets.twitter] : []),\n ];\n}\n\nexport function headTags(\n assets: AppAssets,\n origin?: string,\n): { tag: \"link\" | \"meta\"; props: Record<string, string> }[] {\n const tags: { tag: \"link\" | \"meta\"; props: Record<string, string> }[] = [];\n\n if (assets.favicon) {\n tags.push({\n tag: \"link\",\n props: { rel: \"icon\", href: assets.favicon.href, type: \"image/x-icon\" },\n });\n }\n\n for (const icon of assets.icons) {\n // A maskable icon is for a launcher, padded for its mask; as a tab's icon\n // it would be the small one. It goes in the manifest and nowhere else.\n if (/(^|-)maskable\\b/i.test(icon.file)) continue;\n\n tags.push({\n tag: \"link\",\n props: { rel: \"icon\", href: icon.href, type: typeOf(icon.file) },\n });\n }\n\n if (assets.appleIcon) {\n tags.push({\n tag: \"link\",\n props: { rel: \"apple-touch-icon\", href: assets.appleIcon.href },\n });\n }\n\n // Only the sizes a device has: a query that matches nothing is bytes in\n // every page for no one, and the build names those files instead.\n const splashes = assets.appleSplash.filter((splash) => splash.media !== null);\n\n for (const splash of splashes) {\n tags.push({\n tag: \"link\",\n props: { rel: \"apple-touch-startup-image\", href: splash.href, media: splash.media! },\n });\n }\n\n // Safari shows a launch screen only to an app launched from the home\n // screen as an app. Whoever put these in app/ meant that.\n if (splashes.length > 0) {\n tags.push({ tag: \"meta\", props: { name: \"mobile-web-app-capable\", content: \"yes\" } });\n tags.push({ tag: \"meta\", props: { name: \"apple-mobile-web-app-capable\", content: \"yes\" } });\n }\n\n // Absolute when the app said where it lives. The og spec asks for an absolute\n // url and several crawlers still mean it — a relative one is read by some and\n // ignored by others, which is the worst of both. Relative is what is left\n // when nothing said, and is better than omitting the tag.\n const absolute = (href: string) =>\n origin ? new URL(href, origin).href : href;\n\n if (assets.openGraph) {\n tags.push({\n tag: \"meta\",\n props: { property: \"og:image\", content: absolute(assets.openGraph.href) },\n });\n }\n\n if (assets.twitter) {\n tags.push({\n tag: \"meta\",\n props: { name: \"twitter:image\", content: absolute(assets.twitter.href) },\n });\n tags.push({\n tag: \"meta\",\n props: { name: \"twitter:card\", content: \"summary_large_image\" },\n });\n }\n\n return tags;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"appAssets.js","sourceRoot":"","sources":["../src/appAssets.ts"],"names":[],"mappings":"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","sourcesContent":["// Files that describe the app, found by name rather than configured.\n//\n// src/app/\n// favicon.ico <link rel=\"icon\">\n// icon.png <link rel=\"icon\">, and the manifest's icons\n// icon-192.png …as many as you like; sizes read from the name\n// apple-icon.png <link rel=\"apple-touch-icon\">\n// screenshot-wide-1280x720.png the manifest's screenshots, for Chrome's\n// screenshot-narrow-750x1334.png richer install sheet; size and form\n// factor read from the name\n// apple-splash-1179x2556.png <link rel=\"apple-touch-startup-image\" media=…>\n// …one per device and orientation; see appleSplash.ts\n// opengraph-image.png <meta property=\"og:image\">\n// twitter-image.png <meta name=\"twitter:image\">\n//\n// The two share images may also sit in any folder beneath app/, and are then\n// the card for the routes under that folder - the nearest one wins. See\n// shareImages.ts for the rule and appAssets() for the scan.\n//\n// The same bargain as every other file in this directory: put it where it\n// belongs and it works, with nothing to register. Next established these names\n// and there is nothing to gain by inventing different ones — an app moving\n// between the two should not have to rename its favicon.\n//\n// They live in `app/` rather than `public/` on purpose. `public/` is \"serve\n// this verbatim\"; these are read as well as served — an icon's size decides\n// what goes in the manifest, and its presence decides what goes in the head.\n\nimport { existsSync, readdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { isAppleSplash, splashMedia } from \"./appleSplash.js\";\nimport type { ShareImages, ShareKind } from \"./shareImages.js\";\n\n/** One found file, and where it will be served from. */\nexport interface AppAsset {\n /** The file's name in the app directory. */\n file: string;\n /** The url it is served at. */\n href: string;\n}\n\nexport interface AppAssets {\n favicon: AppAsset | null;\n icons: AppAsset[];\n appleIcon: AppAsset | null;\n /** For the manifest's screenshots: wide for desktop, narrow for a phone. */\n screenshots: AppAsset[];\n /** iOS launch screens, each with the media query its size maps to. */\n appleSplash: (AppAsset & { media: string | null })[];\n /**\n * Share images by the folder they sit in, \"\" being app/ itself. The route\n * that shows one is found by walking up from its own folder.\n */\n shareImages: Record<string, Partial<Record<ShareKind, AppAsset>>>;\n}\n\nconst IMAGE = /\\.(png|svg|jpg|jpeg|webp|gif|avif)$/i;\n\n/**\n * Where these are served from.\n *\n * Their own directory rather than the output root, so an app that happens to\n * have a `public/icon.png` as well is not silently overwritten by one of\n * these, in either direction. The exception is the favicon, which browsers ask\n * for at `/favicon.ico` whatever any markup says.\n */\nexport const ASSET_BASE = \"/_app\";\n\n/**\n * A file's path as a url path: each segment encoded, the slashes kept.\n *\n * A folder under app/ may be `[team]`, and a bracket is not legal in a url's\n * path - a strict scraper refuses the whole address, and a card with no image\n * is all it says. Everything a plain file name is made of passes through\n * untouched, so `icon-192.png` is `icon-192.png`.\n */\nexport function assetHref(file: string): string {\n return `${ASSET_BASE}/${file.split(\"/\").map(encodeURIComponent).join(\"/\")}`;\n}\n\n/** The file path a url path names, which is what the build writes and the dev server reads. */\nexport function assetFile(href: string): string {\n try {\n return decodeURIComponent(href);\n } catch {\n // A stray % is not an encoding. Left as it came, it matches nothing.\n return href;\n }\n}\n\nfunction assetsIn(dir: string, match: (name: string) => boolean): AppAsset[] {\n return readdirSync(dir)\n .filter(match)\n .sort()\n .map((file) => ({ file, href: assetHref(file) }));\n}\n\n/** What the app declared by putting a file where it could be found. */\nexport function appAssets(appDir: string): AppAssets {\n if (!existsSync(appDir)) {\n return {\n favicon: null,\n icons: [],\n appleIcon: null,\n appleSplash: [],\n screenshots: [],\n shareImages: {},\n };\n }\n\n const names = readdirSync(appDir);\n const has = (name: string) => names.includes(name);\n\n return {\n // Served where a browser looks for it regardless of markup: a request for\n // /favicon.ico goes out whether or not a link tag exists, and answering it\n // from somewhere else means answering a 404 to the request that matters.\n favicon: has(\"favicon.ico\")\n ? { file: \"favicon.ico\", href: \"/favicon.ico\" }\n : null,\n icons: assetsIn(\n appDir,\n (name) => /^icon[-\\w]*/.test(name) && IMAGE.test(name),\n ),\n appleIcon:\n assetsIn(\n appDir,\n (name) => /^apple-icon[-\\w]*/.test(name) && IMAGE.test(name),\n )[0] ?? null,\n screenshots: assetsIn(appDir, (name) =>\n /^screenshot-(wide|narrow)[-\\w]*\\.(png|jpe?g|webp)$/i.test(name),\n ),\n appleSplash: assetsIn(appDir, isAppleSplash).map((asset) => ({\n ...asset,\n media: splashMedia(asset.file),\n })),\n shareImages: shareImagesIn(appDir),\n };\n}\n\nconst SHARE_FILES: Record<ShareKind, RegExp> = {\n openGraph: /^opengraph-image[-\\w]*/,\n twitter: /^twitter-image[-\\w]*/,\n};\n\n/**\n * Folders that are not part of the url tree, so a file in one answers no route:\n * private (`_x`), a parallel slot (`@x`), an interception (`(.)x`), dependencies\n * and anything hidden.\n */\nfunction isRouteFolder(name: string): boolean {\n return !/^(?:[._@]|\\(\\.{1,3}\\))/.test(name) && name !== \"node_modules\";\n}\n\n/**\n * Every share image under app/, by the folder it is in.\n *\n * A file in a folder is served from a path that keeps the folder, because two\n * folders may each have an opengraph-image.png and one flat /_app/ name would\n * make the second silently overwrite the first.\n */\nfunction shareImagesIn(appDir: string): AppAssets[\"shareImages\"] {\n const found: AppAssets[\"shareImages\"] = {};\n\n const walk = (folder: string): void => {\n const dir = join(appDir, folder);\n const entries = readdirSync(dir, { withFileTypes: true });\n\n for (const kind of Object.keys(SHARE_FILES) as ShareKind[]) {\n const name = entries\n .filter((e) => e.isFile() && SHARE_FILES[kind].test(e.name) && IMAGE.test(e.name))\n .map((e) => e.name)\n .sort()[0];\n\n if (name) {\n const file = folder === \"\" ? name : `${folder}/${name}`;\n\n (found[folder] ??= {})[kind] = { file, href: assetHref(file) };\n }\n }\n\n for (const entry of entries) {\n if (entry.isDirectory() && isRouteFolder(entry.name)) {\n walk(folder === \"\" ? entry.name : `${folder}/${entry.name}`);\n }\n }\n };\n\n walk(\"\");\n\n return found;\n}\n\n/** The urls only, which is all a page being rendered needs of them. */\nexport function shareImageUrls(assets: AppAssets): ShareImages {\n return Object.fromEntries(\n Object.entries(assets.shareImages).map(([folder, kinds]) => [\n folder,\n Object.fromEntries(\n Object.entries(kinds).map(([kind, asset]) => [kind, asset.href]),\n ),\n ]),\n );\n}\n\nexport function typeOf(file: string): string {\n const extension = file.slice(file.lastIndexOf(\".\") + 1).toLowerCase();\n\n return extension === \"svg\"\n ? \"image/svg+xml\"\n : extension === \"jpg\" || extension === \"jpeg\"\n ? \"image/jpeg\"\n : extension === \"ico\"\n ? \"image/x-icon\"\n : `image/${extension}`;\n}\n\n/** Every asset found, favicon first: what the build copies and the dev server answers. */\nexport function allAppAssets(assets: AppAssets): AppAsset[] {\n return [\n ...(assets.favicon ? [assets.favicon] : []),\n ...assets.icons,\n ...(assets.appleIcon ? [assets.appleIcon] : []),\n ...assets.appleSplash,\n ...assets.screenshots,\n ...Object.values(assets.shareImages).flatMap((kinds) => Object.values(kinds)),\n ];\n}\n\n/**\n * The head tags these produce, as plain data the generated entry turns into\n * elements - the same on every page. The share images are not here: which one\n * a page shows depends on where the page is, and that is decided as it renders.\n */\nexport function headTags(\n assets: AppAssets,\n): { tag: \"link\" | \"meta\"; props: Record<string, string> }[] {\n const tags: { tag: \"link\" | \"meta\"; props: Record<string, string> }[] = [];\n\n if (assets.favicon) {\n tags.push({\n tag: \"link\",\n props: { rel: \"icon\", href: assets.favicon.href, type: \"image/x-icon\" },\n });\n }\n\n for (const icon of assets.icons) {\n // A maskable icon is for a launcher, padded for its mask; as a tab's icon\n // it would be the small one. It goes in the manifest and nowhere else.\n if (/(^|-)maskable\\b/i.test(icon.file)) continue;\n\n tags.push({\n tag: \"link\",\n props: { rel: \"icon\", href: icon.href, type: typeOf(icon.file) },\n });\n }\n\n if (assets.appleIcon) {\n tags.push({\n tag: \"link\",\n props: { rel: \"apple-touch-icon\", href: assets.appleIcon.href },\n });\n }\n\n // Only the sizes a device has: a query that matches nothing is bytes in\n // every page for no one, and the build names those files instead.\n const splashes = assets.appleSplash.filter((splash) => splash.media !== null);\n\n for (const splash of splashes) {\n tags.push({\n tag: \"link\",\n props: { rel: \"apple-touch-startup-image\", href: splash.href, media: splash.media! },\n });\n }\n\n // Safari shows a launch screen only to an app launched from the home\n // screen as an app. Whoever put these in app/ meant that.\n if (splashes.length > 0) {\n tags.push({ tag: \"meta\", props: { name: \"mobile-web-app-capable\", content: \"yes\" } });\n tags.push({ tag: \"meta\", props: { name: \"apple-mobile-web-app-capable\", content: \"yes\" } });\n }\n\n return tags;\n}\n"]}
|
package/dist/metadata.d.ts
CHANGED
|
@@ -42,6 +42,10 @@ export interface OpenGraph {
|
|
|
42
42
|
siteName?: string;
|
|
43
43
|
type?: "website" | "article" | "profile" | "book" | (string & {});
|
|
44
44
|
locale?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Replaces the `opengraph-image.png` of any folder above this page, rather
|
|
47
|
+
* than following it. A file in the page's own folder wins over this.
|
|
48
|
+
*/
|
|
45
49
|
images?: string | URL | OpenGraphImage | (string | URL | OpenGraphImage)[];
|
|
46
50
|
}
|
|
47
51
|
/** The same card for X, which reads `name=` rather than `property=`. */
|
package/dist/metadata.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata.js","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,EAAE;AACF,6DAA6D;AAC7D,EAAE;AACF,4DAA4D;AAC5D,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,+EAA+E;AAC/E,4EAA4E;AAC5E,2EAA2E;AAC3E,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,MAAM","sourcesContent":["// What a page says about itself, as importable types.\n//\n// import type { Metadata } from '@rsc-kit/core/metadata'\n//\n// export const metadata: Metadata = { title: 'Orders' }\n//\n// Imported rather than ambient, and that is the whole point of the move. An\n// ambient declaration has to be COPIED into the project, which means it is not\n// there until the build has run once — so a freshly cloned app reports \"Cannot\n// find name 'Metadata'\" on every page until someone runs the dev server. An\n// import resolves from node_modules the moment dependencies are installed.\n//\n// The ambient names still work. `.rsc-kit/rsc-types.d.ts` now aliases these\n// rather than restating them, so there is one definition and two ways to reach\n// it.\n\nexport interface IconDescriptor {\n url: string | URL;\n type?: string;\n sizes?: string;\n color?: string;\n rel?: string;\n media?: string;\n fetchPriority?: \"high\" | \"low\" | \"auto\";\n}\n\nexport type IconURL = string | URL;\n\nexport interface Icons {\n icon?: IconURL | IconDescriptor | (IconURL | IconDescriptor)[];\n apple?: IconURL | IconDescriptor | (IconURL | IconDescriptor)[];\n shortcut?: IconURL | IconDescriptor | (IconURL | IconDescriptor)[];\n other?: IconDescriptor | IconDescriptor[];\n}\n\n/** A layout's title, wrapping the titles of the pages beneath it. */\nexport interface TitleTemplate {\n /** `%s` stands in for the page's own title. */\n template?: string;\n /** Used by a page that exports no title of its own. */\n default?: string;\n}\n\n/** One image a share card may show. A string is its url. */\nexport interface OpenGraphImage {\n url: string | URL;\n width?: number;\n height?: number;\n alt?: string;\n type?: string;\n}\n\n/**\n * The card a link to this page unfurls into on Facebook, Slack, LinkedIn and\n * most of the rest. Rendered with `property=`, which is what those scrapers\n * read — a `name=` attribute is ignored by every one of them.\n */\nexport interface OpenGraph {\n title?: string;\n description?: string;\n /** Absolute, or relative to `metadataBase`. */\n url?: string | URL;\n siteName?: string;\n type?: \"website\" | \"article\" | \"profile\" | \"book\" | (string & {});\n locale?: string;\n images?: string | URL | OpenGraphImage | (string | URL | OpenGraphImage)[];\n}\n\n/** The same card for X, which reads `name=` rather than `property=`. */\nexport interface Twitter {\n card?: \"summary\" | \"summary_large_image\" | \"app\" | \"player\";\n title?: string;\n description?: string;\n /** The site's account, `@handle`. */\n site?: string;\n /** The author's account, `@handle`. */\n creator?: string;\n images?: string | URL | OpenGraphImage | (string | URL | OpenGraphImage)[];\n}\n\nexport interface Robots {\n index?: boolean;\n follow?: boolean;\n noarchive?: boolean;\n nosnippet?: boolean;\n noimageindex?: boolean;\n nocache?: boolean;\n notranslate?: boolean;\n indexifembedded?: boolean;\n nositelinkssearchbox?: boolean;\n unavailable_after?: string;\n \"max-video-preview\"?: number | string;\n \"max-image-preview\"?: \"none\" | \"standard\" | \"large\";\n \"max-snippet\"?: number;\n googleBot?: string | Omit<Robots, \"googleBot\">;\n}\n\n/** An iOS launch screen: an image, and the media query naming the device it is for. */\nexport interface AppleStartupImage {\n url: string | URL;\n media?: string;\n}\n\nexport interface AppleWebApp {\n /** Opens as an app from the home screen - no browser chrome. */\n capable?: boolean;\n /** The name under the home screen icon, when it should differ from the page title. */\n title?: string;\n /** The status bar over the app: `default`, `black`, or `black-translucent` to draw under it. */\n statusBarStyle?: \"default\" | \"black\" | \"black-translucent\";\n /** Launch screens. A string or url is one image for every device. */\n startupImage?: string | URL | (string | URL | AppleStartupImage)[];\n}\n\nexport interface Metadata {\n /** A string on a page; a template on a layout, applied to the pages below it. */\n title?: string | TitleTemplate;\n description?: string;\n keywords?: string | string[];\n author?: string;\n /**\n * A string, or the object Next takes: `{ index: false, follow: false }`\n * becomes `<meta name=\"robots\" content=\"noindex, nofollow\">`, the flags\n * by name, the limits as `name:value`; `googleBot` is the same shape for\n * `<meta name=\"googlebot\">`.\n */\n robots?: string | Robots;\n /**\n * Where the site lives, so a relative image or url can be made absolute.\n *\n * metadataBase: new URL('https://example.com')\n *\n * On the root layout, once. A share-card scraper needs an absolute url and\n * several refuse a relative one; without this, `og:image` for an image in\n * `app/` is emitted relative and works in some places and not others. The\n * same name as Next, so a port carries it across unchanged.\n */\n metadataBase?: string | URL;\n icons?: IconURL | (IconURL | IconDescriptor)[] | Icons | null;\n openGraph?: OpenGraph;\n twitter?: Twitter;\n /**\n * How the app behaves added to an iPhone's home screen. Next's shape, so a\n * port carries it across unchanged.\n *\n * appleWebApp: {\n * capable: true,\n * title: 'Orders',\n * statusBarStyle: 'black-translucent',\n * startupImage: [{ url: '/splash-1179x2556.png', media: '...' }],\n * }\n *\n * `true` is `{ capable: true }`. Launch screens are simpler as files: an\n * `apple-splash-1179x2556.png` in `app/` is linked with the right media\n * query for the device its size names - see the PWA guide.\n */\n appleWebApp?: boolean | AppleWebApp;\n /** @deprecated Use `openGraph.title`. Still rendered, correctly, as `property=`. */\n \"og:title\"?: string;\n /** @deprecated Use `openGraph.description`. */\n \"og:description\"?: string;\n /** @deprecated Use `openGraph.images`. */\n \"og:image\"?: string;\n /** @deprecated Use `openGraph.url`. */\n \"og:url\"?: string;\n /** @deprecated Use `openGraph.type`. */\n \"og:type\"?: string;\n /** @deprecated Use `openGraph.siteName`. */\n \"og:site_name\"?: string;\n /** @deprecated Use `twitter.card`. */\n \"twitter:card\"?: string;\n /** @deprecated Use `twitter.title`. */\n \"twitter:title\"?: string;\n /** @deprecated Use `twitter.description`. */\n \"twitter:description\"?: string;\n /** @deprecated Use `twitter.images`. */\n \"twitter:image\"?: string;\n /** @deprecated Use `twitter.site`. */\n \"twitter:site\"?: string;\n\n /**\n * Any other meta tag, by name.\n *\n * other: { 'fb:app_id': '123', 'theme-color': '#000' }\n *\n * Here rather than alongside the named keys, and that is what makes the rest\n * of this interface worth annotating. An index signature on the interface\n * itself made every key legal — so `titel` was accepted in silence, and an\n * editor offered no completions at all, because with any identifier valid\n * TypeScript reads an unfinished key as a shorthand property and goes looking\n * for a variable by that name.\n */\n other?: Record<string, string | string[] | null | undefined>;\n}\n\n/**\n * Metadata that depends on the request.\n *\n * Receives the same awaitables a page does, so one shape is learned rather\n * than two:\n *\n * export const generateMetadata: GenerateMetadata<{ slug: string }> =\n * async ({ params }) => ({ title: (await params).slug })\n */\nexport type GenerateMetadata<P = Record<string, string>> = (args: {\n params: Promise<P>;\n searchParams: Promise<URLSearchParams>;\n}) => Metadata | Promise<Metadata>;\n\n/** One rule block of a robots.txt: which agents, what they may and may not fetch. */\nexport interface RobotsRule {\n userAgent?: string | string[];\n allow?: string | string[];\n disallow?: string | string[];\n crawlDelay?: number;\n}\n\n/** One url of a sitemap. `url` may be relative when the root layout has a metadataBase. */\nexport interface SitemapEntry {\n url: string;\n lastModified?: string | Date;\n changeFrequency?:\n \"always\" | \"hourly\" | \"daily\" | \"weekly\" | \"monthly\" | \"yearly\" | \"never\";\n priority?: number;\n /** Image urls on this page, for image search. */\n images?: string[];\n /** Translations of this page: language tag to url. */\n alternates?: { languages?: Record<string, string> };\n}\n\n/** A link in an llms.txt section. */\nexport interface LlmsLink {\n title: string;\n url: string;\n description?: string;\n}\n\nexport interface LlmsSection {\n title: string;\n links: LlmsLink[];\n}\n\n/**\n * The files a site describes itself with, each from a file beside the root\n * layout and written the way Next writes them:\n *\n * app/robots.ts -> /robots.txt default export returns MetadataRoute.Robots\n * app/sitemap.ts -> /sitemap.xml default export returns MetadataRoute.Sitemap\n * app/llms.ts -> /llms.txt default export returns MetadataRoute.Llms\n *\n * Each may return a string instead, served as written. A relative url in any\n * of them is made absolute with the root layout's metadataBase.\n */\nexport namespace MetadataRoute {\n export type Robots = {\n rules: RobotsRule | RobotsRule[];\n sitemap?: string | string[];\n host?: string;\n };\n export type Sitemap = SitemapEntry[];\n /** The llms.txt shape at llmstxt.org: a title, a summary, then sections of links. */\n export type Llms = {\n title: string;\n summary?: string;\n /** Paragraphs after the summary, before the sections. */\n details?: string | string[];\n sections?: LlmsSection[];\n };\n}\n\n/**\n * `export const viewport`, in Next's shape, on a layout or a page: layouts\n * outer to inner, then the page, merged per key. What is not set is\n * `width=device-width, initial-scale=1`, written into every document the way\n * Next wrote it — a layout ported from Next never wrote the tag, and a page\n * without one is the desktop layout on a phone. A layout that renders\n * `<meta name=\"viewport\">` itself is left alone.\n */\nexport interface Viewport {\n width?: string | number;\n height?: string | number;\n initialScale?: number;\n minimumScale?: number;\n maximumScale?: number;\n userScalable?: boolean;\n viewportFit?: \"auto\" | \"cover\" | \"contain\";\n interactiveWidget?: \"resizes-visual\" | \"resizes-content\" | \"overlays-content\";\n /** One colour, or one per media query. Wins over the web manifest's. */\n themeColor?: string | { media?: string; color: string }[];\n colorScheme?: \"normal\" | \"light\" | \"dark\" | \"light dark\" | \"dark light\" | \"only light\";\n}\n"]}
|
|
1
|
+
{"version":3,"file":"metadata.js","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,EAAE;AACF,6DAA6D;AAC7D,EAAE;AACF,4DAA4D;AAC5D,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,+EAA+E;AAC/E,4EAA4E;AAC5E,2EAA2E;AAC3E,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,MAAM","sourcesContent":["// What a page says about itself, as importable types.\n//\n// import type { Metadata } from '@rsc-kit/core/metadata'\n//\n// export const metadata: Metadata = { title: 'Orders' }\n//\n// Imported rather than ambient, and that is the whole point of the move. An\n// ambient declaration has to be COPIED into the project, which means it is not\n// there until the build has run once — so a freshly cloned app reports \"Cannot\n// find name 'Metadata'\" on every page until someone runs the dev server. An\n// import resolves from node_modules the moment dependencies are installed.\n//\n// The ambient names still work. `.rsc-kit/rsc-types.d.ts` now aliases these\n// rather than restating them, so there is one definition and two ways to reach\n// it.\n\nexport interface IconDescriptor {\n url: string | URL;\n type?: string;\n sizes?: string;\n color?: string;\n rel?: string;\n media?: string;\n fetchPriority?: \"high\" | \"low\" | \"auto\";\n}\n\nexport type IconURL = string | URL;\n\nexport interface Icons {\n icon?: IconURL | IconDescriptor | (IconURL | IconDescriptor)[];\n apple?: IconURL | IconDescriptor | (IconURL | IconDescriptor)[];\n shortcut?: IconURL | IconDescriptor | (IconURL | IconDescriptor)[];\n other?: IconDescriptor | IconDescriptor[];\n}\n\n/** A layout's title, wrapping the titles of the pages beneath it. */\nexport interface TitleTemplate {\n /** `%s` stands in for the page's own title. */\n template?: string;\n /** Used by a page that exports no title of its own. */\n default?: string;\n}\n\n/** One image a share card may show. A string is its url. */\nexport interface OpenGraphImage {\n url: string | URL;\n width?: number;\n height?: number;\n alt?: string;\n type?: string;\n}\n\n/**\n * The card a link to this page unfurls into on Facebook, Slack, LinkedIn and\n * most of the rest. Rendered with `property=`, which is what those scrapers\n * read — a `name=` attribute is ignored by every one of them.\n */\nexport interface OpenGraph {\n title?: string;\n description?: string;\n /** Absolute, or relative to `metadataBase`. */\n url?: string | URL;\n siteName?: string;\n type?: \"website\" | \"article\" | \"profile\" | \"book\" | (string & {});\n locale?: string;\n /**\n * Replaces the `opengraph-image.png` of any folder above this page, rather\n * than following it. A file in the page's own folder wins over this.\n */\n images?: string | URL | OpenGraphImage | (string | URL | OpenGraphImage)[];\n}\n\n/** The same card for X, which reads `name=` rather than `property=`. */\nexport interface Twitter {\n card?: \"summary\" | \"summary_large_image\" | \"app\" | \"player\";\n title?: string;\n description?: string;\n /** The site's account, `@handle`. */\n site?: string;\n /** The author's account, `@handle`. */\n creator?: string;\n images?: string | URL | OpenGraphImage | (string | URL | OpenGraphImage)[];\n}\n\nexport interface Robots {\n index?: boolean;\n follow?: boolean;\n noarchive?: boolean;\n nosnippet?: boolean;\n noimageindex?: boolean;\n nocache?: boolean;\n notranslate?: boolean;\n indexifembedded?: boolean;\n nositelinkssearchbox?: boolean;\n unavailable_after?: string;\n \"max-video-preview\"?: number | string;\n \"max-image-preview\"?: \"none\" | \"standard\" | \"large\";\n \"max-snippet\"?: number;\n googleBot?: string | Omit<Robots, \"googleBot\">;\n}\n\n/** An iOS launch screen: an image, and the media query naming the device it is for. */\nexport interface AppleStartupImage {\n url: string | URL;\n media?: string;\n}\n\nexport interface AppleWebApp {\n /** Opens as an app from the home screen - no browser chrome. */\n capable?: boolean;\n /** The name under the home screen icon, when it should differ from the page title. */\n title?: string;\n /** The status bar over the app: `default`, `black`, or `black-translucent` to draw under it. */\n statusBarStyle?: \"default\" | \"black\" | \"black-translucent\";\n /** Launch screens. A string or url is one image for every device. */\n startupImage?: string | URL | (string | URL | AppleStartupImage)[];\n}\n\nexport interface Metadata {\n /** A string on a page; a template on a layout, applied to the pages below it. */\n title?: string | TitleTemplate;\n description?: string;\n keywords?: string | string[];\n author?: string;\n /**\n * A string, or the object Next takes: `{ index: false, follow: false }`\n * becomes `<meta name=\"robots\" content=\"noindex, nofollow\">`, the flags\n * by name, the limits as `name:value`; `googleBot` is the same shape for\n * `<meta name=\"googlebot\">`.\n */\n robots?: string | Robots;\n /**\n * Where the site lives, so a relative image or url can be made absolute.\n *\n * metadataBase: new URL('https://example.com')\n *\n * On the root layout, once. A share-card scraper needs an absolute url and\n * several refuse a relative one; without this, `og:image` for an image in\n * `app/` is emitted relative and works in some places and not others. The\n * same name as Next, so a port carries it across unchanged.\n */\n metadataBase?: string | URL;\n icons?: IconURL | (IconURL | IconDescriptor)[] | Icons | null;\n openGraph?: OpenGraph;\n twitter?: Twitter;\n /**\n * How the app behaves added to an iPhone's home screen. Next's shape, so a\n * port carries it across unchanged.\n *\n * appleWebApp: {\n * capable: true,\n * title: 'Orders',\n * statusBarStyle: 'black-translucent',\n * startupImage: [{ url: '/splash-1179x2556.png', media: '...' }],\n * }\n *\n * `true` is `{ capable: true }`. Launch screens are simpler as files: an\n * `apple-splash-1179x2556.png` in `app/` is linked with the right media\n * query for the device its size names - see the PWA guide.\n */\n appleWebApp?: boolean | AppleWebApp;\n /** @deprecated Use `openGraph.title`. Still rendered, correctly, as `property=`. */\n \"og:title\"?: string;\n /** @deprecated Use `openGraph.description`. */\n \"og:description\"?: string;\n /** @deprecated Use `openGraph.images`. */\n \"og:image\"?: string;\n /** @deprecated Use `openGraph.url`. */\n \"og:url\"?: string;\n /** @deprecated Use `openGraph.type`. */\n \"og:type\"?: string;\n /** @deprecated Use `openGraph.siteName`. */\n \"og:site_name\"?: string;\n /** @deprecated Use `twitter.card`. */\n \"twitter:card\"?: string;\n /** @deprecated Use `twitter.title`. */\n \"twitter:title\"?: string;\n /** @deprecated Use `twitter.description`. */\n \"twitter:description\"?: string;\n /** @deprecated Use `twitter.images`. */\n \"twitter:image\"?: string;\n /** @deprecated Use `twitter.site`. */\n \"twitter:site\"?: string;\n\n /**\n * Any other meta tag, by name.\n *\n * other: { 'fb:app_id': '123', 'theme-color': '#000' }\n *\n * Here rather than alongside the named keys, and that is what makes the rest\n * of this interface worth annotating. An index signature on the interface\n * itself made every key legal — so `titel` was accepted in silence, and an\n * editor offered no completions at all, because with any identifier valid\n * TypeScript reads an unfinished key as a shorthand property and goes looking\n * for a variable by that name.\n */\n other?: Record<string, string | string[] | null | undefined>;\n}\n\n/**\n * Metadata that depends on the request.\n *\n * Receives the same awaitables a page does, so one shape is learned rather\n * than two:\n *\n * export const generateMetadata: GenerateMetadata<{ slug: string }> =\n * async ({ params }) => ({ title: (await params).slug })\n */\nexport type GenerateMetadata<P = Record<string, string>> = (args: {\n params: Promise<P>;\n searchParams: Promise<URLSearchParams>;\n}) => Metadata | Promise<Metadata>;\n\n/** One rule block of a robots.txt: which agents, what they may and may not fetch. */\nexport interface RobotsRule {\n userAgent?: string | string[];\n allow?: string | string[];\n disallow?: string | string[];\n crawlDelay?: number;\n}\n\n/** One url of a sitemap. `url` may be relative when the root layout has a metadataBase. */\nexport interface SitemapEntry {\n url: string;\n lastModified?: string | Date;\n changeFrequency?:\n \"always\" | \"hourly\" | \"daily\" | \"weekly\" | \"monthly\" | \"yearly\" | \"never\";\n priority?: number;\n /** Image urls on this page, for image search. */\n images?: string[];\n /** Translations of this page: language tag to url. */\n alternates?: { languages?: Record<string, string> };\n}\n\n/** A link in an llms.txt section. */\nexport interface LlmsLink {\n title: string;\n url: string;\n description?: string;\n}\n\nexport interface LlmsSection {\n title: string;\n links: LlmsLink[];\n}\n\n/**\n * The files a site describes itself with, each from a file beside the root\n * layout and written the way Next writes them:\n *\n * app/robots.ts -> /robots.txt default export returns MetadataRoute.Robots\n * app/sitemap.ts -> /sitemap.xml default export returns MetadataRoute.Sitemap\n * app/llms.ts -> /llms.txt default export returns MetadataRoute.Llms\n *\n * Each may return a string instead, served as written. A relative url in any\n * of them is made absolute with the root layout's metadataBase.\n */\nexport namespace MetadataRoute {\n export type Robots = {\n rules: RobotsRule | RobotsRule[];\n sitemap?: string | string[];\n host?: string;\n };\n export type Sitemap = SitemapEntry[];\n /** The llms.txt shape at llmstxt.org: a title, a summary, then sections of links. */\n export type Llms = {\n title: string;\n summary?: string;\n /** Paragraphs after the summary, before the sections. */\n details?: string | string[];\n sections?: LlmsSection[];\n };\n}\n\n/**\n * `export const viewport`, in Next's shape, on a layout or a page: layouts\n * outer to inner, then the page, merged per key. What is not set is\n * `width=device-width, initial-scale=1`, written into every document the way\n * Next wrote it — a layout ported from Next never wrote the tag, and a page\n * without one is the desktop layout on a phone. A layout that renders\n * `<meta name=\"viewport\">` itself is left alone.\n */\nexport interface Viewport {\n width?: string | number;\n height?: string | number;\n initialScale?: number;\n minimumScale?: number;\n maximumScale?: number;\n userScalable?: boolean;\n viewportFit?: \"auto\" | \"cover\" | \"contain\";\n interactiveWidget?: \"resizes-visual\" | \"resizes-content\" | \"overlays-content\";\n /** One colour, or one per media query. Wins over the web manifest's. */\n themeColor?: string | { media?: string; color: string }[];\n colorScheme?: \"normal\" | \"light\" | \"dark\" | \"light dark\" | \"dark light\" | \"only light\";\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type ShareKind = "openGraph" | "twitter";
|
|
2
|
+
/** The urls found, by the folder they sit in: "" is app/ itself. */
|
|
3
|
+
export type ShareImages = Record<string, Partial<Record<ShareKind, string>>>;
|
|
4
|
+
/** How far below app/ a folder is: "" is 0, "shop/cart" is 2. */
|
|
5
|
+
export declare function depthOf(folder: string): number;
|
|
6
|
+
/**
|
|
7
|
+
* The folder a route file lives in, below app/.
|
|
8
|
+
*
|
|
9
|
+
* Names are the build's: `app/(shop)/cart/page` is "(shop)/cart", and
|
|
10
|
+
* `app/layout` is "". Route groups stay, because they are folders on disk and
|
|
11
|
+
* a file in one is found by the folder it is in, not by the url it never shows.
|
|
12
|
+
*/
|
|
13
|
+
export declare function folderOf(component: string): string;
|
|
14
|
+
/** The image in the nearest folder at or above `folder`, and how deep that folder is. */
|
|
15
|
+
export declare function nearestShareImage(images: ShareImages, folder: string, kind: ShareKind): {
|
|
16
|
+
href: string;
|
|
17
|
+
depth: number;
|
|
18
|
+
} | null;
|
|
19
|
+
/**
|
|
20
|
+
* Whether the file is what the page shows, given the images its metadata
|
|
21
|
+
* exports.
|
|
22
|
+
*
|
|
23
|
+
* The nearer declaration wins. Equal distance goes to the file: it is the more
|
|
24
|
+
* specific thing to have done - a file beside the page is that page's card.
|
|
25
|
+
*/
|
|
26
|
+
export declare function fileWins(file: {
|
|
27
|
+
depth: number;
|
|
28
|
+
} | null, exported: {
|
|
29
|
+
depth: number;
|
|
30
|
+
} | null): boolean;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Which share image a route shows.
|
|
2
|
+
//
|
|
3
|
+
// An opengraph-image.png or twitter-image.png can sit in app/ itself or in any
|
|
4
|
+
// folder beneath it, and the nearest one to a route is the route's. A page
|
|
5
|
+
// that exports its own openGraph.images is a declaration too, and the nearer
|
|
6
|
+
// of the two wins - at the same distance the file does, which is Next's rule
|
|
7
|
+
// and the reason this one is not different.
|
|
8
|
+
//
|
|
9
|
+
// Kept apart from appAssets.ts, which reads the disk: this runs while a page
|
|
10
|
+
// renders, inside the bundle, and a module that imports node:fs does not
|
|
11
|
+
// belong there.
|
|
12
|
+
/** How far below app/ a folder is: "" is 0, "shop/cart" is 2. */
|
|
13
|
+
export function depthOf(folder) {
|
|
14
|
+
return folder === "" ? 0 : folder.split("/").length;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The folder a route file lives in, below app/.
|
|
18
|
+
*
|
|
19
|
+
* Names are the build's: `app/(shop)/cart/page` is "(shop)/cart", and
|
|
20
|
+
* `app/layout` is "". Route groups stay, because they are folders on disk and
|
|
21
|
+
* a file in one is found by the folder it is in, not by the url it never shows.
|
|
22
|
+
*/
|
|
23
|
+
export function folderOf(component) {
|
|
24
|
+
return component.split("/").slice(1, -1).join("/");
|
|
25
|
+
}
|
|
26
|
+
/** The image in the nearest folder at or above `folder`, and how deep that folder is. */
|
|
27
|
+
export function nearestShareImage(images, folder, kind) {
|
|
28
|
+
const parts = folder === "" ? [] : folder.split("/");
|
|
29
|
+
for (let n = parts.length; n >= 0; n--) {
|
|
30
|
+
const href = images[parts.slice(0, n).join("/")]?.[kind];
|
|
31
|
+
if (href)
|
|
32
|
+
return { href, depth: n };
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Whether the file is what the page shows, given the images its metadata
|
|
38
|
+
* exports.
|
|
39
|
+
*
|
|
40
|
+
* The nearer declaration wins. Equal distance goes to the file: it is the more
|
|
41
|
+
* specific thing to have done - a file beside the page is that page's card.
|
|
42
|
+
*/
|
|
43
|
+
export function fileWins(file, exported) {
|
|
44
|
+
if (!file)
|
|
45
|
+
return false;
|
|
46
|
+
if (!exported)
|
|
47
|
+
return true;
|
|
48
|
+
return file.depth >= exported.depth;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=shareImages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shareImages.js","sourceRoot":"","sources":["../src/shareImages.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,EAAE;AACF,+EAA+E;AAC/E,2EAA2E;AAC3E,6EAA6E;AAC7E,6EAA6E;AAC7E,4CAA4C;AAC5C,EAAE;AACF,6EAA6E;AAC7E,yEAAyE;AACzE,gBAAgB;AAOhB,iEAAiE;AACjE,MAAM,UAAU,OAAO,CAAC,MAAc;IACpC,OAAO,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;AACtD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,SAAiB;IACxC,OAAO,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACrD,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,iBAAiB,CAC/B,MAAmB,EACnB,MAAc,EACd,IAAe;IAEf,MAAM,KAAK,GAAG,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAErD,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QAEzD,IAAI,IAAI;YAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IACtC,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CACtB,IAA8B,EAC9B,QAAkC;IAElC,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,OAAO,IAAI,CAAC,KAAK,IAAI,QAAQ,CAAC,KAAK,CAAC;AACtC,CAAC","sourcesContent":["// Which share image a route shows.\n//\n// An opengraph-image.png or twitter-image.png can sit in app/ itself or in any\n// folder beneath it, and the nearest one to a route is the route's. A page\n// that exports its own openGraph.images is a declaration too, and the nearer\n// of the two wins - at the same distance the file does, which is Next's rule\n// and the reason this one is not different.\n//\n// Kept apart from appAssets.ts, which reads the disk: this runs while a page\n// renders, inside the bundle, and a module that imports node:fs does not\n// belong there.\n\nexport type ShareKind = \"openGraph\" | \"twitter\";\n\n/** The urls found, by the folder they sit in: \"\" is app/ itself. */\nexport type ShareImages = Record<string, Partial<Record<ShareKind, string>>>;\n\n/** How far below app/ a folder is: \"\" is 0, \"shop/cart\" is 2. */\nexport function depthOf(folder: string): number {\n return folder === \"\" ? 0 : folder.split(\"/\").length;\n}\n\n/**\n * The folder a route file lives in, below app/.\n *\n * Names are the build's: `app/(shop)/cart/page` is \"(shop)/cart\", and\n * `app/layout` is \"\". Route groups stay, because they are folders on disk and\n * a file in one is found by the folder it is in, not by the url it never shows.\n */\nexport function folderOf(component: string): string {\n return component.split(\"/\").slice(1, -1).join(\"/\");\n}\n\n/** The image in the nearest folder at or above `folder`, and how deep that folder is. */\nexport function nearestShareImage(\n images: ShareImages,\n folder: string,\n kind: ShareKind,\n): { href: string; depth: number } | null {\n const parts = folder === \"\" ? [] : folder.split(\"/\");\n\n for (let n = parts.length; n >= 0; n--) {\n const href = images[parts.slice(0, n).join(\"/\")]?.[kind];\n\n if (href) return { href, depth: n };\n }\n\n return null;\n}\n\n/**\n * Whether the file is what the page shows, given the images its metadata\n * exports.\n *\n * The nearer declaration wins. Equal distance goes to the file: it is the more\n * specific thing to have done - a file beside the page is that page's card.\n */\nexport function fileWins(\n file: { depth: number } | null,\n exported: { depth: number } | null,\n): boolean {\n if (!file) return false;\n if (!exported) return true;\n\n return file.depth >= exported.depth;\n}\n"]}
|
package/dist/vite.d.ts
CHANGED
|
@@ -186,6 +186,24 @@ export interface RscKitOptions {
|
|
|
186
186
|
* alone before Nitro built the server from the same entry.
|
|
187
187
|
*/
|
|
188
188
|
devFallback?: string | false;
|
|
189
|
+
/**
|
|
190
|
+
* Paths that belong to the backend, whatever page would also match them.
|
|
191
|
+
*
|
|
192
|
+
* A url nothing here owns is handed to the backend, but a page that matches
|
|
193
|
+
* is this app's: a dynamic root route such as `[team]/[app]` matches every
|
|
194
|
+
* two-segment url, renders, says `notFound()` - and a page that said that is
|
|
195
|
+
* never forwarded, so `/gitlab/connect` was the not-found page and the
|
|
196
|
+
* backend never saw it. Naming the prefixes the backend owns forwards them
|
|
197
|
+
* before any page is matched:
|
|
198
|
+
*
|
|
199
|
+
* rscKit({ backendPaths: ['/auth', '/github', '/gitlab'] })
|
|
200
|
+
*
|
|
201
|
+
* Each is a prefix at a segment boundary - `/auth` is `/auth` and everything
|
|
202
|
+
* under it, never `/authors` - and every method goes, a POST as a GET. A
|
|
203
|
+
* team called `auth` is then unreachable, which is the price of the name.
|
|
204
|
+
* Without a backend where this runs, such a url is this app's not-found page.
|
|
205
|
+
*/
|
|
206
|
+
backendPaths?: string[];
|
|
189
207
|
/**
|
|
190
208
|
* This package's directory, holding the client runtime the browser entry
|
|
191
209
|
* imports. Vite stages configs through node_modules/.vite-temp, so
|
|
@@ -297,6 +315,12 @@ export interface RscKitOptions {
|
|
|
297
315
|
watch?: string[];
|
|
298
316
|
};
|
|
299
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* The prefixes a backend owns, as the matcher reads them: `/auth`, no trailing
|
|
320
|
+
* slash. Refused rather than corrected where a mistake would silently send the
|
|
321
|
+
* whole app, or its own endpoints, to the backend.
|
|
322
|
+
*/
|
|
323
|
+
export declare function normaliseBackendPaths(paths: readonly string[] | undefined): string[];
|
|
300
324
|
/**
|
|
301
325
|
* The service worker, written into the client output at build time.
|
|
302
326
|
*
|