@rsc-kit/core 0.30.27 → 0.30.29

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/vite.d.ts CHANGED
@@ -421,6 +421,21 @@ export declare function bootsTheApp(file: string): boolean;
421
421
  * that changes what the worker holds is a new worker.
422
422
  */
423
423
  export declare function workerVersion(precache: string[], storedDir: string | null): string;
424
+ /**
425
+ * The PPR contract's build: what an edge checks a cached shell against.
426
+ *
427
+ * Not the client's build id, which is what a browser tab is told and covers
428
+ * only what the browser downloads - a release that changed only what the
429
+ * server renders kept it, and an edge holding the old shell would have joined
430
+ * it to the new server's holes. So it is that id and the bytes of every shell
431
+ * and its postponed state. A server change that reaches a shell is a new
432
+ * build to the edge; one that leaves them all byte for byte the same is not,
433
+ * because the shell the edge holds is then this build's. A tab is not made to
434
+ * reload for either.
435
+ */
436
+ export declare function pprBuildId(clientBuild: string, storedDir: string): string;
437
+ /** Where the host reads the PPR build back, beside the shells it describes. */
438
+ export declare const PPR_BUILD_FILE = "_ppr.build";
424
439
  export declare function smallStylesheetReader(assetsDir: string, setting: "auto" | true | number): (href: string) => string | null;
425
440
  /** A server action the bundle registered: where it is and what it is called. */
426
441
  export interface KnownAction {
package/dist/vite.js CHANGED
@@ -23,6 +23,7 @@ import rsc, { getPluginApi } from "@vitejs/plugin-rsc";
23
23
  import { loadEnv, parseAst } from "vite";
24
24
  import { stampDisplayNames } from "./stableNames.js";
25
25
  import { REPORT_FILE, buildReport } from "./buildReport.js";
26
+ import { edgeShells, pprMarker } from "./edgeShells.js";
26
27
  import { MANIFEST_PATH, manifestWarning, webManifest } from "./webManifest.js";
27
28
  import { ASSET_BASE, allAppAssets, assetFile, appAssets, headTags, shareImageUrls, typeOf, } from "./appAssets.js";
28
29
  import { reactCacheImports } from "./reactCache.js";
@@ -1607,6 +1608,41 @@ export function workerVersion(precache, storedDir) {
1607
1608
  }
1608
1609
  return version.digest("hex").slice(0, 12);
1609
1610
  }
1611
+ /**
1612
+ * The PPR contract's build: what an edge checks a cached shell against.
1613
+ *
1614
+ * Not the client's build id, which is what a browser tab is told and covers
1615
+ * only what the browser downloads - a release that changed only what the
1616
+ * server renders kept it, and an edge holding the old shell would have joined
1617
+ * it to the new server's holes. So it is that id and the bytes of every shell
1618
+ * and its postponed state. A server change that reaches a shell is a new
1619
+ * build to the edge; one that leaves them all byte for byte the same is not,
1620
+ * because the shell the edge holds is then this build's. A tab is not made to
1621
+ * reload for either.
1622
+ */
1623
+ export function pprBuildId(clientBuild, storedDir) {
1624
+ const id = createHash("sha256");
1625
+ id.update(clientBuild);
1626
+ const shells = [];
1627
+ const walk = (dir) => {
1628
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
1629
+ const path = join(dir, entry.name);
1630
+ if (entry.isDirectory())
1631
+ walk(path);
1632
+ else if (/\.(?:ppr\.html|postponed\.json)$/.test(entry.name))
1633
+ shells.push(path);
1634
+ }
1635
+ };
1636
+ if (existsSync(storedDir))
1637
+ walk(storedDir);
1638
+ for (const path of shells.sort()) {
1639
+ id.update("\n" + relative(storedDir, path) + "\n");
1640
+ id.update(readFileSync(path));
1641
+ }
1642
+ return id.digest("hex").slice(0, 16);
1643
+ }
1644
+ /** Where the host reads the PPR build back, beside the shells it describes. */
1645
+ export const PPR_BUILD_FILE = "_ppr.build";
1610
1646
  function writeServiceWorker(clientDir, frozen = [], offlineUrl = null,
1611
1647
  // The pages the build stored, which the worker serves cache-first. Their
1612
1648
  // bytes are part of what a version is - see below.
@@ -1764,6 +1800,7 @@ async function prerenderAfterBundles(bundle, staticDir, assetsDir, knownActions
1764
1800
  const collected = [];
1765
1801
  let refusal = null;
1766
1802
  let results;
1803
+ const build = await engine.buildId?.();
1767
1804
  try {
1768
1805
  results = await prerender({
1769
1806
  engine,
@@ -1774,7 +1811,7 @@ async function prerenderAfterBundles(bundle, staticDir, assetsDir, knownActions
1774
1811
  // them, and replayed against a different bundle React finds the
1775
1812
  // wrong name in every slot. Unstamped - which is what every build
1776
1813
  // before this one wrote - the host cannot tell and resumes anyway.
1777
- version: await engine.buildId?.(),
1814
+ version: build,
1778
1815
  serviceWorker: offline,
1779
1816
  stylesheet: inlineStylesheets === false
1780
1817
  ? undefined
@@ -1972,6 +2009,8 @@ ${legend(counted)}
1972
2009
  .filter((r) => existsSync(join(staticDir, pathKeyOf(r.url) + ".html")))
1973
2010
  .map((r) => r.url),
1974
2011
  results,
2012
+ shells: edgeShells(results, manifest.routes),
2013
+ build,
1975
2014
  };
1976
2015
  }
1977
2016
  /**
@@ -8073,7 +8112,12 @@ export function rscKit(options = {}) {
8073
8112
  const staticDir = clientOut
8074
8113
  ? join(dirname(clientOut), "server", NITRO_STATIC_DIR)
8075
8114
  : join(outDir, NITRO_STATIC_DIR);
8076
- const { frozen, results } = await prerenderAfterBundles(bundle, staticDir, clientOut ?? publicAssetsDir, builder ? knownActionsOf(builder.config, projectRoot) : []);
8115
+ const { frozen, results, shells, build: clientBuild } = await prerenderAfterBundles(bundle, staticDir, clientOut ?? publicAssetsDir, builder ? knownActionsOf(builder.config, projectRoot) : []);
8116
+ // Before the pages are packed into a module or the assets, so the host
8117
+ // finds it wherever it reads the shells from.
8118
+ const build = clientBuild ? pprBuildId(clientBuild, staticDir) : undefined;
8119
+ if (build && existsSync(staticDir))
8120
+ writeFileSync(join(staticDir, PPR_BUILD_FILE), build);
8077
8121
  // The same pages once more, as a module. A Worker has no filesystem, and
8078
8122
  // until this existed every Cloudflare deploy rendered its "static" pages
8079
8123
  // live, silently - the reader found no directory and fell through. The
@@ -8109,6 +8153,9 @@ export function rscKit(options = {}) {
8109
8153
  // after the prerender, and an export taken any earlier left them out.
8110
8154
  if (output === "export")
8111
8155
  await exportAfterPrerender(results, staticDir, clientOut ?? publicAssetsDir);
8156
+ if (shells.left.length) {
8157
+ log(`ppr: not listed for an edge in ppr.json, so served by the origin: ${shells.left.join("; ")}`);
8158
+ }
8112
8159
  // What this build produced, where a deploy can read it without parsing
8113
8160
  // vite.config.ts - which can turn export on through RSC_OUTPUT, or
8114
8161
  // compute it - or knowing where rsc-kit keeps things inside .output.
@@ -8122,6 +8169,20 @@ export function rscKit(options = {}) {
8122
8169
  ...compileStep(projectRoot),
8123
8170
  ...(buildCommands().length ? { commands: buildCommands().map((c) => c.name) } : {}),
8124
8171
  }, null, 2) + "\n");
8172
+ // The PPR contract's marker: the pages an edge may answer from a
8173
+ // cached shell, and the build both endpoints will say they belong to.
8174
+ // Written by every server build, with an empty list when there is no
8175
+ // PPR page, so a platform can tell "none" from "an rsc-kit too old to
8176
+ // say". Not by an export, which has no origin to resume against.
8177
+ const marker = join(dirname(clientOut), "ppr.json");
8178
+ if (output !== "export" && build) {
8179
+ writeFileSync(marker, JSON.stringify(pprMarker(build, shells.shells), null, 2) + "\n");
8180
+ }
8181
+ else {
8182
+ // One left by an earlier server build would promise an origin that
8183
+ // is not there.
8184
+ rmSync(marker, { force: true });
8185
+ }
8125
8186
  }
8126
8187
  },
8127
8188
  configResolved(config) {