@react-router/dev 0.0.0-experimental-fbbd4fd81 → 0.0.0-experimental-27337b0d6

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.
Files changed (38) hide show
  1. package/dist/cli/commands.js +2 -2
  2. package/dist/cli/detectPackageManager.js +1 -1
  3. package/dist/cli/index.js +1 -1
  4. package/dist/cli/run.js +1 -1
  5. package/dist/cli/useJavascript.js +1 -1
  6. package/dist/cli.js +1 -1
  7. package/dist/colors.js +1 -1
  8. package/dist/config/defaults/entry.client.rsc.tsx +90 -0
  9. package/dist/config/defaults/entry.react-server.node.tsx +15 -0
  10. package/dist/config/defaults/entry.react-server.web.tsx +9 -0
  11. package/dist/config/defaults/entry.server.node.rsc.tsx +164 -0
  12. package/dist/config/defaults/entry.server.spa.tsx +63 -10
  13. package/dist/config/findConfig.js +1 -1
  14. package/dist/config/flatRoutes.js +1 -1
  15. package/dist/config/format.js +1 -1
  16. package/dist/config/routes.js +1 -1
  17. package/dist/config.d.ts +15 -1
  18. package/dist/config.js +35 -9
  19. package/dist/index.js +1 -1
  20. package/dist/invariant.js +1 -1
  21. package/dist/runtime.client.d.ts +1 -0
  22. package/dist/runtime.client.js +19 -0
  23. package/dist/vite/babel.js +1 -1
  24. package/dist/vite/build.js +15 -2
  25. package/dist/vite/cloudflare-proxy-plugin.js +1 -1
  26. package/dist/vite/dev.js +1 -1
  27. package/dist/vite/import-vite-esm-sync.js +1 -1
  28. package/dist/vite/index.js +1 -1
  29. package/dist/vite/node-adapter.js +1 -1
  30. package/dist/vite/plugin.d.ts +9 -0
  31. package/dist/vite/plugin.js +347 -40
  32. package/dist/vite/profiler.js +1 -1
  33. package/dist/vite/remove-exports.d.ts +3 -1
  34. package/dist/vite/remove-exports.js +18 -2
  35. package/dist/vite/resolve-file-url.js +1 -1
  36. package/dist/vite/styles.js +1 -1
  37. package/dist/vite/vmod.js +1 -1
  38. package/package.json +13 -7
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-fbbd4fd81
2
+ * @react-router/dev v0.0.0-experimental-27337b0d6
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -227,7 +227,16 @@ async function build(root, {
227
227
  });
228
228
  }
229
229
  await cleanBuildDirectory(viteConfig, ctx);
230
- // Run the Vite client build first
230
+ // Then run React server build first
231
+ if (reactRouterConfig.future.unstable_serverComponents) {
232
+ // TODO: This will be handled by the vite env API in the future
233
+ process.env.REACT_SERVER_BUILD = "1";
234
+ await viteBuild({
235
+ ssr: true
236
+ });
237
+ }
238
+ process.env.REACT_SERVER_BUILD = "";
239
+ // Run the Vite client build second
231
240
  await viteBuild({
232
241
  ssr: false
233
242
  });
@@ -267,6 +276,10 @@ async function build(root, {
267
276
  reactRouterConfig,
268
277
  viteConfig
269
278
  }));
279
+ const {
280
+ serverModules
281
+ } = plugin.getReactServerOptions();
282
+ invariant["default"](!serverModules.size, "`use server` is not yet supported.");
270
283
  }
271
284
 
272
285
  exports.build = build;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-fbbd4fd81
2
+ * @react-router/dev v0.0.0-experimental-27337b0d6
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
package/dist/vite/dev.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-fbbd4fd81
2
+ * @react-router/dev v0.0.0-experimental-27337b0d6
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-fbbd4fd81
2
+ * @react-router/dev v0.0.0-experimental-27337b0d6
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-fbbd4fd81
2
+ * @react-router/dev v0.0.0-experimental-27337b0d6
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-fbbd4fd81
2
+ * @react-router/dev v0.0.0-experimental-27337b0d6
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -59,6 +59,7 @@ type ReactRouterPluginSsrBuildContext = {
59
59
  export type ReactRouterPluginContext = ReactRouterPluginSsrBuildContext & {
60
60
  rootDirectory: string;
61
61
  entryClientFilePath: string;
62
+ entryReactServerFilePath?: string;
62
63
  entryServerFilePath: string;
63
64
  reactRouterConfig: ResolvedVitePluginConfig;
64
65
  viteManifestEnabled: boolean;
@@ -68,4 +69,12 @@ type MaybePromise<T> = T | Promise<T>;
68
69
  export declare let setReactRouterDevLoadContext: (loadContext: (request: Request) => MaybePromise<Record<string, unknown>>) => void;
69
70
  export type ReactRouterVitePlugin = (config?: VitePluginConfig) => Vite.Plugin[];
70
71
  export declare const reactRouterVitePlugin: ReactRouterVitePlugin;
72
+ declare global {
73
+ var __clientModules: Set<string>;
74
+ var __serverModules: Set<string>;
75
+ }
76
+ export declare function getReactServerOptions(): {
77
+ clientModules: Set<string>;
78
+ serverModules: Set<string>;
79
+ };
71
80
  export {};
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-fbbd4fd81
2
+ * @react-router/dev v0.0.0-experimental-27337b0d6
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -18,9 +18,11 @@ var url = require('node:url');
18
18
  var fse = require('fs-extra');
19
19
  var babel = require('@babel/core');
20
20
  var serverRuntime = require('@react-router/server-runtime');
21
+ var reactRouter = require('react-router');
21
22
  var esModuleLexer = require('es-module-lexer');
22
23
  var jsesc = require('jsesc');
23
24
  var colors = require('picocolors');
25
+ var unpluginRsc = require('unplugin-rsc');
24
26
  var findConfig = require('../config/findConfig.js');
25
27
  var invariant = require('../invariant.js');
26
28
  var nodeAdapter = require('./node-adapter.js');
@@ -107,21 +109,26 @@ async function loadPluginContext({
107
109
  }
108
110
  const SERVER_ONLY_ROUTE_EXPORTS = ["loader", "action", "headers"];
109
111
  const CLIENT_ROUTE_EXPORTS = ["clientAction", "clientLoader", "default", "ErrorBoundary", "handle", "HydrateFallback", "Layout", "links", "meta", "shouldRevalidate"];
110
- // The "=1" suffix ensures client route requests can be processed before hitting
111
- // the Vite plugin since "?client-route" can be serialized as "?client-route="
112
- const CLIENT_ROUTE_QUERY_STRING = "?client-route=1";
112
+ /** This is used to manage a build optimization to remove unused route exports
113
+ from the client build output. This is important in cases where custom route
114
+ exports are only ever used on the server. Without this optimization we can't
115
+ tree-shake any unused custom exports because routes are entry points. */
116
+ const BUILD_CLIENT_ROUTE_QUERY_STRING = "?__remix-build-client-route";
113
117
  let serverBuildId = vmod.id("server-build");
114
118
  let serverManifestId = vmod.id("server-manifest");
115
119
  let browserManifestId = vmod.id("browser-manifest");
116
120
  let hmrRuntimeId = vmod.id("hmr-runtime");
117
121
  let injectHmrRuntimeId = vmod.id("inject-hmr-runtime");
122
+ let reactServerBuildId = vmod.id("react-server-build");
123
+ let clientReferencesId = vmod.id("client-references");
124
+ let serverReferencesId = vmod.id("server-references");
118
125
  const resolveRelativeRouteFilePath = (route, reactRouterConfig) => {
119
126
  let vite = importViteEsmSync.importViteEsmSync();
120
127
  let file = route.file;
121
128
  let fullPath = path__namespace.resolve(reactRouterConfig.appDirectory, file);
122
129
  return vite.normalizePath(fullPath);
123
130
  };
124
- let vmods = [serverBuildId, serverManifestId, browserManifestId];
131
+ let vmods = [serverBuildId, serverManifestId, browserManifestId, reactServerBuildId, clientReferencesId, serverReferencesId];
125
132
  const invalidateVirtualModules = viteDevServer => {
126
133
  vmods.forEach(vmod$1 => {
127
134
  let mod = viteDevServer.moduleGraph.getModuleById(vmod.resolve(vmod$1));
@@ -134,13 +141,10 @@ const getHash = (source, maxLength) => {
134
141
  let hash = node_crypto.createHash("sha256").update(source).digest("hex");
135
142
  return typeof maxLength === "number" ? hash.slice(0, maxLength) : hash;
136
143
  };
137
- const isClientRoute = id => {
138
- return id.endsWith(CLIENT_ROUTE_QUERY_STRING);
139
- };
140
144
  const resolveChunk = (ctx, viteManifest, absoluteFilePath) => {
141
145
  let vite = importViteEsmSync.importViteEsmSync();
142
146
  let rootRelativeFilePath = vite.normalizePath(path__namespace.relative(ctx.rootDirectory, absoluteFilePath));
143
- let entryChunk = viteManifest[rootRelativeFilePath + CLIENT_ROUTE_QUERY_STRING] ?? viteManifest[rootRelativeFilePath];
147
+ let entryChunk = viteManifest[rootRelativeFilePath + BUILD_CLIENT_ROUTE_QUERY_STRING] ?? viteManifest[rootRelativeFilePath];
144
148
  if (!entryChunk) {
145
149
  let knownManifestKeys = Object.keys(viteManifest).map(key => '"' + key + '"').join(", ");
146
150
  throw new Error(`No manifest entry found for "${rootRelativeFilePath}". Known manifest keys: ${knownManifestKeys}`);
@@ -233,6 +237,7 @@ const getServerBundleBuildConfig = viteUserConfig => {
233
237
  return viteUserConfig.__reactRouterServerBundleBuildConfig;
234
238
  };
235
239
  let getServerBuildDirectory = ctx => path__namespace.join(ctx.reactRouterConfig.buildDirectory, "server", ...(ctx.serverBundleBuildConfig ? [ctx.serverBundleBuildConfig.serverBundleId] : []));
240
+ let getReactServerBuildDirectory = reactRouterConfig => path__namespace.join(reactRouterConfig.buildDirectory, "react-server");
236
241
  let getClientBuildDirectory = reactRouterConfig => path__namespace.join(reactRouterConfig.buildDirectory, "client");
237
242
  let defaultEntriesDir = path__namespace.resolve(__dirname, "..", "config", "defaults");
238
243
  let defaultEntries = fse__namespace.readdirSync(defaultEntriesDir).map(filename => path__namespace.join(defaultEntriesDir, filename));
@@ -280,6 +285,7 @@ const reactRouterVitePlugin = _config => {
280
285
  });
281
286
  let {
282
287
  entryClientFilePath,
288
+ entryReactServerFilePath,
283
289
  entryServerFilePath
284
290
  } = await config.resolveEntryFiles({
285
291
  rootDirectory,
@@ -297,6 +303,7 @@ const reactRouterVitePlugin = _config => {
297
303
  reactRouterConfig,
298
304
  rootDirectory,
299
305
  entryClientFilePath,
306
+ entryReactServerFilePath,
300
307
  entryServerFilePath,
301
308
  viteManifestEnabled,
302
309
  ...ssrBuildCtx
@@ -314,7 +321,7 @@ const reactRouterVitePlugin = _config => {
314
321
  ctx.serverBundleBuildConfig.routes :
315
322
  // Otherwise, all routes are imported as usual
316
323
  ctx.reactRouterConfig.routes;
317
- return `
324
+ let code = `
318
325
  import * as entryServer from ${JSON.stringify(resolveFileUrl.resolveFileUrl(ctx, ctx.entryServerFilePath))};
319
326
  ${Object.keys(routes).map((key, index) => {
320
327
  let route = routes[key];
@@ -324,7 +331,7 @@ const reactRouterVitePlugin = _config => {
324
331
  export const assetsBuildDirectory = ${JSON.stringify(path__namespace.relative(ctx.rootDirectory, getClientBuildDirectory(ctx.reactRouterConfig)))};
325
332
  export const basename = ${JSON.stringify(ctx.reactRouterConfig.basename)};
326
333
  export const future = ${JSON.stringify(ctx.reactRouterConfig.future)};
327
- export const isSpaMode = ${!ctx.reactRouterConfig.ssr};
334
+ export const isSpaMode = ${!ctx.reactRouterConfig.ssr && ctx.reactRouterConfig.prerender == null};
328
335
  export const publicPath = ${JSON.stringify(ctx.reactRouterConfig.publicPath)};
329
336
  export const entry = { module: entryServer };
330
337
  export const routes = {
@@ -340,6 +347,61 @@ const reactRouterVitePlugin = _config => {
340
347
  }`;
341
348
  }).join(",\n ")}
342
349
  };`;
350
+ if (ctx.reactRouterConfig.future.unstable_serverComponents) {
351
+ code += `
352
+ export { default as clientReferences } from ${JSON.stringify(clientReferencesId)};`;
353
+ }
354
+ return code;
355
+ };
356
+ let {
357
+ clientModules,
358
+ serverModules
359
+ } = getReactServerOptions();
360
+ let getReactServerEntry = () => {
361
+ invariant["default"](viteConfig, "viteconfig required to generate the react-server entry");
362
+ invariant["default"](ctx.entryReactServerFilePath, "entryReactServerFilePath required to generate the react-server entry");
363
+ let routes = ctx.serverBundleBuildConfig ?
364
+ // For server bundle builds, the server build should only import the
365
+ // routes for this bundle rather than importing all routes
366
+ ctx.serverBundleBuildConfig.routes :
367
+ // Otherwise, all routes are imported as usual
368
+ ctx.reactRouterConfig.routes;
369
+ return `
370
+ import * as entryServer from ${JSON.stringify(resolveFileUrl.resolveFileUrl(ctx, ctx.entryReactServerFilePath))};
371
+ ${Object.keys(routes).map((key, index) => {
372
+ let route = routes[key];
373
+ return `import * as route${index} from ${JSON.stringify(resolveFileUrl.resolveFileUrl(ctx, resolveRelativeRouteFilePath(route, ctx.reactRouterConfig)))};`;
374
+ }).join("\n")}
375
+ export const future = ${JSON.stringify(ctx.reactRouterConfig.future)};
376
+ export const basename = ${JSON.stringify(ctx.reactRouterConfig.basename)};
377
+ export const entry = { module: entryServer };
378
+ export const routes = {
379
+ ${Object.keys(routes).map((key, index) => {
380
+ let route = routes[key];
381
+ return `${JSON.stringify(key)}: {
382
+ id: ${JSON.stringify(route.id)},
383
+ parentId: ${JSON.stringify(route.parentId)},
384
+ path: ${JSON.stringify(route.path)},
385
+ index: ${JSON.stringify(route.index)},
386
+ caseSensitive: ${JSON.stringify(route.caseSensitive)},
387
+ module: route${index}
388
+ }`;
389
+ }).join(",\n ")}
390
+ };`;
391
+ };
392
+ let getClientReferencesEntry = () => {
393
+ let result = "export default {";
394
+ for (let clientModule of clientModules) {
395
+ result += `${JSON.stringify(prodHash(clientModule))}: () => import(${JSON.stringify(clientModule)}),`;
396
+ }
397
+ return `${result}};`;
398
+ };
399
+ let getServerReferencesEntry = () => {
400
+ let result = "export default {";
401
+ for (let serverModule of serverModules) {
402
+ result += `${JSON.stringify(prodHash(serverModule))}: () => import(${JSON.stringify(serverModule)}),`;
403
+ }
404
+ return `${result}\};`;
343
405
  };
344
406
  let loadViteManifest = async directory => {
345
407
  let manifestContents = await fse__namespace.readFile(path__namespace.resolve(directory, ".vite", "manifest.json"), "utf-8");
@@ -432,7 +494,7 @@ const reactRouterVitePlugin = _config => {
432
494
  path: route.path,
433
495
  index: route.index,
434
496
  caseSensitive: route.caseSensitive,
435
- module: path__namespace.posix.join(ctx.reactRouterConfig.publicPath, `${resolveFileUrl.resolveFileUrl(ctx, resolveRelativeRouteFilePath(route, ctx.reactRouterConfig))}${CLIENT_ROUTE_QUERY_STRING}`),
497
+ module: path__namespace.posix.join(ctx.reactRouterConfig.publicPath, `${resolveFileUrl.resolveFileUrl(ctx, resolveRelativeRouteFilePath(route, ctx.reactRouterConfig))}`),
436
498
  hasAction: sourceExports.includes("action"),
437
499
  hasLoader: sourceExports.includes("loader"),
438
500
  hasClientAction: sourceExports.includes("clientAction"),
@@ -538,9 +600,9 @@ const reactRouterVitePlugin = _config => {
538
600
  rollupOptions: {
539
601
  ...baseRollupOptions,
540
602
  preserveEntrySignatures: "exports-only",
541
- input: [ctx.entryClientFilePath, ...Object.values(ctx.reactRouterConfig.routes).map(route => `${path__namespace.resolve(ctx.reactRouterConfig.appDirectory, route.file)}${CLIENT_ROUTE_QUERY_STRING}`)]
603
+ input: [ctx.entryClientFilePath, ...Object.values(ctx.reactRouterConfig.routes).map(route => `${path__namespace.resolve(ctx.reactRouterConfig.appDirectory, route.file)}${BUILD_CLIENT_ROUTE_QUERY_STRING}`), ...(ctx.reactRouterConfig.future.unstable_serverComponents ? clientModules : [])]
542
604
  }
543
- } : {
605
+ } : !ctx.reactRouterConfig.future.unstable_serverComponents || !process.env.REACT_SERVER_BUILD ? {
544
606
  // We move SSR-only assets to client assets. Note that the
545
607
  // SSR build can also emit code-split JS files (e.g. by
546
608
  // dynamic import) under the same assets directory
@@ -561,6 +623,28 @@ const reactRouterVitePlugin = _config => {
561
623
  format: ctx.reactRouterConfig.serverModuleFormat
562
624
  }
563
625
  }
626
+ } : {
627
+ // We move SSR-only assets to client assets. Note that the
628
+ // SSR build can also emit code-split JS files (e.g. by
629
+ // dynamic import) under the same assets directory
630
+ // regardless of "ssrEmitAssets" option, so we also need to
631
+ // keep these JS files have to be kept as-is.
632
+ ssrEmitAssets: true,
633
+ copyPublicDir: false,
634
+ // Assets in the public directory are only used by the client
635
+ manifest: true,
636
+ // We need the manifest to detect SSR-only assets
637
+ outDir: getReactServerBuildDirectory(ctx.reactRouterConfig),
638
+ rollupOptions: {
639
+ ...baseRollupOptions,
640
+ preserveEntrySignatures: "exports-only",
641
+ // TODO: Add server references (serverModules) to input
642
+ input: reactServerBuildId,
643
+ output: {
644
+ entryFileNames: ctx.reactRouterConfig.serverBuildFile,
645
+ format: ctx.reactRouterConfig.serverModuleFormat
646
+ }
647
+ }
564
648
  })
565
649
  }
566
650
  } : undefined),
@@ -635,8 +719,8 @@ const reactRouterVitePlugin = _config => {
635
719
  if (styles.isCssModulesFile(id)) {
636
720
  cssModulesManifest[id] = code;
637
721
  }
638
- if (isClientRoute(id)) {
639
- let routeModuleId = id.replace(CLIENT_ROUTE_QUERY_STRING, "");
722
+ if (id.endsWith(BUILD_CLIENT_ROUTE_QUERY_STRING)) {
723
+ let routeModuleId = id.replace(BUILD_CLIENT_ROUTE_QUERY_STRING, "");
640
724
  let sourceExports = await getRouteModuleExports(viteChildCompiler, ctx, routeModuleId);
641
725
  let routeFileName = path__namespace.basename(routeModuleId);
642
726
  let clientExports = sourceExports.filter(exportName => CLIENT_ROUTE_EXPORTS.includes(exportName)).join(", ");
@@ -714,7 +798,7 @@ const reactRouterVitePlugin = _config => {
714
798
  // After the SSR build is finished, we inspect the Vite manifest for
715
799
  // the SSR build and move server-only assets to client assets directory
716
800
  async handler() {
717
- if (!ctx.isSsrBuild) {
801
+ if (!ctx.isSsrBuild || process.env.REACT_SERVER_BUILD) {
718
802
  return;
719
803
  }
720
804
  invariant["default"](viteConfig);
@@ -748,8 +832,19 @@ const reactRouterVitePlugin = _config => {
748
832
  if (movedAssetPaths.length) {
749
833
  viteConfig.logger.info(["", `${colors__default["default"].green("✓")} ${movedAssetPaths.length} asset${movedAssetPaths.length > 1 ? "s" : ""} moved from React Router server build to client assets.`, ...movedAssetPaths.map(movedAssetPath => colors__default["default"].dim(path__namespace.relative(ctx.rootDirectory, movedAssetPath))), ""].join("\n"));
750
834
  }
835
+ if (ctx.reactRouterConfig.prerender != null) {
836
+ // If we have prerender routes, that takes precedence over SPA mode
837
+ // which is ssr:false and only the rot route being rendered
838
+ await handlePrerender(viteConfig, ctx.reactRouterConfig, serverBuildDirectory, clientBuildDirectory);
839
+ } else if (!ctx.reactRouterConfig.ssr) {
840
+ await handleSpaMode(viteConfig, ctx.reactRouterConfig, serverBuildDirectory, clientBuildDirectory);
841
+ }
842
+ // For both SPA mode and prerendering, we can remove the server builds
843
+ // if ssr:false is set
751
844
  if (!ctx.reactRouterConfig.ssr) {
752
- await handleSpaMode(serverBuildDirectory, ctx.reactRouterConfig.serverBuildFile, clientBuildDirectory, viteConfig, ctx.reactRouterConfig.basename);
845
+ // Cleanup - we no longer need the server build assets
846
+ viteConfig.logger.info(["Removing the server build in", colors__default["default"].green(serverBuildDirectory), "due to ssr:false"].join(" "));
847
+ fse__namespace.removeSync(serverBuildDirectory);
753
848
  }
754
849
  }
755
850
  },
@@ -787,6 +882,18 @@ const reactRouterVitePlugin = _config => {
787
882
  });
788
883
  return `window.__remixManifest=${reactRouterManifestString};`;
789
884
  }
885
+ case vmod.resolve(reactServerBuildId):
886
+ {
887
+ return getReactServerEntry();
888
+ }
889
+ case vmod.resolve(clientReferencesId):
890
+ {
891
+ return getClientReferencesEntry();
892
+ }
893
+ case vmod.resolve(serverReferencesId):
894
+ {
895
+ return getServerReferencesEntry();
896
+ }
790
897
  }
791
898
  }
792
899
  }, {
@@ -842,9 +949,20 @@ const reactRouterVitePlugin = _config => {
842
949
  }, {
843
950
  name: "react-router-route-exports",
844
951
  async transform(code, id, options) {
845
- if (options !== null && options !== void 0 && options.ssr) return;
952
+ var _ctx;
953
+ if (options !== null && options !== void 0 && options.ssr && !((_ctx = ctx) !== null && _ctx !== void 0 && _ctx.reactRouterConfig.future.unstable_serverComponents)) {
954
+ return;
955
+ }
846
956
  let route = getRoute(ctx.reactRouterConfig, id);
847
957
  if (!route) return;
958
+ let [filepath] = id.split("?");
959
+ if (ctx.reactRouterConfig.future.unstable_serverComponents && process.env.REACT_SERVER_BUILD) {
960
+ return removeExports.removeExports(code, CLIENT_ROUTE_EXPORTS, {
961
+ sourceMaps: true,
962
+ filename: id,
963
+ sourceFileName: filepath
964
+ });
965
+ }
848
966
  if (!ctx.reactRouterConfig.ssr) {
849
967
  let serverOnlyExports = esModuleLexer.parse(code)[1].map(exp => exp.n).filter(exp => SERVER_ONLY_ROUTE_EXPORTS.includes(exp));
850
968
  if (serverOnlyExports.length > 0) {
@@ -860,7 +978,6 @@ const reactRouterVitePlugin = _config => {
860
978
  }
861
979
  }
862
980
  }
863
- let [filepath] = id.split("?");
864
981
  return removeExports.removeExports(code, SERVER_ONLY_ROUTE_EXPORTS, {
865
982
  sourceMaps: true,
866
983
  filename: id,
@@ -902,11 +1019,6 @@ const reactRouterVitePlugin = _config => {
902
1019
  let isJSX = filepath.endsWith("x");
903
1020
  let useFastRefresh = !ssr && (isJSX || code.includes(devRuntime));
904
1021
  if (!useFastRefresh) return;
905
- if (isClientRoute(id)) {
906
- return {
907
- code: addRefreshWrapper(ctx.reactRouterConfig, code, id)
908
- };
909
- }
910
1022
  let result = await babel__default["default"].transformAsync(code, {
911
1023
  babelrc: false,
912
1024
  configFile: false,
@@ -961,6 +1073,82 @@ const reactRouterVitePlugin = _config => {
961
1073
  });
962
1074
  return modules;
963
1075
  }
1076
+ }, {
1077
+ name: "remix:react-server",
1078
+ config() {
1079
+ const env = process.env.REACT_SERVER_BUILD ? "server" : "client";
1080
+ switch (env) {
1081
+ case "server":
1082
+ return {
1083
+ optimizeDeps: {
1084
+ include: ["react", "react/jsx-runtime", "react/jsx-dev-runtime", "react-server-dom-diy/server"]
1085
+ },
1086
+ resolve: {
1087
+ conditions: ["react-server"]
1088
+ },
1089
+ ssr: {
1090
+ noExternal: ["react", "react/jsx-runtime", "react/jsx-dev-runtime", "react-server-dom-diy/server"],
1091
+ optimizeDeps: {
1092
+ include: ["react", "react/jsx-runtime", "react/jsx-dev-runtime", "react-server-dom-diy/server"]
1093
+ },
1094
+ resolve: {
1095
+ conditions: ["react-server"],
1096
+ externalConditions: ["react-server"]
1097
+ }
1098
+ }
1099
+ };
1100
+ }
1101
+ },
1102
+ configResolved(resolvedViteConfig) {
1103
+ viteConfig = resolvedViteConfig;
1104
+ invariant["default"](viteConfig);
1105
+ },
1106
+ transform(...args) {
1107
+ invariant["default"](viteConfig);
1108
+ const env = process.env.REACT_SERVER_BUILD ? "server" : "client";
1109
+ let hash = viteConfig.mode !== "production" ? devHash : prodHash;
1110
+ switch (env) {
1111
+ case "client":
1112
+ return unpluginRsc.rscClientPlugin.vite({
1113
+ include: ["**/*"],
1114
+ transformModuleId: hash,
1115
+ useServerRuntime: {
1116
+ function: "createServerReference",
1117
+ module: "@react-router/dev/dist/runtime.client.js"
1118
+ },
1119
+ onModuleFound(id, type) {
1120
+ switch (type) {
1121
+ case "use server":
1122
+ serverModules.add(id);
1123
+ break;
1124
+ }
1125
+ }
1126
+ }).transform.call(this, ...args);
1127
+ case "server":
1128
+ return unpluginRsc.rscServerPlugin.vite({
1129
+ include: ["**/*"],
1130
+ transformModuleId: hash,
1131
+ useClientRuntime: {
1132
+ function: "registerClientReference",
1133
+ module: "react-server-dom-diy/server"
1134
+ },
1135
+ useServerRuntime: {
1136
+ function: "registerServerReference",
1137
+ module: "react-server-dom-diy/server"
1138
+ },
1139
+ onModuleFound(id, type) {
1140
+ switch (type) {
1141
+ case "use client":
1142
+ clientModules.add(id);
1143
+ break;
1144
+ case "use server":
1145
+ serverModules.add(id);
1146
+ break;
1147
+ }
1148
+ }
1149
+ }).transform.call(this, ...args);
1150
+ }
1151
+ }
964
1152
  }];
965
1153
  };
966
1154
  function isInReactRouterMonorepo() {
@@ -975,7 +1163,7 @@ function isEqualJson(v1, v2) {
975
1163
  }
976
1164
  function addRefreshWrapper(reactRouterConfig, code, id) {
977
1165
  let route = getRoute(reactRouterConfig, id);
978
- let acceptExports = route || isClientRoute(id) ? ["clientAction", "clientLoader", "handle", "meta", "links", "shouldRevalidate"] : [];
1166
+ let acceptExports = route ? ["clientAction", "clientLoader", "handle", "meta", "links", "shouldRevalidate"] : [];
979
1167
  return REACT_REFRESH_HEADER.replaceAll("__SOURCE__", JSON.stringify(id)) + code + REACT_REFRESH_FOOTER.replaceAll("__SOURCE__", JSON.stringify(id)).replaceAll("__ACCEPT_EXPORTS__", JSON.stringify(acceptExports)).replaceAll("__ROUTE_ID__", JSON.stringify(route === null || route === void 0 ? void 0 : route.id));
980
1168
  }
981
1169
  const REACT_REFRESH_HEADER = `
@@ -1039,32 +1227,151 @@ async function getRouteMetadata(ctx, viteChildCompiler, route, readRouteFile) {
1039
1227
  };
1040
1228
  return info;
1041
1229
  }
1042
- async function handleSpaMode(serverBuildDirectoryPath, serverBuildFile, clientBuildDirectory, viteConfig, basename) {
1043
- // Create a handler and call it for the `/` path - rendering down to the
1044
- // proper HydrateFallback ... or not! Maybe they have a static landing page
1045
- // generated from routes/_index.tsx.
1046
- let serverBuildPath = path__namespace.join(serverBuildDirectoryPath, serverBuildFile);
1230
+ async function getPrerenderBuildAndHandler(viteConfig, reactRouterConfig, serverBuildDirectory) {
1231
+ let serverBuildPath = path__namespace.join(serverBuildDirectory, reactRouterConfig.serverBuildFile);
1047
1232
  let build = await import(url__namespace.pathToFileURL(serverBuildPath).toString());
1048
1233
  let {
1049
1234
  createRequestHandler: createHandler
1050
1235
  } = await import('@react-router/node');
1051
- let handler = createHandler(build, viteConfig.mode);
1052
- let response = await handler(new Request(`http://localhost${basename}`));
1236
+ return {
1237
+ build,
1238
+ handler: createHandler(build, viteConfig.mode)
1239
+ };
1240
+ }
1241
+ async function handleSpaMode(viteConfig, reactRouterConfig, serverBuildDirectory, clientBuildDirectory) {
1242
+ let {
1243
+ handler
1244
+ } = await getPrerenderBuildAndHandler(viteConfig, reactRouterConfig, serverBuildDirectory);
1245
+ let request = new Request(`http://localhost${reactRouterConfig.basename}`);
1246
+ let response = await handler(request);
1247
+ let html = await response.text();
1248
+ validatePrerenderedResponse(response, html, "SPA Mode", "/");
1249
+ validatePrerenderedHtml(html, "SPA Mode");
1250
+ // Write out the index.html file for the SPA
1251
+ await fse__namespace.writeFile(path__namespace.join(clientBuildDirectory, "index.html"), html);
1252
+ viteConfig.logger.info("SPA Mode: index.html has been written to your " + colors__default["default"].bold(path__namespace.relative(process.cwd(), clientBuildDirectory)) + " directory");
1253
+ }
1254
+ async function handlePrerender(viteConfig, reactRouterConfig, serverBuildDirectory, clientBuildDirectory) {
1255
+ let {
1256
+ build,
1257
+ handler
1258
+ } = await getPrerenderBuildAndHandler(viteConfig, reactRouterConfig, serverBuildDirectory);
1259
+ let routes = createPrerenderRoutes(build.routes);
1260
+ let routesToPrerender = reactRouterConfig.prerender || ["/"];
1261
+ let requestInit = {
1262
+ headers: {
1263
+ // Header that can be used in the loader to know if you're running at
1264
+ // build time or runtime
1265
+ "X-React-Router-Prerender": "yes"
1266
+ }
1267
+ };
1268
+ for (let path of routesToPrerender) {
1269
+ var _matchRoutes;
1270
+ let hasLoaders = (_matchRoutes = reactRouter.matchRoutes(routes, path)) === null || _matchRoutes === void 0 ? void 0 : _matchRoutes.some(m => m.route.loader);
1271
+ if (hasLoaders) {
1272
+ await prerenderData(handler, path, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit);
1273
+ }
1274
+ await prerenderRoute(handler, path, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit);
1275
+ }
1276
+ async function prerenderData(handler, prerenderPath, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
1277
+ let normalizedPath = `${reactRouterConfig.basename}${prerenderPath === "/" ? "/_root.data" : `${prerenderPath.replace(/\/$/, "")}.data`}`.replace(/\/\/+/g, "/");
1278
+ let request = new Request(`http://localhost${normalizedPath}`, requestInit);
1279
+ let response = await handler(request);
1280
+ let data = await response.text();
1281
+ validatePrerenderedResponse(response, data, "Prerender", normalizedPath);
1282
+ // Write out the .data file
1283
+ let outdir = path__namespace.relative(process.cwd(), clientBuildDirectory);
1284
+ let outfile = path__namespace.join(outdir, normalizedPath.split("/").join(path__namespace.sep));
1285
+ await fse__namespace.ensureDir(path__namespace.dirname(outfile));
1286
+ await fse__namespace.outputFile(outfile, data);
1287
+ viteConfig.logger.info(`Prerender: Generated ${colors__default["default"].bold(outfile)}`);
1288
+ }
1289
+ }
1290
+ async function prerenderRoute(handler, prerenderPath, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
1291
+ let normalizedPath = `${reactRouterConfig.basename}${prerenderPath}/`.replace(/\/\/+/g, "/");
1292
+ let request = new Request(`http://localhost${normalizedPath}`, requestInit);
1293
+ let response = await handler(request);
1053
1294
  let html = await response.text();
1295
+ validatePrerenderedResponse(response, html, "Prerender", normalizedPath);
1296
+ if (!reactRouterConfig.ssr) {
1297
+ validatePrerenderedHtml(html, "Prerender");
1298
+ }
1299
+ // Write out the HTML file
1300
+ let outdir = path__namespace.relative(process.cwd(), clientBuildDirectory);
1301
+ let outfile = path__namespace.join(outdir, ...normalizedPath.split("/"), "index.html");
1302
+ await fse__namespace.ensureDir(path__namespace.dirname(outfile));
1303
+ await fse__namespace.outputFile(outfile, html);
1304
+ viteConfig.logger.info(`Prerender: Generated ${colors__default["default"].bold(outfile)}`);
1305
+ }
1306
+ function validatePrerenderedResponse(response, html, prefix, path) {
1054
1307
  if (response.status !== 200) {
1055
- throw new Error(`SPA Mode: Received a ${response.status} status code from ` + `\`entry.server.tsx\` while generating the \`index.html\` file.\n${html}`);
1308
+ throw new Error(`${prefix}: Received a ${response.status} status code from ` + `\`entry.server.tsx\` while prerendering the \`${path}\` ` + `path.\n${html}`);
1056
1309
  }
1310
+ }
1311
+ function validatePrerenderedHtml(html, prefix) {
1057
1312
  if (!html.includes("window.__remixContext =") || !html.includes("window.__remixRouteModules =")) {
1058
- throw new Error("SPA Mode: Did you forget to include <Scripts/> in your `root.tsx` " + "`HydrateFallback` component? Your `index.html` file cannot hydrate " + "into a SPA without `<Scripts />`.");
1313
+ throw new Error(`${prefix}: Did you forget to include <Scripts/> in your root route? ` + "Your pre-rendered HTML files cannot hydrate without `<Scripts />`.");
1059
1314
  }
1060
- // Write out the index.html file for the SPA
1061
- await fse__namespace.writeFile(path__namespace.join(clientBuildDirectory, "index.html"), html);
1062
- viteConfig.logger.info("SPA Mode: index.html has been written to your " + colors__default["default"].bold(path__namespace.relative(process.cwd(), clientBuildDirectory)) + " directory");
1063
- // Cleanup - we no longer need the server build assets
1064
- fse__namespace.removeSync(serverBuildDirectoryPath);
1315
+ }
1316
+ // Note: Duplicated from remix-server-runtime
1317
+ function groupRoutesByParentId(manifest) {
1318
+ let routes = {};
1319
+ Object.values(manifest).forEach(route => {
1320
+ let parentId = route.parentId || "";
1321
+ if (!routes[parentId]) {
1322
+ routes[parentId] = [];
1323
+ }
1324
+ routes[parentId].push(route);
1325
+ });
1326
+ return routes;
1327
+ }
1328
+ // Note: Duplicated from remix-server-runtime
1329
+ function createPrerenderRoutes(manifest, parentId = "", routesByParentId = groupRoutesByParentId(manifest)) {
1330
+ return (routesByParentId[parentId] || []).map(route => {
1331
+ let commonRoute = {
1332
+ // Always include root due to default boundaries
1333
+ hasErrorBoundary: route.id === "root" || route.module.ErrorBoundary != null,
1334
+ id: route.id,
1335
+ path: route.path,
1336
+ loader: route.module.loader ? () => null : undefined,
1337
+ action: undefined,
1338
+ handle: route.module.handle
1339
+ };
1340
+ return route.index ? {
1341
+ index: true,
1342
+ ...commonRoute
1343
+ } : {
1344
+ caseSensitive: route.caseSensitive,
1345
+ children: createPrerenderRoutes(manifest, route.id, routesByParentId),
1346
+ ...commonRoute
1347
+ };
1348
+ });
1349
+ }
1350
+ function prodHash(str, _) {
1351
+ return `/${path__namespace.relative(process.cwd(), str)}`;
1352
+ }
1353
+ function devHash(str, _) {
1354
+ const resolved = path__namespace.resolve(str);
1355
+ let unixPath = resolved.replace(/\\/g, "/");
1356
+ if (!unixPath.startsWith("/")) {
1357
+ unixPath = `/${unixPath}`;
1358
+ }
1359
+ if (resolved.startsWith(process.cwd())) {
1360
+ return `/${path__namespace.relative(process.cwd(), unixPath)}`;
1361
+ }
1362
+ return `/@fs${unixPath}`;
1363
+ }
1364
+ global.__clientModules = global.__clientModules || new Set();
1365
+ global.__serverModules = global.__serverModules || new Set();
1366
+ function getReactServerOptions() {
1367
+ return {
1368
+ clientModules: global.__clientModules,
1369
+ serverModules: global.__serverModules
1370
+ };
1065
1371
  }
1066
1372
 
1067
1373
  exports.extractPluginContext = extractPluginContext;
1374
+ exports.getReactServerOptions = getReactServerOptions;
1068
1375
  exports.getServerBuildDirectory = getServerBuildDirectory;
1069
1376
  exports.loadPluginContext = loadPluginContext;
1070
1377
  exports.reactRouterVitePlugin = reactRouterVitePlugin;