@react-router/dev 0.0.0-experimental-8e9d8ef63 → 0.0.0-experimental-66d5af831

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/cli/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * @react-router/dev v0.0.0-experimental-8e9d8ef63
3
+ * @react-router/dev v0.0.0-experimental-66d5af831
4
4
  *
5
5
  * Copyright (c) Remix Software Inc.
6
6
  *
@@ -389,6 +389,7 @@ async function resolveConfig({
389
389
  );
390
390
  }
391
391
  let future = {
392
+ turboV3: reactRouterUserConfig.future?.turboV3 ?? false,
392
393
  unstable_optimizeDeps: reactRouterUserConfig.future?.unstable_optimizeDeps ?? false,
393
394
  unstable_splitRouteModules: reactRouterUserConfig.future?.unstable_splitRouteModules ?? false,
394
395
  unstable_viteEnvironmentApi: reactRouterUserConfig.future?.unstable_viteEnvironmentApi ?? false
@@ -1361,7 +1362,7 @@ async function getEnvironmentOptionsResolvers(ctx, buildManifest, viteCommand) {
1361
1362
  build: {
1362
1363
  outDir: getServerBuildDirectory(ctx),
1363
1364
  rollupOptions: {
1364
- input: viteUserConfig.build?.rollupOptions?.input ?? virtual.serverBuild.id
1365
+ input: (ctx.reactRouterConfig.future.unstable_viteEnvironmentApi ? viteUserConfig.environments?.ssr?.build?.rollupOptions?.input : viteUserConfig.build?.rollupOptions?.input) ?? virtual.serverBuild.id
1365
1366
  }
1366
1367
  }
1367
1368
  });
@@ -2058,7 +2059,7 @@ async function run2(argv = process.argv.slice(2)) {
2058
2059
  return;
2059
2060
  }
2060
2061
  if (flags.version) {
2061
- let version = require("../package.json").version;
2062
+ let version = require("../../package.json").version;
2062
2063
  console.log(version);
2063
2064
  return;
2064
2065
  }
package/dist/config.d.ts CHANGED
@@ -37,6 +37,7 @@ type ServerBundlesBuildManifest = BaseBuildManifest & {
37
37
  };
38
38
  type ServerModuleFormat = "esm" | "cjs";
39
39
  interface FutureConfig {
40
+ turboV3: boolean;
40
41
  unstable_optimizeDeps: boolean;
41
42
  /**
42
43
  * Automatically split route modules into multiple chunks when possible.
package/dist/config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-8e9d8ef63
2
+ * @react-router/dev v0.0.0-experimental-66d5af831
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
package/dist/routes.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-8e9d8ef63
2
+ * @react-router/dev v0.0.0-experimental-66d5af831
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -53,7 +53,7 @@ const enqueueUpdate = debounce(async () => {
53
53
  needsRevalidation,
54
54
  manifest.routes,
55
55
  window.__reactRouterRouteModules,
56
- window.__reactRouterContext.future,
56
+ window.__reactRouterContext.ssr,
57
57
  window.__reactRouterContext.isSpaMode
58
58
  );
59
59
  __reactRouterDataRouter._internalSetRoutes(routes);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-8e9d8ef63
2
+ * @react-router/dev v0.0.0-experimental-66d5af831
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -477,6 +477,7 @@ async function resolveConfig({
477
477
  );
478
478
  }
479
479
  let future = {
480
+ turboV3: reactRouterUserConfig.future?.turboV3 ?? false,
480
481
  unstable_optimizeDeps: reactRouterUserConfig.future?.unstable_optimizeDeps ?? false,
481
482
  unstable_splitRouteModules: reactRouterUserConfig.future?.unstable_splitRouteModules ?? false,
482
483
  unstable_viteEnvironmentApi: reactRouterUserConfig.future?.unstable_viteEnvironmentApi ?? false
@@ -678,11 +679,14 @@ var cloudflareDevProxyVitePlugin = (options = {}) => {
678
679
  }
679
680
  },
680
681
  configureServer: async (viteDevServer) => {
681
- let { getPlatformProxy } = await importWrangler();
682
- let { dispose, ...cloudflare } = await getPlatformProxy(
683
- restOptions
684
- );
685
- let context = { cloudflare };
682
+ let context;
683
+ let getContext = async () => {
684
+ let { getPlatformProxy } = await importWrangler();
685
+ let { dispose, ...cloudflare } = await getPlatformProxy(
686
+ restOptions
687
+ );
688
+ return { cloudflare };
689
+ };
686
690
  return () => {
687
691
  if (!viteDevServer.config.server.middlewareMode) {
688
692
  viteDevServer.middlewares.use(async (nodeReq, nodeRes, next) => {
@@ -692,6 +696,7 @@ var cloudflareDevProxyVitePlugin = (options = {}) => {
692
696
  );
693
697
  let handler = (0, import_react_router.createRequestHandler)(build, "development");
694
698
  let req = fromNodeRequest(nodeReq, nodeRes);
699
+ context ??= await getContext();
695
700
  let loadContext = getLoadContext ? await getLoadContext({ request: req, context }) : context;
696
701
  let res = await handler(req, loadContext);
697
702
  await toNodeRequest(res, nodeRes);
package/dist/vite.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-8e9d8ef63
2
+ * @react-router/dev v0.0.0-experimental-66d5af831
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -453,6 +453,7 @@ async function resolveConfig({
453
453
  );
454
454
  }
455
455
  let future = {
456
+ turboV3: reactRouterUserConfig.future?.turboV3 ?? false,
456
457
  unstable_optimizeDeps: reactRouterUserConfig.future?.unstable_optimizeDeps ?? false,
457
458
  unstable_splitRouteModules: reactRouterUserConfig.future?.unstable_splitRouteModules ?? false,
458
459
  unstable_viteEnvironmentApi: reactRouterUserConfig.future?.unstable_viteEnvironmentApi ?? false
@@ -1857,6 +1858,8 @@ var plugin = {
1857
1858
  return function Wrapped() {
1858
1859
  const props = {
1859
1860
  params: useParams(),
1861
+ loaderData: useLoaderData(),
1862
+ actionData: useActionData(),
1860
1863
  };
1861
1864
  return h(HydrateFallback, props);
1862
1865
  };
@@ -2232,6 +2235,11 @@ var reactRouterVitePlugin = () => {
2232
2235
  // Otherwise, all routes are imported as usual
2233
2236
  ctx.reactRouterConfig.routes
2234
2237
  );
2238
+ let prerenderPaths = await getPrerenderPaths(
2239
+ ctx.reactRouterConfig.prerender,
2240
+ ctx.reactRouterConfig.ssr,
2241
+ routes
2242
+ );
2235
2243
  return `
2236
2244
  import * as entryServer from ${JSON.stringify(
2237
2245
  resolveFileUrl(ctx, ctx.entryServerFilePath)
@@ -2258,6 +2266,7 @@ var reactRouterVitePlugin = () => {
2258
2266
  export const future = ${JSON.stringify(ctx.reactRouterConfig.future)};
2259
2267
  export const ssr = ${ctx.reactRouterConfig.ssr};
2260
2268
  export const isSpaMode = ${isSpaModeEnabled(ctx.reactRouterConfig)};
2269
+ export const prerender = ${JSON.stringify(prerenderPaths)};
2261
2270
  export const publicPath = ${JSON.stringify(ctx.publicPath)};
2262
2271
  export const entry = { module: entryServer };
2263
2272
  export const routes = {
@@ -2818,7 +2827,7 @@ var reactRouterVitePlugin = () => {
2818
2827
  ].join("\n")
2819
2828
  );
2820
2829
  }
2821
- if (ctx.reactRouterConfig.prerender != null && ctx.reactRouterConfig.prerender !== false) {
2830
+ if (isPrerenderingEnabled(ctx.reactRouterConfig)) {
2822
2831
  await handlePrerender(
2823
2832
  viteConfig,
2824
2833
  ctx.reactRouterConfig,
@@ -2992,6 +3001,15 @@ var reactRouterVitePlugin = () => {
2992
3001
  let reactRouterManifest = viteCommand === "build" ? (await generateReactRouterManifestsForBuild({
2993
3002
  routeIds
2994
3003
  })).reactRouterServerManifest : await getReactRouterManifestForDev();
3004
+ if (!ctx.reactRouterConfig.ssr) {
3005
+ invariant(viteConfig);
3006
+ validateSsrFalsePrerenderExports(
3007
+ viteConfig,
3008
+ ctx,
3009
+ reactRouterManifest,
3010
+ viteChildCompiler
3011
+ );
3012
+ }
2995
3013
  return `export default ${(0, import_jsesc.default)(reactRouterManifest, {
2996
3014
  es6: true
2997
3015
  })};`;
@@ -3092,12 +3110,15 @@ var reactRouterVitePlugin = () => {
3092
3110
  if (!route) return;
3093
3111
  if (!options?.ssr && isSpaModeEnabled(ctx.reactRouterConfig)) {
3094
3112
  let exportNames = getExportNames(code);
3095
- let serverOnlyExports = exportNames.filter(
3096
- (exp) => SERVER_ONLY_ROUTE_EXPORTS.includes(exp)
3097
- );
3113
+ let serverOnlyExports = exportNames.filter((exp) => {
3114
+ if (route.id === "root" && exp === "loader") {
3115
+ return false;
3116
+ }
3117
+ return SERVER_ONLY_ROUTE_EXPORTS.includes(exp);
3118
+ });
3098
3119
  if (serverOnlyExports.length > 0) {
3099
3120
  let str = serverOnlyExports.map((e) => `\`${e}\``).join(", ");
3100
- let message = `SPA Mode: ${serverOnlyExports.length} invalid route export(s) in \`${route.file}\`: ${str}. See https://remix.run/guides/spa-mode for more information.`;
3121
+ let message = `SPA Mode: ${serverOnlyExports.length} invalid route export(s) in \`${route.file}\`: ${str}. See https://reactrouter.com/how-to/spa for more information.`;
3101
3122
  throw Error(message);
3102
3123
  }
3103
3124
  if (route.id !== "root") {
@@ -3105,7 +3126,7 @@ var reactRouterVitePlugin = () => {
3105
3126
  (exp) => exp === "HydrateFallback"
3106
3127
  );
3107
3128
  if (hasHydrateFallback) {
3108
- let message = `SPA Mode: Invalid \`HydrateFallback\` export found in \`${route.file}\`. \`HydrateFallback\` is only permitted on the root route in SPA Mode. See https://remix.run/guides/spa-mode for more information.`;
3129
+ let message = `SPA Mode: Invalid \`HydrateFallback\` export found in \`${route.file}\`. \`HydrateFallback\` is only permitted on the root route in SPA Mode. See https://reactrouter.com/how-to/spa for more information.`;
3109
3130
  throw Error(message);
3110
3131
  }
3111
3132
  }
@@ -3398,8 +3419,11 @@ async function getRouteMetadata(cache, ctx, viteChildCompiler, route, readRouteF
3398
3419
  };
3399
3420
  return info;
3400
3421
  }
3422
+ function isPrerenderingEnabled(reactRouterConfig) {
3423
+ return reactRouterConfig.prerender != null && reactRouterConfig.prerender !== false;
3424
+ }
3401
3425
  function isSpaModeEnabled(reactRouterConfig) {
3402
- return reactRouterConfig.ssr === false && (reactRouterConfig.prerender == null || reactRouterConfig.prerender === false || Array.isArray(reactRouterConfig.prerender) && reactRouterConfig.prerender.length === 1 && reactRouterConfig.prerender[0] === "/");
3426
+ return reactRouterConfig.ssr === false && !isPrerenderingEnabled(reactRouterConfig);
3403
3427
  }
3404
3428
  async function getPrerenderBuildAndHandler(viteConfig, serverBuildDirectory, serverBuildFile) {
3405
3429
  let serverBuildPath = path6.join(serverBuildDirectory, serverBuildFile);
@@ -3411,20 +3435,49 @@ async function getPrerenderBuildAndHandler(viteConfig, serverBuildDirectory, ser
3411
3435
  };
3412
3436
  }
3413
3437
  async function handleSpaMode(viteConfig, reactRouterConfig, serverBuildDirectory, serverBuildFile, clientBuildDirectory) {
3414
- let { handler } = await getPrerenderBuildAndHandler(
3438
+ let { build, handler } = await getPrerenderBuildAndHandler(
3415
3439
  viteConfig,
3416
3440
  serverBuildDirectory,
3417
3441
  serverBuildFile
3418
3442
  );
3419
- let request = new Request(`http://localhost${reactRouterConfig.basename}`);
3443
+ let request = new Request(`http://localhost${reactRouterConfig.basename}`, {
3444
+ headers: {
3445
+ // Enable SPA mode in the server runtime and only render down to the root
3446
+ "X-React-Router-SPA-Mode": "yes"
3447
+ }
3448
+ });
3420
3449
  let response = await handler(request);
3421
3450
  let html = await response.text();
3422
- validatePrerenderedResponse(response, html, "SPA Mode", "/");
3423
- validatePrerenderedHtml(html, "SPA Mode");
3424
- await fse.writeFile(path6.join(clientBuildDirectory, "index.html"), html);
3425
- viteConfig.logger.info(
3426
- "SPA Mode: index.html has been written to your " + import_picocolors3.default.bold(path6.relative(process.cwd(), clientBuildDirectory)) + " directory"
3427
- );
3451
+ let isPrerenderSpaFallback = build.prerender.includes("/");
3452
+ let filename3 = isPrerenderSpaFallback ? "__spa-fallback.html" : "index.html";
3453
+ if (response.status !== 200) {
3454
+ if (isPrerenderSpaFallback) {
3455
+ throw new Error(
3456
+ `Prerender: Received a ${response.status} status code from \`entry.server.tsx\` while prerendering your \`${filename3}\` file.
3457
+ ` + html
3458
+ );
3459
+ } else {
3460
+ throw new Error(
3461
+ `SPA Mode: Received a ${response.status} status code from \`entry.server.tsx\` while prerendering your \`${filename3}\` file.
3462
+ ` + html
3463
+ );
3464
+ }
3465
+ }
3466
+ if (!html.includes("window.__reactRouterContext =") || !html.includes("window.__reactRouterRouteModules =")) {
3467
+ throw new Error(
3468
+ "SPA Mode: Did you forget to include `<Scripts/>` in your root route? Your pre-rendered HTML cannot hydrate without `<Scripts />`."
3469
+ );
3470
+ }
3471
+ await fse.writeFile(path6.join(clientBuildDirectory, filename3), html);
3472
+ let prettyDir = path6.relative(process.cwd(), clientBuildDirectory);
3473
+ let prettyPath = path6.join(prettyDir, filename3);
3474
+ if (build.prerender.length > 0) {
3475
+ viteConfig.logger.info(
3476
+ `Prerender (html): SPA Fallback -> ${import_picocolors3.default.bold(prettyPath)}`
3477
+ );
3478
+ } else {
3479
+ viteConfig.logger.info(`SPA Mode: Generated ${import_picocolors3.default.bold(prettyPath)}`);
3480
+ }
3428
3481
  }
3429
3482
  async function handlePrerender(viteConfig, reactRouterConfig, serverBuildDirectory, serverBuildPath, clientBuildDirectory) {
3430
3483
  let { build, handler } = await getPrerenderBuildAndHandler(
@@ -3433,53 +3486,62 @@ async function handlePrerender(viteConfig, reactRouterConfig, serverBuildDirecto
3433
3486
  serverBuildPath
3434
3487
  );
3435
3488
  let routes = createPrerenderRoutes(build.routes);
3436
- let routesToPrerender;
3437
- if (typeof reactRouterConfig.prerender === "boolean") {
3438
- invariant(reactRouterConfig.prerender, "Expected prerender:true");
3439
- routesToPrerender = determineStaticPrerenderRoutes(
3440
- routes,
3441
- viteConfig,
3442
- true
3443
- );
3444
- } else if (typeof reactRouterConfig.prerender === "function") {
3445
- routesToPrerender = await reactRouterConfig.prerender({
3446
- getStaticPaths: () => determineStaticPrerenderRoutes(routes, viteConfig, false)
3447
- });
3448
- } else {
3449
- routesToPrerender = reactRouterConfig.prerender || ["/"];
3450
- }
3451
3489
  let headers = {
3452
3490
  // Header that can be used in the loader to know if you're running at
3453
3491
  // build time or runtime
3454
3492
  "X-React-Router-Prerender": "yes"
3455
3493
  };
3456
- for (let path7 of routesToPrerender) {
3494
+ for (let path7 of build.prerender) {
3457
3495
  let matches = (0, import_react_router2.matchRoutes)(routes, `/${path7}/`.replace(/^\/\/+/, "/"));
3458
- let hasLoaders = matches?.some((m) => m.route.loader);
3459
- let data;
3460
- if (hasLoaders) {
3461
- data = await prerenderData(
3462
- handler,
3463
- path7,
3464
- clientBuildDirectory,
3465
- reactRouterConfig,
3466
- viteConfig,
3467
- { headers }
3468
- );
3469
- }
3496
+ invariant(
3497
+ matches,
3498
+ `Unable to prerender path because it does not match any routes: ${path7}`
3499
+ );
3470
3500
  let leafRoute = matches ? matches[matches.length - 1].route : null;
3471
3501
  let manifestRoute = leafRoute ? build.routes[leafRoute.id]?.module : null;
3472
- let isResourceRoute = manifestRoute && !manifestRoute.default && !manifestRoute.ErrorBoundary && manifestRoute.loader;
3502
+ let isResourceRoute = manifestRoute && !manifestRoute.default && !manifestRoute.ErrorBoundary;
3473
3503
  if (isResourceRoute) {
3474
- await prerenderResourceRoute(
3475
- handler,
3476
- path7,
3477
- clientBuildDirectory,
3478
- reactRouterConfig,
3479
- viteConfig,
3480
- { headers }
3481
- );
3504
+ invariant(leafRoute);
3505
+ invariant(manifestRoute);
3506
+ if (manifestRoute.loader) {
3507
+ await prerenderData(
3508
+ handler,
3509
+ path7,
3510
+ [leafRoute.id],
3511
+ clientBuildDirectory,
3512
+ reactRouterConfig,
3513
+ viteConfig,
3514
+ { headers }
3515
+ );
3516
+ await prerenderResourceRoute(
3517
+ handler,
3518
+ path7,
3519
+ clientBuildDirectory,
3520
+ reactRouterConfig,
3521
+ viteConfig,
3522
+ { headers }
3523
+ );
3524
+ } else {
3525
+ viteConfig.logger.warn(
3526
+ `\u26A0\uFE0F Skipping prerendering for resource route without a loader: ${leafRoute?.id}`
3527
+ );
3528
+ }
3482
3529
  } else {
3530
+ let hasLoaders = matches.some(
3531
+ (m) => build.assets.routes[m.route.id]?.hasLoader
3532
+ );
3533
+ let data;
3534
+ if (!isResourceRoute && hasLoaders) {
3535
+ data = await prerenderData(
3536
+ handler,
3537
+ path7,
3538
+ null,
3539
+ clientBuildDirectory,
3540
+ reactRouterConfig,
3541
+ viteConfig,
3542
+ { headers }
3543
+ );
3544
+ }
3483
3545
  await prerenderRoute(
3484
3546
  handler,
3485
3547
  path7,
@@ -3496,7 +3558,7 @@ async function handlePrerender(viteConfig, reactRouterConfig, serverBuildDirecto
3496
3558
  }
3497
3559
  }
3498
3560
  }
3499
- function determineStaticPrerenderRoutes(routes, viteConfig, isBooleanUsage = false) {
3561
+ function getStaticPrerenderPaths(routes) {
3500
3562
  let paths = ["/"];
3501
3563
  let paramRoutes = [];
3502
3564
  function recurse(subtree, prefix = "") {
@@ -3516,28 +3578,33 @@ function determineStaticPrerenderRoutes(routes, viteConfig, isBooleanUsage = fal
3516
3578
  }
3517
3579
  }
3518
3580
  recurse(routes);
3519
- if (isBooleanUsage && paramRoutes.length > 0) {
3520
- viteConfig.logger.warn(
3521
- [
3522
- "\u26A0\uFE0F Paths with dynamic/splat params cannot be prerendered when using `prerender: true`.",
3523
- "You may want to use the `prerender()` API to prerender the following paths:",
3524
- ...paramRoutes.map((p) => " - " + p)
3525
- ].join("\n")
3526
- );
3527
- }
3528
- return paths.map((p) => p.replace(/\/\/+/g, "/").replace(/(.+)\/$/, "$1"));
3581
+ return {
3582
+ paths: paths.map((p) => p.replace(/\/\/+/g, "/").replace(/(.+)\/$/, "$1")),
3583
+ paramRoutes
3584
+ };
3529
3585
  }
3530
- async function prerenderData(handler, prerenderPath, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
3586
+ async function prerenderData(handler, prerenderPath, onlyRoutes, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
3531
3587
  let normalizedPath = `${reactRouterConfig.basename}${prerenderPath === "/" ? "/_root.data" : `${prerenderPath.replace(/\/$/, "")}.data`}`.replace(/\/\/+/g, "/");
3532
- let request = new Request(`http://localhost${normalizedPath}`, requestInit);
3588
+ let url2 = new URL(`http://localhost${normalizedPath}`);
3589
+ if (onlyRoutes?.length) {
3590
+ url2.searchParams.set("_routes", onlyRoutes.join(","));
3591
+ }
3592
+ let request = new Request(url2, requestInit);
3533
3593
  let response = await handler(request);
3534
3594
  let data = await response.text();
3535
- validatePrerenderedResponse(response, data, "Prerender", normalizedPath);
3595
+ if (response.status !== 200) {
3596
+ throw new Error(
3597
+ `Prerender (data): Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${path6}\` path.
3598
+ ${normalizedPath}`
3599
+ );
3600
+ }
3536
3601
  let outdir = path6.relative(process.cwd(), clientBuildDirectory);
3537
3602
  let outfile = path6.join(outdir, ...normalizedPath.split("/"));
3538
3603
  await fse.ensureDir(path6.dirname(outfile));
3539
3604
  await fse.outputFile(outfile, data);
3540
- viteConfig.logger.info(`Prerender: Generated ${import_picocolors3.default.bold(outfile)}`);
3605
+ viteConfig.logger.info(
3606
+ `Prerender (data): ${prerenderPath} -> ${import_picocolors3.default.bold(outfile)}`
3607
+ );
3541
3608
  return data;
3542
3609
  }
3543
3610
  async function prerenderRoute(handler, prerenderPath, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
@@ -3548,42 +3615,65 @@ async function prerenderRoute(handler, prerenderPath, clientBuildDirectory, reac
3548
3615
  let request = new Request(`http://localhost${normalizedPath}`, requestInit);
3549
3616
  let response = await handler(request);
3550
3617
  let html = await response.text();
3551
- validatePrerenderedResponse(response, html, "Prerender", normalizedPath);
3552
- if (!reactRouterConfig.ssr) {
3553
- validatePrerenderedHtml(html, "Prerender");
3618
+ if (response.status !== 200) {
3619
+ throw new Error(
3620
+ `Prerender (html): Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${normalizedPath}\` path.
3621
+ ${html}`
3622
+ );
3554
3623
  }
3555
3624
  let outdir = path6.relative(process.cwd(), clientBuildDirectory);
3556
3625
  let outfile = path6.join(outdir, ...normalizedPath.split("/"), "index.html");
3557
3626
  await fse.ensureDir(path6.dirname(outfile));
3558
3627
  await fse.outputFile(outfile, html);
3559
- viteConfig.logger.info(`Prerender: Generated ${import_picocolors3.default.bold(outfile)}`);
3628
+ viteConfig.logger.info(
3629
+ `Prerender (html): ${prerenderPath} -> ${import_picocolors3.default.bold(outfile)}`
3630
+ );
3560
3631
  }
3561
3632
  async function prerenderResourceRoute(handler, prerenderPath, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
3562
3633
  let normalizedPath = `${reactRouterConfig.basename}${prerenderPath}/`.replace(/\/\/+/g, "/").replace(/\/$/g, "");
3563
3634
  let request = new Request(`http://localhost${normalizedPath}`, requestInit);
3564
3635
  let response = await handler(request);
3565
3636
  let text = await response.text();
3566
- validatePrerenderedResponse(response, text, "Prerender", normalizedPath);
3567
- let outdir = path6.relative(process.cwd(), clientBuildDirectory);
3568
- let outfile = path6.join(outdir, ...normalizedPath.split("/"));
3569
- await fse.ensureDir(path6.dirname(outfile));
3570
- await fse.outputFile(outfile, text);
3571
- viteConfig.logger.info(`Prerender: Generated ${import_picocolors3.default.bold(outfile)}`);
3572
- }
3573
- function validatePrerenderedResponse(response, html, prefix, path7) {
3574
3637
  if (response.status !== 200) {
3575
3638
  throw new Error(
3576
- `${prefix}: Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${path7}\` path.
3577
- ${html}`
3639
+ `Prerender (resource): Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${normalizedPath}\` path.
3640
+ ${text}`
3578
3641
  );
3579
3642
  }
3643
+ let outdir = path6.relative(process.cwd(), clientBuildDirectory);
3644
+ let outfile = path6.join(outdir, ...normalizedPath.split("/"));
3645
+ await fse.ensureDir(path6.dirname(outfile));
3646
+ await fse.outputFile(outfile, text);
3647
+ viteConfig.logger.info(
3648
+ `Prerender (resource): ${prerenderPath} -> ${import_picocolors3.default.bold(outfile)}`
3649
+ );
3580
3650
  }
3581
- function validatePrerenderedHtml(html, prefix) {
3582
- if (!html.includes("window.__reactRouterContext =") || !html.includes("window.__reactRouterRouteModules =")) {
3583
- throw new Error(
3584
- `${prefix}: Did you forget to include <Scripts/> in your root route? Your pre-rendered HTML files cannot hydrate without \`<Scripts />\`.`
3585
- );
3651
+ async function getPrerenderPaths(prerender, ssr, routes, logWarning = false) {
3652
+ let prerenderPaths = [];
3653
+ if (prerender != null && prerender !== false) {
3654
+ let prerenderRoutes = createPrerenderRoutes(routes);
3655
+ if (prerender === true) {
3656
+ let { paths, paramRoutes } = getStaticPrerenderPaths(prerenderRoutes);
3657
+ if (logWarning && !ssr && paramRoutes.length > 0) {
3658
+ console.warn(
3659
+ import_picocolors3.default.yellow(
3660
+ [
3661
+ "\u26A0\uFE0F Paths with dynamic/splat params cannot be prerendered when using `prerender: true`. You may want to use the `prerender()` API to prerender the following paths:",
3662
+ ...paramRoutes.map((p) => " - " + p)
3663
+ ].join("\n")
3664
+ )
3665
+ );
3666
+ }
3667
+ prerenderPaths = paths;
3668
+ } else if (typeof prerender === "function") {
3669
+ prerenderPaths = await prerender({
3670
+ getStaticPaths: () => getStaticPrerenderPaths(prerenderRoutes).paths
3671
+ });
3672
+ } else {
3673
+ prerenderPaths = prerender || ["/"];
3674
+ }
3586
3675
  }
3676
+ return prerenderPaths;
3587
3677
  }
3588
3678
  function groupRoutesByParentId2(manifest) {
3589
3679
  let routes = {};
@@ -3601,24 +3691,73 @@ function groupRoutesByParentId2(manifest) {
3601
3691
  function createPrerenderRoutes(manifest, parentId = "", routesByParentId = groupRoutesByParentId2(manifest)) {
3602
3692
  return (routesByParentId[parentId] || []).map((route) => {
3603
3693
  let commonRoute = {
3604
- // Always include root due to default boundaries
3605
- hasErrorBoundary: route.id === "root" || route.module.ErrorBoundary != null,
3606
3694
  id: route.id,
3607
- path: route.path,
3608
- loader: route.module.loader ? () => null : void 0,
3609
- action: void 0,
3610
- handle: route.module.handle
3695
+ path: route.path
3611
3696
  };
3612
- return route.index ? {
3613
- index: true,
3614
- ...commonRoute
3615
- } : {
3616
- caseSensitive: route.caseSensitive,
3697
+ if (route.index) {
3698
+ return {
3699
+ index: true,
3700
+ ...commonRoute
3701
+ };
3702
+ }
3703
+ return {
3617
3704
  children: createPrerenderRoutes(manifest, route.id, routesByParentId),
3618
3705
  ...commonRoute
3619
3706
  };
3620
3707
  });
3621
3708
  }
3709
+ async function validateSsrFalsePrerenderExports(viteConfig, ctx, manifest, viteChildCompiler) {
3710
+ let prerenderPaths = await getPrerenderPaths(
3711
+ ctx.reactRouterConfig.prerender,
3712
+ ctx.reactRouterConfig.ssr,
3713
+ manifest.routes,
3714
+ true
3715
+ );
3716
+ if (prerenderPaths.length === 0) {
3717
+ return;
3718
+ }
3719
+ let prerenderRoutes = createPrerenderRoutes(manifest.routes);
3720
+ let prerenderedRoutes = /* @__PURE__ */ new Set();
3721
+ for (let path7 of prerenderPaths) {
3722
+ let matches = (0, import_react_router2.matchRoutes)(
3723
+ prerenderRoutes,
3724
+ `/${path7}/`.replace(/^\/\/+/, "/")
3725
+ );
3726
+ invariant(
3727
+ matches,
3728
+ `Unable to prerender path because it does not match any routes: ${path7}`
3729
+ );
3730
+ matches.forEach((m) => prerenderedRoutes.add(m.route.id));
3731
+ }
3732
+ let errors = [];
3733
+ let routeExports = await getRouteManifestModuleExports(
3734
+ viteChildCompiler,
3735
+ ctx
3736
+ );
3737
+ for (let [routeId, route] of Object.entries(manifest.routes)) {
3738
+ let invalidApis = [];
3739
+ invariant(route, "Expected a route object in validateSsrFalseExports");
3740
+ let exports2 = routeExports[route.id];
3741
+ if (exports2.includes("headers")) invalidApis.push("headers");
3742
+ if (exports2.includes("action")) invalidApis.push("action");
3743
+ if (invalidApis.length > 0) {
3744
+ errors.push(
3745
+ `Prerender: ${invalidApis.length} invalid route export(s) in \`${route.id}\` when prerendering with \`ssr:false\`: ${invalidApis.join(", ")}. See https://reactrouter.com/how-to/pre-rendering for more information.`
3746
+ );
3747
+ }
3748
+ if (exports2.includes("loader") && !prerenderedRoutes.has(routeId)) {
3749
+ errors.push(
3750
+ `Prerender: 1 invalid route export in \`${route.id}\` when using \`ssr:false\` with \`prerender\` because the route is never prerendered so the loader will never be called. See https://reactrouter.com/how-to/pre-rendering for more information.`
3751
+ );
3752
+ }
3753
+ }
3754
+ if (errors.length > 0) {
3755
+ viteConfig.logger.error(import_picocolors3.default.red(errors.join("\n")));
3756
+ throw new Error(
3757
+ "Invalid route exports found when prerendering with `ssr:false`"
3758
+ );
3759
+ }
3760
+ }
3622
3761
  function getAddressableRoutes(routes) {
3623
3762
  let nonAddressableIds = /* @__PURE__ */ new Set();
3624
3763
  for (let id in routes) {
@@ -3961,7 +4100,7 @@ async function getEnvironmentOptionsResolvers(ctx, buildManifest, viteCommand) {
3961
4100
  build: {
3962
4101
  outDir: getServerBuildDirectory(ctx),
3963
4102
  rollupOptions: {
3964
- input: viteUserConfig.build?.rollupOptions?.input ?? virtual.serverBuild.id
4103
+ input: (ctx.reactRouterConfig.future.unstable_viteEnvironmentApi ? viteUserConfig.environments?.ssr?.build?.rollupOptions?.input : viteUserConfig.build?.rollupOptions?.input) ?? virtual.serverBuild.id
3965
4104
  }
3966
4105
  }
3967
4106
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-router/dev",
3
- "version": "0.0.0-experimental-8e9d8ef63",
3
+ "version": "0.0.0-experimental-66d5af831",
4
4
  "description": "Dev tools and CLI for React Router",
5
5
  "homepage": "https://reactrouter.com",
6
6
  "bugs": {
@@ -88,7 +88,7 @@
88
88
  "set-cookie-parser": "^2.6.0",
89
89
  "valibot": "^0.41.0",
90
90
  "vite-node": "3.0.0-beta.2",
91
- "@react-router/node": "0.0.0-experimental-8e9d8ef63"
91
+ "@react-router/node": "0.0.0-experimental-66d5af831"
92
92
  },
93
93
  "devDependencies": {
94
94
  "@types/babel__core": "^7.20.5",
@@ -117,15 +117,15 @@
117
117
  "vite": "^6.0.0",
118
118
  "wireit": "0.14.9",
119
119
  "wrangler": "^3.28.2",
120
- "react-router": "^0.0.0-experimental-8e9d8ef63",
121
- "@react-router/serve": "0.0.0-experimental-8e9d8ef63"
120
+ "react-router": "^0.0.0-experimental-66d5af831",
121
+ "@react-router/serve": "0.0.0-experimental-66d5af831"
122
122
  },
123
123
  "peerDependencies": {
124
124
  "typescript": "^5.1.0",
125
125
  "vite": "^5.1.0 || ^6.0.0",
126
126
  "wrangler": "^3.28.2",
127
- "@react-router/serve": "^0.0.0-experimental-8e9d8ef63",
128
- "react-router": "^0.0.0-experimental-8e9d8ef63"
127
+ "@react-router/serve": "^0.0.0-experimental-66d5af831",
128
+ "react-router": "^0.0.0-experimental-66d5af831"
129
129
  },
130
130
  "peerDependenciesMeta": {
131
131
  "@react-router/serve": {