@rsc-kit/core 0.20.1 → 0.20.3
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/bytecodeCheck.d.ts +15 -0
- package/dist/bytecodeCheck.js +65 -0
- package/dist/bytecodeCheck.js.map +1 -0
- package/dist/clientPackages.d.ts +25 -0
- package/dist/clientPackages.js +31 -0
- package/dist/clientPackages.js.map +1 -1
- package/dist/files.d.ts +9 -3
- package/dist/files.js +20 -10
- package/dist/files.js.map +1 -1
- package/dist/host.d.ts +7 -4
- package/dist/host.js +23 -1
- package/dist/host.js.map +1 -1
- package/dist/js/DefaultRouteError.js +15 -5
- package/dist/js/DefaultRouteError.js.map +1 -1
- package/dist/js/Link.d.ts +1 -1
- package/dist/js/Link.js +28 -2
- package/dist/js/Link.js.map +1 -1
- package/dist/js/createViteRscApp.js +9 -4
- package/dist/js/createViteRscApp.js.map +1 -1
- package/dist/js/navigate.js +39 -6
- package/dist/js/navigate.js.map +1 -1
- package/dist/js/staleAssets.d.ts +10 -0
- package/dist/js/staleAssets.js +27 -6
- package/dist/js/staleAssets.js.map +1 -1
- package/dist/js/viewportPrefetch.d.ts +21 -0
- package/dist/js/viewportPrefetch.js +67 -0
- package/dist/js/viewportPrefetch.js.map +1 -0
- package/dist/metadata.d.ts +24 -0
- package/dist/metadata.js.map +1 -1
- package/dist/request.js +36 -8
- package/dist/request.js.map +1 -1
- package/dist/useSsr.js +10 -3
- package/dist/useSsr.js.map +1 -1
- package/dist/vite.js +173 -11
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
package/dist/vite.js
CHANGED
|
@@ -32,7 +32,8 @@ import { ownHosts } from "./hostRouting.js";
|
|
|
32
32
|
import { unrollBarrelImports } from "./barrelImports.js";
|
|
33
33
|
import { serverRendererMessage, SERVER_RENDERER, ssrProxyModule, UseSsrError, withoutSsrDirective } from "./useSsr.js";
|
|
34
34
|
import { httpHostCalls } from "./hostCalls.js";
|
|
35
|
-
import {
|
|
35
|
+
import { bytecodeNote } from "./bytecodeCheck.js";
|
|
36
|
+
import { clientPackages, importsServerRenderer, packageDir as installedPackageDir, packageEntryInGraph, polyfillsToTrace, traceableByName } from "./clientPackages.js";
|
|
36
37
|
// Resolved once per rscKit() call. One build runs in one process, so these are
|
|
37
38
|
// module state rather than threaded through every helper.
|
|
38
39
|
let projectRoot;
|
|
@@ -2092,6 +2093,11 @@ function metadataExports(absPath) {
|
|
|
2092
2093
|
return {
|
|
2093
2094
|
static: /export\s+const\s+metadata\b/.test(src),
|
|
2094
2095
|
generate: /export\s+(async\s+)?function\s+generateMetadata\b/.test(src),
|
|
2096
|
+
viewport: /export\s+const\s+viewport\b/.test(src),
|
|
2097
|
+
writes: {
|
|
2098
|
+
charset: /\bcharSet\s*=/.test(src),
|
|
2099
|
+
viewport: /name\s*=\s*["']viewport["']/.test(src),
|
|
2100
|
+
},
|
|
2095
2101
|
};
|
|
2096
2102
|
}
|
|
2097
2103
|
/**
|
|
@@ -2258,7 +2264,6 @@ const NITRO_HANDLER_OPTIONS = ` props: (match, request) => ({
|
|
|
2258
2264
|
...match.params,
|
|
2259
2265
|
...Object.fromEntries(new URL(request.url).searchParams),
|
|
2260
2266
|
}),
|
|
2261
|
-
version: process.env.RSC_BUILD_VERSION,
|
|
2262
2267
|
// A built server gzips what it answers, where the runtime can; the dev
|
|
2263
2268
|
// server answers raw, which is what a person reading a response wants.
|
|
2264
2269
|
compress: import.meta.env.PROD,
|
|
@@ -2340,11 +2345,15 @@ function generateEntryRsc(fallbackOrigin = "") {
|
|
|
2340
2345
|
imports.push(`import ${c.alias} from ${JSON.stringify(c.absPath)}`);
|
|
2341
2346
|
mapEntries.push(` ${JSON.stringify(c.name)}: ${c.alias},`);
|
|
2342
2347
|
const meta = metadataExports(c.absPath);
|
|
2343
|
-
if (meta.static || meta.generate) {
|
|
2344
|
-
|
|
2348
|
+
if (meta.static || meta.generate || meta.viewport || meta.writes.charset || meta.writes.viewport) {
|
|
2349
|
+
if (meta.static || meta.generate || meta.viewport) {
|
|
2350
|
+
imports.push(`import * as ${c.alias}_meta from ${JSON.stringify(c.absPath)}`);
|
|
2351
|
+
}
|
|
2345
2352
|
const fields = [
|
|
2346
2353
|
meta.static ? `static: ${c.alias}_meta.metadata` : null,
|
|
2347
2354
|
meta.generate ? `generate: ${c.alias}_meta.generateMetadata` : null,
|
|
2355
|
+
meta.viewport ? `viewport: ${c.alias}_meta.viewport` : null,
|
|
2356
|
+
meta.writes.charset || meta.writes.viewport ? `writes: ${JSON.stringify(meta.writes)}` : null,
|
|
2348
2357
|
].filter(Boolean);
|
|
2349
2358
|
metaEntries.push(` ${JSON.stringify(c.name)}: { ${fields.join(", ")} },`);
|
|
2350
2359
|
}
|
|
@@ -2583,10 +2592,57 @@ export async function handleApiRoute(
|
|
|
2583
2592
|
return answer
|
|
2584
2593
|
}
|
|
2585
2594
|
|
|
2586
|
-
const metadataMap: Record<string, { static?: any; generate?: (p: any) => any }> = {
|
|
2595
|
+
const metadataMap: Record<string, { static?: any; generate?: (p: any) => any; viewport?: any; writes?: { charset: boolean; viewport: boolean } }> = {
|
|
2587
2596
|
${metaEntries.join("\n")}
|
|
2588
2597
|
}
|
|
2589
2598
|
|
|
2599
|
+
/**
|
|
2600
|
+
* The viewport, in Next's shape: export const viewport on a layout or page,
|
|
2601
|
+
* layouts outer to inner then the page, merged per key. What is not set is
|
|
2602
|
+
* Next's default - width=device-width, initial-scale=1 - because a layout
|
|
2603
|
+
* ported from Next never wrote the tag: Next put it there, and a page with
|
|
2604
|
+
* no viewport meta is the desktop layout on a phone, which is how a port
|
|
2605
|
+
* found out.
|
|
2606
|
+
*/
|
|
2607
|
+
function resolveViewport(component: string, layouts: LayoutEntry[]): Record<string, unknown> {
|
|
2608
|
+
const merged: Record<string, unknown> = { width: 'device-width', initialScale: 1 }
|
|
2609
|
+
|
|
2610
|
+
for (const l of layouts) Object.assign(merged, metadataMap[l.component]?.viewport ?? {})
|
|
2611
|
+
Object.assign(merged, metadataMap[component]?.viewport ?? {})
|
|
2612
|
+
|
|
2613
|
+
return merged
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
/** The content attribute for a viewport object, spelled the way browsers read it. */
|
|
2617
|
+
function viewportContent(viewport: Record<string, unknown>): string {
|
|
2618
|
+
const names: Record<string, string> = {
|
|
2619
|
+
width: 'width',
|
|
2620
|
+
height: 'height',
|
|
2621
|
+
initialScale: 'initial-scale',
|
|
2622
|
+
minimumScale: 'minimum-scale',
|
|
2623
|
+
maximumScale: 'maximum-scale',
|
|
2624
|
+
userScalable: 'user-scalable',
|
|
2625
|
+
viewportFit: 'viewport-fit',
|
|
2626
|
+
interactiveWidget: 'interactive-widget',
|
|
2627
|
+
}
|
|
2628
|
+
const parts: string[] = []
|
|
2629
|
+
|
|
2630
|
+
for (const [key, name] of Object.entries(names)) {
|
|
2631
|
+
const value = viewport[key]
|
|
2632
|
+
|
|
2633
|
+
if (value === undefined || value === null) continue
|
|
2634
|
+
|
|
2635
|
+
parts.push(name + '=' + (typeof value === 'boolean' ? (value ? 'yes' : 'no') : String(value)))
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
return parts.join(', ')
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
/** Whether any file on the route renders the tag itself. */
|
|
2642
|
+
function routeWrites(component: string, layouts: LayoutEntry[], tag: 'charset' | 'viewport'): boolean {
|
|
2643
|
+
return [...layouts.map((l) => l.component), component].some((name) => metadataMap[name]?.writes?.[tag] === true)
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2590
2646
|
const staticParamsMap: Record<string, () => any> = {
|
|
2591
2647
|
${paramEntries.join("\n")}
|
|
2592
2648
|
}
|
|
@@ -2603,6 +2659,35 @@ export function manifest(): any {
|
|
|
2603
2659
|
return ${JSON.stringify(routeManifest())}
|
|
2604
2660
|
}
|
|
2605
2661
|
|
|
2662
|
+
let cachedBuildId: string | null = null
|
|
2663
|
+
|
|
2664
|
+
/**
|
|
2665
|
+
* What this build's client is, as a short id.
|
|
2666
|
+
*
|
|
2667
|
+
* Derived from the bootstrap script the document ships - the hashed name of
|
|
2668
|
+
* the browser entry, which changes with the client references and chunks
|
|
2669
|
+
* it maps - so two builds whose browsers would disagree never share one.
|
|
2670
|
+
* The host puts it on every answer and compares it against what a client
|
|
2671
|
+
* says it is running; a mismatch on a payload request is a 409, and the
|
|
2672
|
+
* client loads the document instead. Read once, on demand, because the
|
|
2673
|
+
* bootstrap content is a build product this bundle only reaches at runtime.
|
|
2674
|
+
*/
|
|
2675
|
+
export async function buildId(): Promise<string> {
|
|
2676
|
+
if (cachedBuildId !== null) return cachedBuildId
|
|
2677
|
+
|
|
2678
|
+
const content: string = await (import.meta as any).viteRsc.loadBootstrapScriptContent('index')
|
|
2679
|
+
let hash = 2166136261
|
|
2680
|
+
|
|
2681
|
+
for (let i = 0; i < content.length; i++) {
|
|
2682
|
+
hash ^= content.charCodeAt(i)
|
|
2683
|
+
hash = Math.imul(hash, 16777619) >>> 0
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
cachedBuildId = hash.toString(16).padStart(8, '0')
|
|
2687
|
+
|
|
2688
|
+
return cachedBuildId
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2606
2691
|
/**
|
|
2607
2692
|
* For each server action id, whether a client built it. The build asks after
|
|
2608
2693
|
* the bundle exists, because the answer is a mark on the loaded function and
|
|
@@ -3088,6 +3173,33 @@ async function renderTree(
|
|
|
3088
3173
|
return new URL(text, base).href
|
|
3089
3174
|
}
|
|
3090
3175
|
|
|
3176
|
+
// What Next wrote into every document without being asked, and a layout
|
|
3177
|
+
// copied from a Next app therefore never writes: the charset, and the
|
|
3178
|
+
// viewport - without which a phone lays the page out at desktop width.
|
|
3179
|
+
// Rendered unless a file on the route renders the tag itself.
|
|
3180
|
+
if (!routeWrites(component, layouts, 'charset')) head.push(createElement('meta', { key: '__cs', charSet: 'utf-8' }))
|
|
3181
|
+
|
|
3182
|
+
const viewport = resolveViewport(component, layouts)
|
|
3183
|
+
|
|
3184
|
+
if (!routeWrites(component, layouts, 'viewport')) {
|
|
3185
|
+
head.push(createElement('meta', { key: '__vp', name: 'viewport', content: viewportContent(viewport) }))
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3188
|
+
// themeColor and colorScheme live on the viewport export in Next, and one
|
|
3189
|
+
// set there wins over the web manifest's colour below.
|
|
3190
|
+
const themeColors = viewport.themeColor as string | { media?: string; color: string }[] | undefined
|
|
3191
|
+
|
|
3192
|
+
if (typeof themeColors === 'string') {
|
|
3193
|
+
head.push(createElement('meta', { key: '__vtc', name: 'theme-color', content: themeColors }))
|
|
3194
|
+
} else if (Array.isArray(themeColors)) {
|
|
3195
|
+
for (const [i, entry] of themeColors.entries()) {
|
|
3196
|
+
head.push(createElement('meta', { key: '__vtc' + i, name: 'theme-color', content: entry.color, ...(entry.media ? { media: entry.media } : {}) }))
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
if (typeof viewport.colorScheme === 'string') {
|
|
3200
|
+
head.push(createElement('meta', { key: '__vcs', name: 'color-scheme', content: viewport.colorScheme }))
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3091
3203
|
for (const [i, found] of APP_HEAD.entries()) {
|
|
3092
3204
|
const props = { ...found.props }
|
|
3093
3205
|
|
|
@@ -3101,7 +3213,7 @@ async function renderTree(
|
|
|
3101
3213
|
if (WEB_MANIFEST) {
|
|
3102
3214
|
head.push(createElement('link', { key: '__mf', rel: 'manifest', href: WEB_MANIFEST.href }))
|
|
3103
3215
|
|
|
3104
|
-
if (WEB_MANIFEST.themeColor) {
|
|
3216
|
+
if (WEB_MANIFEST.themeColor && themeColors === undefined) {
|
|
3105
3217
|
head.push(
|
|
3106
3218
|
createElement('meta', { key: '__tc', name: 'theme-color', content: WEB_MANIFEST.themeColor }),
|
|
3107
3219
|
)
|
|
@@ -3122,6 +3234,15 @@ async function renderTree(
|
|
|
3122
3234
|
if (md.description != null) head.push(createElement('meta', { key: '__d', name: 'description', content: String(md.description) }))
|
|
3123
3235
|
// The name only, never the version - see the identify option.
|
|
3124
3236
|
if (manifest().build?.identify) head.push(createElement('meta', { key: '__g', name: 'generator', content: 'rsc-kit' }))
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
// Which build this document is, for the client to say on every navigation
|
|
3240
|
+
// - read from the document rather than learned from the first answer,
|
|
3241
|
+
// because a document a service worker served from its cache is the last
|
|
3242
|
+
// build's while the answers are this one's. An id, not a version number.
|
|
3243
|
+
if (bootstrap) head.push(createElement('meta', { key: '__v', name: 'rsc-kit:build', content: await buildId() }))
|
|
3244
|
+
|
|
3245
|
+
if (md) {
|
|
3125
3246
|
|
|
3126
3247
|
// robots is a string, or the object Next takes: index and follow as
|
|
3127
3248
|
// their no- forms, the flags by name, the limits as name:value. googleBot
|
|
@@ -4353,6 +4474,7 @@ async function serve(request: Request): Promise<Response> {
|
|
|
4353
4474
|
handleApiRoute,
|
|
4354
4475
|
resolveMetadata,
|
|
4355
4476
|
runRouteMiddleware,
|
|
4477
|
+
buildId,
|
|
4356
4478
|
} as never,
|
|
4357
4479
|
${NITRO_HANDLER_OPTIONS}${NITRO_PRERENDERED} maxActionBody: ${maxActionBody === undefined ? "undefined" : String(maxActionBody)},
|
|
4358
4480
|
})
|
|
@@ -5297,18 +5419,35 @@ export function rscKit(options = {}) {
|
|
|
5297
5419
|
setup(nitro) {
|
|
5298
5420
|
if (nitro.options.dev)
|
|
5299
5421
|
return;
|
|
5422
|
+
// Said at the end of a bun build, only when true: which module keeps
|
|
5423
|
+
// `bun build --compile --bytecode` from applying, and where. Bun
|
|
5424
|
+
// itself names no file and exits 0. See bytecodeCheck.ts.
|
|
5425
|
+
if (nitro.options.preset === "bun") {
|
|
5426
|
+
nitro.hooks.hook("compiled", () => {
|
|
5427
|
+
const note = bytecodeNote(nitro.options.output.serverDir);
|
|
5428
|
+
if (note)
|
|
5429
|
+
log(note);
|
|
5430
|
+
});
|
|
5431
|
+
}
|
|
5300
5432
|
// External at Nitro's layer too. The Vite build leaves these as
|
|
5301
5433
|
// imports, and Nitro would then bundle them into its own chunks -
|
|
5302
5434
|
// which for a native package fails at load, and for a polyfill like
|
|
5303
5435
|
// reflect-metadata runs it after the chunk that checks for it.
|
|
5304
5436
|
// Traced, each is copied into .output/server/node_modules and
|
|
5305
5437
|
// imported by the built server the way its author expected.
|
|
5306
|
-
// Only the ones the
|
|
5307
|
-
// name, for every entry it cannot
|
|
5308
|
-
//
|
|
5438
|
+
// Only the ones the tracer can find by name. It says so, once per
|
|
5439
|
+
// name, for every entry it cannot - a list of every native package
|
|
5440
|
+
// anyone might install is mostly ones this app does not have, and
|
|
5441
|
+
// one the app has only through a dependency, under an isolated
|
|
5442
|
+
// store, is reached through that dependency and not by name.
|
|
5443
|
+
// And the polyfills, by pattern: bundled, one merged into the chunk
|
|
5444
|
+
// of the dependency that checks for it, after that chunk's imports -
|
|
5445
|
+
// and the server did not boot. External, the entry's import of it
|
|
5446
|
+
// comes first. See polyfillsToTrace.
|
|
5309
5447
|
nitro.options.traceDeps = [
|
|
5310
5448
|
...(nitro.options.traceDeps ?? []),
|
|
5311
|
-
...[...DEFAULT_SERVER_EXTERNALS, ...(options.serverExternalPackages ?? [])]
|
|
5449
|
+
...traceableByName([...DEFAULT_SERVER_EXTERNALS, ...(options.serverExternalPackages ?? [])], projectRoot),
|
|
5450
|
+
...polyfillsToTrace(FIRST_POLYFILLS, projectRoot),
|
|
5312
5451
|
];
|
|
5313
5452
|
// The assets, precompressed at build and served with their encoding
|
|
5314
5453
|
// by Nitro's own static handler; the host gzips the rest as it
|
|
@@ -5319,6 +5458,21 @@ export function rscKit(options = {}) {
|
|
|
5319
5458
|
if (typeof nitro.options.compressPublicAssets !== "object")
|
|
5320
5459
|
nitro.options.compressPublicAssets = true;
|
|
5321
5460
|
nitro.options.virtual ??= {};
|
|
5461
|
+
// The names of functions survive Nitro's bundle. The pages were
|
|
5462
|
+
// prerendered against Vite's own server bundles, where a dependency
|
|
5463
|
+
// is loaded from node_modules under the names it was written with;
|
|
5464
|
+
// Nitro then bundles that dependency into the server, and renames a
|
|
5465
|
+
// function whose name collides with another's - J becomes J2 - and
|
|
5466
|
+
// `name` follows the identifier. React's resume of a stored shell
|
|
5467
|
+
// compares the component names it recorded with the ones it meets,
|
|
5468
|
+
// and a renamed one is "Expected the resume to render <J> in this
|
|
5469
|
+
// slot but instead it rendered <J2>", on every request, with the
|
|
5470
|
+
// hole rendered in the browser instead. keepNames pins `name` to
|
|
5471
|
+
// what the source said, whatever the identifier became.
|
|
5472
|
+
nitro.options.rollupConfig = {
|
|
5473
|
+
...(nitro.options.rollupConfig ?? {}),
|
|
5474
|
+
output: { keepNames: true, ...(nitro.options.rollupConfig?.output ?? {}) },
|
|
5475
|
+
};
|
|
5322
5476
|
// A polyfill a dependency checks for at module evaluation, loaded
|
|
5323
5477
|
// before any service. The bundler places an external import after
|
|
5324
5478
|
// the chunk imports of the module that had it, whatever the source
|
|
@@ -5532,7 +5686,15 @@ export function rscKit(options = {}) {
|
|
|
5532
5686
|
// node_modules. Installed from npm the name resolves on its own.
|
|
5533
5687
|
alias: aliasEntries(),
|
|
5534
5688
|
},
|
|
5535
|
-
build: {
|
|
5689
|
+
build: {
|
|
5690
|
+
emptyOutDir: true,
|
|
5691
|
+
// Rolldown reports, after every build, which plugin hooks took
|
|
5692
|
+
// the time. Here the answer is always the same: the typecheck hook
|
|
5693
|
+
// - tsc, run before the bundle on purpose, as `next build` does -
|
|
5694
|
+
// and the two resolvers that look at every import. A paragraph
|
|
5695
|
+
// saying so at the end of each docker build is not a finding.
|
|
5696
|
+
rollupOptions: { checks: { pluginTimings: false } },
|
|
5697
|
+
},
|
|
5536
5698
|
// What reaches the browser. Vite's own VITE_ prefix, and PUBLIC_ - the
|
|
5537
5699
|
// scaffold's spelling, and Next's minus its brand - so a variable
|
|
5538
5700
|
// named for a port reads through import.meta.env without a config
|