@rsc-kit/core 0.30.14 → 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.js +69 -16
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
package/dist/vite.js
CHANGED
|
@@ -24,7 +24,7 @@ import { loadEnv, parseAst } from "vite";
|
|
|
24
24
|
import { stampDisplayNames } from "./stableNames.js";
|
|
25
25
|
import { REPORT_FILE, buildReport } from "./buildReport.js";
|
|
26
26
|
import { MANIFEST_PATH, manifestWarning, webManifest } from "./webManifest.js";
|
|
27
|
-
import { ASSET_BASE, allAppAssets, appAssets, headTags, typeOf, } from "./appAssets.js";
|
|
27
|
+
import { ASSET_BASE, allAppAssets, assetFile, appAssets, headTags, shareImageUrls, typeOf, } from "./appAssets.js";
|
|
28
28
|
import { reactCacheImports } from "./reactCache.js";
|
|
29
29
|
import { clientEntries, clientScanPlugin, engineClientEntries, } from "./clientEntries.js";
|
|
30
30
|
import { serverImportsOfClientPackages } from "./clientImports.js";
|
|
@@ -195,8 +195,7 @@ let foundAssets = {
|
|
|
195
195
|
appleIcon: null,
|
|
196
196
|
appleSplash: [],
|
|
197
197
|
screenshots: [],
|
|
198
|
-
|
|
199
|
-
twitter: null,
|
|
198
|
+
shareImages: {},
|
|
200
199
|
};
|
|
201
200
|
/** Host functions to generate stubs for — see RscKitOptions.hostActions. */
|
|
202
201
|
let hostActions;
|
|
@@ -1393,7 +1392,7 @@ function copyAppAssets(clientDir) {
|
|
|
1393
1392
|
if (all.length === 0)
|
|
1394
1393
|
return;
|
|
1395
1394
|
for (const asset of all) {
|
|
1396
|
-
const to = join(clientDir, asset.href.slice(1));
|
|
1395
|
+
const to = join(clientDir, assetFile(asset.href.slice(1)));
|
|
1397
1396
|
mkdirSync(dirname(to), { recursive: true });
|
|
1398
1397
|
copyFileSync(join(sourceDir, "app", asset.file), to);
|
|
1399
1398
|
}
|
|
@@ -2424,6 +2423,8 @@ const API_METHODS = [
|
|
|
2424
2423
|
];
|
|
2425
2424
|
/** `orders.section.tsx` — a region of a page that can be refreshed by name. */
|
|
2426
2425
|
const SECTION_FILE = /\.section\.(tsx|jsx|ts|js)$/;
|
|
2426
|
+
/** A share image file: the pages beneath its folder change what they say about themselves. */
|
|
2427
|
+
const SHARE_IMAGE_FILE = /^(?:opengraph|twitter)-image[-\w]*\.(?:png|svg|jpe?g|webp|gif|avif)$/i;
|
|
2427
2428
|
/**
|
|
2428
2429
|
* Whether a page or a section declares refreshOn. Read from the source, as
|
|
2429
2430
|
* section names are, because the manifest is written before any bundle exists.
|
|
@@ -3146,6 +3147,7 @@ ${
|
|
|
3146
3147
|
import { SegmentBoundary } from ${JSON.stringify(join(packageDir, "js/SegmentBoundary"))}
|
|
3147
3148
|
import { LoadingBoundary } from ${JSON.stringify(join(packageDir, "js/LoadingBoundary"))}
|
|
3148
3149
|
import { DocumentTitle } from ${JSON.stringify(join(packageDir, "js/DocumentTitle"))}
|
|
3150
|
+
import { depthOf, fileWins, folderOf, nearestShareImage } from ${JSON.stringify(join(packageDir, "shareImages"))}
|
|
3149
3151
|
import { SlotBoundary } from ${JSON.stringify(join(packageDir, "js/SlotBoundary"))}
|
|
3150
3152
|
import { RouteErrorBoundary } from ${JSON.stringify(join(packageDir, "js/RouteErrorBoundary"))}
|
|
3151
3153
|
import __rsc_assets from 'virtual:vite-rsc/assets-manifest'
|
|
@@ -3199,6 +3201,9 @@ ${mapEntries.join("\n")}
|
|
|
3199
3201
|
/** Head tags for the icons and share images found in app/. */
|
|
3200
3202
|
const APP_HEAD: { tag: any; props: Record<string, string> }[] = ${JSON.stringify(headTags(foundAssets))}
|
|
3201
3203
|
|
|
3204
|
+
/** The share images found under app/, by the folder each is in - see shareImages.ts. */
|
|
3205
|
+
const SHARE_IMAGES: Record<string, { openGraph?: string; twitter?: string }> = ${JSON.stringify(shareImageUrls(foundAssets))}
|
|
3206
|
+
|
|
3202
3207
|
const WEB_MANIFEST: { href: string; themeColor?: string } | null = ${JSON.stringify(webManifestOptions
|
|
3203
3208
|
? {
|
|
3204
3209
|
href: MANIFEST_PATH,
|
|
@@ -4199,13 +4204,35 @@ async function renderTree(
|
|
|
4199
4204
|
}
|
|
4200
4205
|
|
|
4201
4206
|
for (const [i, found] of APP_HEAD.entries()) {
|
|
4202
|
-
|
|
4207
|
+
head.push(createElement(found.tag, { key: '__a' + i, ...found.props }))
|
|
4208
|
+
}
|
|
4203
4209
|
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4210
|
+
// The share image files found in app/. The nearest to this page is its
|
|
4211
|
+
// card - unless its metadata exports images from a segment nearer still,
|
|
4212
|
+
// in which case the file is left out and the exported ones render, so a
|
|
4213
|
+
// page that names its own image replaces the site's instead of following
|
|
4214
|
+
// it. Scrapers read the first og:image they meet; a second after the
|
|
4215
|
+
// site's would be a card that never changes.
|
|
4216
|
+
const folder = folderOf(component)
|
|
4217
|
+
const sources = md ? imageSources.get(md) : undefined
|
|
4218
|
+
const shared = (kind: 'openGraph' | 'twitter') => {
|
|
4219
|
+
const file = nearestShareImage(SHARE_IMAGES, folder, kind)
|
|
4220
|
+
const source = sources?.[kind]
|
|
4221
|
+
|
|
4222
|
+
return fileWins(file, source?.exports ? source : null) ? file : null
|
|
4223
|
+
}
|
|
4224
|
+
const ogFile = shared('openGraph')
|
|
4225
|
+
const twitterFile = shared('twitter')
|
|
4207
4226
|
|
|
4208
|
-
|
|
4227
|
+
if (ogFile) head.push(createElement('meta', { key: '__og', property: 'og:image', content: absolute(ogFile.href) }))
|
|
4228
|
+
|
|
4229
|
+
if (twitterFile) {
|
|
4230
|
+
head.push(createElement('meta', { key: '__tw', name: 'twitter:image', content: absolute(twitterFile.href) }))
|
|
4231
|
+
|
|
4232
|
+
// The card a twitter image implies, unless the page named its own.
|
|
4233
|
+
if ((md?.twitter as { card?: unknown } | undefined)?.card == null) {
|
|
4234
|
+
head.push(createElement('meta', { key: '__twc', name: 'twitter:card', content: 'summary_large_image' }))
|
|
4235
|
+
}
|
|
4209
4236
|
}
|
|
4210
4237
|
|
|
4211
4238
|
if (md) {
|
|
@@ -4305,7 +4332,7 @@ async function renderTree(
|
|
|
4305
4332
|
if (og.siteName != null) head.push(tag('og:site_name', og.siteName))
|
|
4306
4333
|
if (og.type != null) head.push(tag('og:type', og.type))
|
|
4307
4334
|
if (og.locale != null) head.push(tag('og:locale', og.locale))
|
|
4308
|
-
if (og.images != null) images('og:image', og.images)
|
|
4335
|
+
if (og.images != null && !ogFile) images('og:image', og.images)
|
|
4309
4336
|
}
|
|
4310
4337
|
|
|
4311
4338
|
if (md.twitter) {
|
|
@@ -4316,7 +4343,7 @@ async function renderTree(
|
|
|
4316
4343
|
if (tw.description != null) head.push(tag('twitter:description', tw.description))
|
|
4317
4344
|
if (tw.site != null) head.push(tag('twitter:site', tw.site))
|
|
4318
4345
|
if (tw.creator != null) head.push(tag('twitter:creator', tw.creator))
|
|
4319
|
-
if (tw.images != null) images('twitter:image', tw.images)
|
|
4346
|
+
if (tw.images != null && !twitterFile) images('twitter:image', tw.images)
|
|
4320
4347
|
}
|
|
4321
4348
|
|
|
4322
4349
|
// icons is links, not meta. A string is one icon; an object names which
|
|
@@ -5209,6 +5236,17 @@ const METADATA_PROBE_GRACE_MS = 20
|
|
|
5209
5236
|
|
|
5210
5237
|
const METADATA_POSTPONED = Symbol('metadata postponed')
|
|
5211
5238
|
|
|
5239
|
+
/**
|
|
5240
|
+
* For each of openGraph and twitter, how deep the segment that supplied the
|
|
5241
|
+
* merged object is, and whether it exports images - the other half of the
|
|
5242
|
+
* comparison a share image file is held to. Beside the metadata rather than
|
|
5243
|
+
* inside it, so the object a page's tags are rendered from stays only what
|
|
5244
|
+
* the app wrote.
|
|
5245
|
+
*/
|
|
5246
|
+
type ImageSources = { openGraph?: { depth: number; exports: boolean }; twitter?: { depth: number; exports: boolean } }
|
|
5247
|
+
|
|
5248
|
+
const imageSources = new WeakMap<object, ImageSources>()
|
|
5249
|
+
|
|
5212
5250
|
export async function resolveMetadata(
|
|
5213
5251
|
component: string,
|
|
5214
5252
|
props: Record<string, unknown> = {},
|
|
@@ -5273,18 +5311,27 @@ export async function resolveMetadata(
|
|
|
5273
5311
|
// happened to declare one of its own.
|
|
5274
5312
|
const merged: Record<string, unknown> = {}
|
|
5275
5313
|
const other: Record<string, unknown> = {}
|
|
5314
|
+
const sources: ImageSources = {}
|
|
5276
5315
|
|
|
5277
|
-
const take = (from: Record<string, unknown
|
|
5316
|
+
const take = (from: Record<string, unknown>, depth: number) => {
|
|
5278
5317
|
for (const [k, v] of Object.entries(from)) {
|
|
5279
5318
|
if (k === 'title') continue
|
|
5280
5319
|
if (k === 'other') Object.assign(other, v as Record<string, unknown>)
|
|
5281
5320
|
else merged[k] = v
|
|
5321
|
+
|
|
5322
|
+
// openGraph and twitter are replaced whole by a nearer segment's, so
|
|
5323
|
+
// whichever segment took them last is the one whose images - or lack of
|
|
5324
|
+
// them - the merged object carries. That segment's depth is what a file
|
|
5325
|
+
// in app/ is weighed against; see shareImages.ts.
|
|
5326
|
+
if (k === 'openGraph' || k === 'twitter') {
|
|
5327
|
+
sources[k] = { depth, exports: typeof v === 'object' && v !== null && (v as { images?: unknown }).images != null }
|
|
5328
|
+
}
|
|
5282
5329
|
}
|
|
5283
5330
|
}
|
|
5284
5331
|
|
|
5285
|
-
|
|
5332
|
+
layoutMeta.forEach((m, i) => take(m, depthOf(folderOf(layouts[i].component))))
|
|
5286
5333
|
|
|
5287
|
-
take(page)
|
|
5334
|
+
take(page, depthOf(folderOf(component)))
|
|
5288
5335
|
|
|
5289
5336
|
if (Object.keys(other).length > 0) merged.other = other
|
|
5290
5337
|
|
|
@@ -5309,7 +5356,11 @@ export async function resolveMetadata(
|
|
|
5309
5356
|
}
|
|
5310
5357
|
if (title != null) merged.title = title
|
|
5311
5358
|
|
|
5312
|
-
|
|
5359
|
+
if (Object.keys(merged).length === 0) return null
|
|
5360
|
+
|
|
5361
|
+
imageSources.set(merged, sources)
|
|
5362
|
+
|
|
5363
|
+
return merged
|
|
5313
5364
|
}
|
|
5314
5365
|
|
|
5315
5366
|
// Buffered render (worker: rsc / rscWithoutCallbacks — used at prerender time).
|
|
@@ -7637,7 +7688,8 @@ export function rscKit(options = {}) {
|
|
|
7637
7688
|
res.end(webManifest(webManifestOptions));
|
|
7638
7689
|
return;
|
|
7639
7690
|
}
|
|
7640
|
-
|
|
7691
|
+
// Either spelling of a bracket: the tag says %5B, a hand-typed url says [.
|
|
7692
|
+
const asset = allAppAssets(foundAssets).find((a) => assetFile(a.href) === assetFile(url));
|
|
7641
7693
|
const file = asset ? join(sourceDir, "app", asset.file) : null;
|
|
7642
7694
|
if (!asset || !file || !existsSync(file))
|
|
7643
7695
|
return next();
|
|
@@ -7724,6 +7776,7 @@ export function rscKit(options = {}) {
|
|
|
7724
7776
|
// published without any — generic-host.test.ts fails if it reappears.
|
|
7725
7777
|
return ((base !== stem && shapes.has(stem)) ||
|
|
7726
7778
|
SECTION_FILE.test(base) ||
|
|
7779
|
+
SHARE_IMAGE_FILE.test(base) ||
|
|
7727
7780
|
(routeConfig !== null && base === routeConfig.file));
|
|
7728
7781
|
};
|
|
7729
7782
|
const restart = (file) => {
|