@rsc-kit/core 0.29.3 → 0.29.5

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.js CHANGED
@@ -1705,7 +1705,11 @@ async function prerenderAfterBundles(bundle, staticDir, assetsDir, knownActions
1705
1705
  const writesSitemap = manifest.apis?.some((api) => api.name === "app/sitemap.xml/route");
1706
1706
  const hasSitemapFile = rootFiles.includes("sitemap.xml");
1707
1707
  if (!writesSitemap && !hasSitemapFile) {
1708
- const base = rootMetadataBase(join(sourceDir, "app"));
1708
+ // The source first, then the app itself: a metadataBase computed from
1709
+ // the environment - new URL(process.env.SITE_URL) - is not a literal the
1710
+ // source can be read for, but the bundle is loaded by now and knows it.
1711
+ const base = rootMetadataBase(join(sourceDir, "app")) ??
1712
+ (await loadedMetadataBase(engine, manifest));
1709
1713
  if (base) {
1710
1714
  const xml = automaticSitemap(results, manifest.routes, base);
1711
1715
  const stored = JSON.stringify({
@@ -1728,7 +1732,9 @@ async function prerenderAfterBundles(bundle, staticDir, assetsDir, knownActions
1728
1732
  line: " - /sitemap.xml",
1729
1733
  bytes: null,
1730
1734
  extra: [
1731
- " not written: the root layout has no metadataBase to make the urls absolute",
1735
+ rootMentionsMetadataBase(join(sourceDir, "app"))
1736
+ ? " not written: the root layout's metadataBase gave no url at build time - is the variable it reads set for the build?"
1737
+ : " not written: the root layout has no metadataBase to make the urls absolute",
1732
1738
  ],
1733
1739
  });
1734
1740
  }
@@ -1830,8 +1836,6 @@ ${legend(counted)}
1830
1836
  "reachable from the build. Give it that, or mark the read with `await connection()`\n" +
1831
1837
  "so the page renders per request and the rest of it is still stored.");
1832
1838
  }
1833
- if (output === "export")
1834
- await exportAfterPrerender(results, staticDir, assetsDir);
1835
1839
  // The urls whose answer cannot change until the next build. The service
1836
1840
  // worker serves these from its cache first rather than asking the network
1837
1841
  // and falling back — see writeServiceWorker.
@@ -1916,20 +1920,41 @@ function weighClientJs(assetsDir) {
1916
1920
  * page that loads and then stays empty forever.
1917
1921
  */
1918
1922
  async function exportAfterPrerender(results, staticDir, assetsDir) {
1919
- const [{ exportSite, NotExportable }, { writeTo, prerenderedFrom, copyAssets },] = await Promise.all([import("./export.js"), import("./files.js")]);
1923
+ const [{ exportSite, NotExportable }, { writeTo, prerenderedFrom, copyPublic, exportStoredAnswers },] = await Promise.all([import("./export.js"), import("./files.js")]);
1920
1924
  try {
1921
1925
  const { pages, refused } = await exportSite({
1922
1926
  results,
1923
1927
  read: prerenderedFrom(staticDir),
1924
1928
  write: writeTo(exportPath),
1925
1929
  manifest: routeManifest(),
1926
- assets: copyAssets(join(assetsDir, "assets"), exportPath, "/assets/"),
1930
+ // Every file a server would have answered as a file: the app's own
1931
+ // public/ - which Nitro copies itself, so it is not in the client
1932
+ // output - and then the client output, not only assets/: the icons and
1933
+ // share image, the web manifest, the service worker.
1934
+ assets: async () => {
1935
+ const own = join(projectRoot, "public");
1936
+ if (existsSync(own))
1937
+ await copyPublic(own, exportPath)();
1938
+ await copyPublic(assetsDir, exportPath)();
1939
+ },
1927
1940
  force: process.env.RSC_EXPORT_FORCE === "1",
1928
1941
  });
1929
1942
  console.log(`\n Exported ${pages} page${pages === 1 ? "" : "s"} to ${relative(projectRoot, exportPath)}`);
1930
1943
  if (refused.length > 0) {
1931
1944
  console.log(` Left out ${refused.length}: ${refused.map((r) => r.url).join(", ")}`);
1932
1945
  }
1946
+ // Stored route.ts answers - robots.txt, sitemap.xml, a JSON feed - as
1947
+ // the files they are. A static host types a file by its extension, so an
1948
+ // answer at a url without one cannot keep its content type: said, not
1949
+ // written as something it was not.
1950
+ const answers = await exportStoredAnswers(staticDir, exportPath);
1951
+ if (answers.written.length > 0) {
1952
+ console.log(` Exported ${answers.written.length} route answer${answers.written.length === 1 ? "" : "s"}: ${answers.written.join(", ")}`);
1953
+ }
1954
+ if (answers.skipped.length > 0) {
1955
+ console.log(` Left out ${answers.skipped.length} route answer${answers.skipped.length === 1 ? "" : "s"}: ` +
1956
+ answers.skipped.map((s) => `${s.url} (${s.why})`).join(", "));
1957
+ }
1933
1958
  }
1934
1959
  catch (error) {
1935
1960
  if (!(error instanceof NotExportable))
@@ -2540,6 +2565,28 @@ function discover(dir) {
2540
2565
  * generateStaticParams is detected by reading. A `new URL('…')` or a string
2541
2566
  * literal; anything computed is not seen, and rscKit({ hosts }) says it.
2542
2567
  */
2568
+ /** The root layout's metadataBase as the loaded app has it, whatever it was computed from. */
2569
+ async function loadedMetadataBase(engine, manifest) {
2570
+ const resolve = engine
2571
+ .resolveMetadata;
2572
+ const root = manifest.routes.find((r) => r.layouts?.length)?.layouts?.[0];
2573
+ if (!resolve || !root)
2574
+ return null;
2575
+ try {
2576
+ const base = (await resolve(root, {}, []))?.metadataBase;
2577
+ return base ? String(base) : null;
2578
+ }
2579
+ catch {
2580
+ return null;
2581
+ }
2582
+ }
2583
+ /** Whether the root layout sets a metadataBase at all, literal or not. */
2584
+ function rootMentionsMetadataBase(appDir) {
2585
+ const layout = ["layout.tsx", "layout.jsx", "layout.ts", "layout.js"]
2586
+ .map((name) => join(appDir, name))
2587
+ .find((file) => existsSync(file));
2588
+ return layout ? /\bmetadataBase\b/.test(readFileSync(layout, "utf-8")) : false;
2589
+ }
2543
2590
  function rootMetadataBase(appDir) {
2544
2591
  const layout = ["layout.tsx", "layout.jsx", "layout.ts", "layout.js"]
2545
2592
  .map((name) => join(appDir, name))
@@ -5029,7 +5076,7 @@ export async function handleRsc(
5029
5076
  * export. Parsed by splitting rather than matching, because this function is
5030
5077
  * emitted into a template literal where a regex would need double escaping.
5031
5078
  */
5032
- function clientReferenceNames(payload: string): string[] {
5079
+ export function clientReferenceNames(payload: string): string[] {
5033
5080
  const names = new Set<string>()
5034
5081
 
5035
5082
  // A string React has already sent is referenced by row - "$1" for the
@@ -5038,8 +5085,12 @@ function clientReferenceNames(payload: string): string[] {
5038
5085
  // way and has to be looked up, or the refusal names a component "$1".
5039
5086
  const strings = new Map<string, string>()
5040
5087
 
5088
+ // Row ids are hexadecimal: row 26 is "1a", referenced as "$1a". Matched
5089
+ // as decimal, every reference past row 9 stayed unresolved, read as an
5090
+ // app's client component, and a page with nothing interactive on it
5091
+ // shipped the whole runtime.
5041
5092
  for (const line of payload.split('\\n')) {
5042
- const match = /^(\\d+):"(.*)"$/.exec(line)
5093
+ const match = /^([0-9a-f]+):"(.*)"$/.exec(line)
5043
5094
  if (match) strings.set('$' + match[1], match[2]!)
5044
5095
  }
5045
5096
 
@@ -5058,7 +5109,9 @@ function clientReferenceNames(payload: string): string[] {
5058
5109
  * A page with one needs the runtime to submit it.
5059
5110
  */
5060
5111
  export function hasServerReference(payload: string): boolean {
5061
- return /^\\d+:\\{"id":"[^"]+","bound":/m.test(payload)
5112
+ // Hexadecimal row ids, as above: a server reference in row 1a was missed,
5113
+ // and its page stored without the runtime its form needs.
5114
+ return /^[0-9a-f]+:\\{"id":"[^"]+","bound":/m.test(payload)
5062
5115
  }
5063
5116
 
5064
5117
  // Flight payload only (worker: rsc-payload — build-time).
@@ -7290,6 +7343,11 @@ export function rscKit(options = {}) {
7290
7343
  if (offline) {
7291
7344
  writeServiceWorker(clientOut ?? publicAssetsDir, frozen, offlineFallback(frozen, results), staticDir);
7292
7345
  }
7346
+ // Last, once the client output is complete: the icons, the share
7347
+ // image, the web manifest and the service worker are written above,
7348
+ // after the prerender, and an export taken any earlier left them out.
7349
+ if (output === "export")
7350
+ await exportAfterPrerender(results, staticDir, clientOut ?? publicAssetsDir);
7293
7351
  },
7294
7352
  configResolved(config) {
7295
7353
  // A build that is not a production build cannot work here, and the