@react-router/dev 0.0.0-experimental-abd9fc79b → 0.0.0-experimental-2a01b6fd8

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/CHANGELOG.md CHANGED
@@ -1,5 +1,85 @@
1
1
  # `@react-router/dev`
2
2
 
3
+ ## v8.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Restart `react-router dev` with `--conditions=development` when not already configured ([#15291](https://github.com/remix-run/react-router/pull/15291))
8
+
9
+ ### Patch Changes
10
+
11
+ - Allow `typescript@7` to be used ([#15317](https://github.com/remix-run/react-router/pull/15317))
12
+
13
+ ### Unstable Changes
14
+
15
+ ⚠️ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_
16
+
17
+ - Add `unstable_rsc/client-version` client build version virtual module ([#15318](https://github.com/remix-run/react-router/pull/15318))
18
+
19
+ - Support the `subResourceIntegrity` config option in RSC Framework Mode ([#15321](https://github.com/remix-run/react-router/pull/15321))
20
+
21
+ #### Migration guide
22
+
23
+ No changes are required when using the default RSC SSR entry. If you maintain a custom `app/entry.ssr.tsx`, import the new virtual module and pass its hashes to React's `importMap` render option:
24
+
25
+ ```diff
26
+ +import subResourceIntegrity from "virtual:react-router/unstable_rsc/subresource-integrity";
27
+
28
+ return renderToReadableStream(<RSCStaticRouter getPayload={getPayload} />, {
29
+ ...options,
30
+ bootstrapScriptContent,
31
+ formState,
32
+ + importMap: subResourceIntegrity
33
+ + ? { integrity: subResourceIntegrity }
34
+ + : undefined,
35
+ signal: request.signal,
36
+ });
37
+ ```
38
+
39
+ - Updated dependencies:
40
+ - [`react-router@8.3.0`](https://github.com/remix-run/react-router/releases/tag/react-router@8.3.0)
41
+ - [`@react-router/node@8.3.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@8.3.0)
42
+ - [`@react-router/serve@8.3.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/serve@8.3.0)
43
+
44
+ ## v8.2.0
45
+
46
+ ### Minor Changes
47
+
48
+ - Add a Web Streams default server entry for non-Node Framework mode apps ([#15290](https://github.com/remix-run/react-router/pull/15290))
49
+ - Apps using `@react-router/node`, `@react-router/express`, or `@react-router/serve` continue to use the `renderToPipeableStream` default server entry
50
+ - Apps without those Node server adapter dependencies use a `renderToReadableStream` default server entry
51
+ - Non-Node apps with their own `entry.server.tsx` may be able to remove it in favor of the default if it is not doing anything custom
52
+ - Detect `nub` as a supported package manager when installing framework dependencies ([#15276](https://github.com/remix-run/react-router/pull/15276))
53
+
54
+ ### Patch Changes
55
+
56
+ - Detect user `rolldownOptions` config in Vite 8+ ([#15278](https://github.com/remix-run/react-router/pull/15278))
57
+
58
+ ### Unstable Changes
59
+
60
+ ⚠️ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_
61
+
62
+ - Add the [`future.unstable_enableNodeReadableStream`](https://reactrouter.com/upgrading/future#futureunstable_enablenodereadablestream) flag to opt Node Framework mode apps into using `renderToReadableStream` instead of `renderToPipeableStream` ([#15290](https://github.com/remix-run/react-router/pull/15290))
63
+ - This flag has no effect if you have your own `entry.server.tsx`
64
+ - Updated dependencies:
65
+ - [`react-router@8.2.0`](https://github.com/remix-run/react-router/releases/tag/react-router@8.2.0)
66
+ - [`@react-router/node@8.2.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@8.2.0)
67
+ - [`@react-router/serve@8.2.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/serve@8.2.0)
68
+
69
+ ## v8.1.0
70
+
71
+ ### Patch Changes
72
+
73
+ - Fix a regression with the new prerendering plugin where the `react-router.config.ts` `buildEnd` hook would run before prerendering was completed ([#15211](https://github.com/remix-run/react-router/pull/15211))
74
+ - Fixed `react-router typegen` crashes under the Bun runtime when Babel default imports are already unwrapped ([#15214](https://github.com/remix-run/react-router/pull/15214))
75
+ - Replace the deprecated `envFile:false` Vite config with `envDir:false` to eliminate a deprecation warning when using vite@8.1.0+ ([#15230](https://github.com/remix-run/react-router/pull/15230))
76
+ - Only add the `"node"` Vite server condition for Framework mode apps that declare a Node server adapter dependency ([#15242](https://github.com/remix-run/react-router/pull/15242))
77
+ - This prevents non-Node SSR runtimes from resolving Node-specific package exports by default
78
+ - Updated dependencies:
79
+ - [`react-router@8.1.0`](https://github.com/remix-run/react-router/releases/tag/react-router@8.1.0)
80
+ - [`@react-router/node@8.1.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@8.1.0)
81
+ - [`@react-router/serve@8.1.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/serve@8.1.0)
82
+
3
83
  ## v8.0.1
4
84
 
5
85
  ### Patch Changes
package/bin.cjs CHANGED
@@ -1,13 +1,20 @@
1
1
  #!/usr/bin/env node
2
- void (async () => {
3
- let { default: arg } = await import("arg");
2
+ let { parseArgs } = require("node:util");
3
+
4
+ const commands = new Set(["build", "dev", "reveal", "routes", "typegen"]);
4
5
 
6
+ void (async () => {
5
7
  // Minimal replication of our actual parsing in `run.ts`. If not already set,
6
8
  // default `NODE_ENV` so React loads the proper version in its CJS entry script.
7
9
  // We have to do this before importing `run.ts` since that is what imports
8
10
  // `react` (indirectly via `react-router`)
9
- let args = arg({}, { argv: process.argv.slice(2), permissive: true });
10
- if (args._.length === 0 || args._[0] === "dev") {
11
+ let { positionals } = parseArgs({
12
+ args: process.argv.slice(2),
13
+ allowPositionals: true,
14
+ strict: false,
15
+ });
16
+ let command = positionals.find((positional) => commands.has(positional));
17
+ if (!command || command === "dev") {
11
18
  process.env.NODE_ENV = process.env.NODE_ENV ?? "development";
12
19
  } else {
13
20
  process.env.NODE_ENV = process.env.NODE_ENV ?? "production";
@@ -0,0 +1,2 @@
1
+ declare const developmentConditionEnabled: boolean;
2
+ export default developmentConditionEnabled;
@@ -0,0 +1,2 @@
1
+ const developmentConditionEnabled = false;
2
+ export default developmentConditionEnabled;
@@ -0,0 +1,2 @@
1
+ declare const developmentConditionEnabled: boolean;
2
+ export default developmentConditionEnabled;
@@ -0,0 +1,2 @@
1
+ const developmentConditionEnabled = true;
2
+ export default developmentConditionEnabled;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-abd9fc79b
2
+ * @react-router/dev v0.0.0-experimental-2a01b6fd8
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import { g as preloadVite, h as getVite } from "./typegen-DWfipfw8.js";
11
+ import { _ as getVite, v as preloadVite } from "./typegen-CydnYlE9.js";
12
12
  //#region vite/build.ts
13
13
  async function build(root, viteBuildOptions) {
14
14
  await preloadVite();
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-abd9fc79b
3
+ * @react-router/dev v0.0.0-experimental-2a01b6fd8
4
4
  *
5
5
  * Copyright (c) Remix Software Inc.
6
6
  *
@@ -9,9 +9,9 @@
9
9
  *
10
10
  * @license MIT
11
11
  */
12
- import { g as preloadVite, h as getVite, l as loadConfig, n as watch, t as run$1 } from "../typegen-DWfipfw8.js";
12
+ import { _ as getVite, l as hasNodeDependency, n as watch, t as run$1, u as loadConfig, v as preloadVite } from "../typegen-CydnYlE9.js";
13
13
  import { createRequire } from "node:module";
14
- import arg from "arg";
14
+ import { parseArgs } from "node:util";
15
15
  import semver from "semver";
16
16
  import colors from "picocolors";
17
17
  import fs, { existsSync } from "node:fs";
@@ -21,10 +21,9 @@ import path from "node:path";
21
21
  import exitHook from "exit-hook";
22
22
  import { readPackageJSON } from "pkg-types";
23
23
  import "react-router";
24
- import * as babel from "@babel/core";
25
- import babelPluginSyntaxJSX from "@babel/plugin-syntax-jsx";
26
- import babelPresetTypeScript from "@babel/preset-typescript";
27
- import prettier from "prettier";
24
+ import developmentConditionEnabled from "#development-condition-enabled";
25
+ import { spawn } from "node:child_process";
26
+ import process$1 from "node:process";
28
27
  import packageJson from "@react-router/dev/package.json" with { type: "json" };
29
28
  //#region config/format.ts
30
29
  function formatRoutes(routeManifest, format) {
@@ -69,24 +68,6 @@ function formatRoutesAsJsx(routeManifest) {
69
68
  return output;
70
69
  }
71
70
  //#endregion
72
- //#region cli/useJavascript.ts
73
- async function transpile(tsx, options = {}) {
74
- let mjs = babel.transformSync(tsx, {
75
- compact: false,
76
- cwd: options.cwd,
77
- filename: options.filename,
78
- plugins: [babelPluginSyntaxJSX],
79
- presets: [[babelPresetTypeScript, { jsx: "preserve" }]],
80
- retainLines: true
81
- });
82
- if (!mjs || !mjs.code) throw new Error("Could not parse TypeScript");
83
- /**
84
- * Babel's `compact` and `retainLines` options are both bad at formatting code.
85
- * Use Prettier for nicer formatting.
86
- */
87
- return await prettier.format(mjs.code, { parser: "babel" });
88
- }
89
- //#endregion
90
71
  //#region vite/profiler.ts
91
72
  const getSession = () => global.__reactRouter_profile_session;
92
73
  const start = async (callback) => {
@@ -124,6 +105,42 @@ async function hasReactRouterRscPlugin({ root, viteBuildOptions: { config, logLe
124
105
  }, "build", "production", "production")).plugins.some((plugin) => plugin?.name === "react-router/rsc");
125
106
  }
126
107
  //#endregion
108
+ //#region restart-with-conditions.ts
109
+ /**
110
+ * Restarts the current Node process, appending new flags to the
111
+ * existing NODE_OPTIONS. SIGINT/SIGTERM are always forwarded to the child.
112
+ */
113
+ function restartWithMergedOptions(nodeOptions) {
114
+ if (process$1.env.REACT_ROUTER_DEV_RESTARTED === "true") throw new Error("restartWithMergedOptions() was called, but the process has already been restarted. This is likely a bug in @react-router/dev.");
115
+ const mergedOptions = [process$1.env.NODE_OPTIONS, nodeOptions].filter(Boolean).join(" ").trim();
116
+ console.log(`[restart] Relaunching with NODE_OPTIONS: ${mergedOptions}`);
117
+ const [cmd, ...args] = process$1.argv;
118
+ const child = spawn(cmd, args, {
119
+ env: {
120
+ ...process$1.env,
121
+ NODE_OPTIONS: mergedOptions,
122
+ REACT_ROUTER_DEV_RESTARTED: "true"
123
+ },
124
+ stdio: "inherit"
125
+ });
126
+ let signalHandlers = ["SIGINT", "SIGTERM"].map((sig) => {
127
+ let handler = () => {
128
+ child.kill(sig);
129
+ };
130
+ process$1.on(sig, handler);
131
+ return [sig, handler];
132
+ });
133
+ child.on("exit", (code, signal) => {
134
+ for (let [sig, handler] of signalHandlers) process$1.off(sig, handler);
135
+ if (signal) process$1.kill(process$1.pid, signal);
136
+ else process$1.exit(code ?? 0);
137
+ });
138
+ child.on("error", (err) => {
139
+ console.error("[restart] Failed to spawn child process:", err);
140
+ process$1.exit(1);
141
+ });
142
+ }
143
+ //#endregion
127
144
  //#region cli/commands.ts
128
145
  const nodeRequire = createRequire(import.meta.url);
129
146
  async function routes(rootDirectory, flags = {}) {
@@ -141,7 +158,7 @@ async function routes(rootDirectory, flags = {}) {
141
158
  }
142
159
  async function build(root, options = {}) {
143
160
  root = resolveRootDirectory(root, options);
144
- let { build } = await import("../build-DLQ9EJvQ.js");
161
+ let { build } = await import("../build-BY8q9ar7.js");
145
162
  if (options.profile) await start();
146
163
  try {
147
164
  await build(root, options);
@@ -150,11 +167,13 @@ async function build(root, options = {}) {
150
167
  }
151
168
  }
152
169
  async function dev(root, options = {}) {
153
- let { dev } = await import("../dev-CPAzEdKN.js");
154
- if (options.profile) await start();
155
- exitHook(() => stop(console.info));
156
- root = resolveRootDirectory(root, options);
157
- await dev(root, options);
170
+ if (developmentConditionEnabled) {
171
+ let { dev } = await import("../dev-D4wCelLk.js");
172
+ if (options.profile) await start();
173
+ exitHook(() => stop(console.info));
174
+ root = resolveRootDirectory(root, options);
175
+ await dev(root, options);
176
+ } else restartWithMergedOptions("--conditions=development");
158
177
  await new Promise(() => {});
159
178
  }
160
179
  let clientEntries = [
@@ -231,24 +250,14 @@ async function generateEntry(entry, rootDirectory, flags = {}) {
231
250
  }
232
251
  await copyFile(defaultEntry, outputFile);
233
252
  } else {
234
- if (!((await readPackageJSON(rootDirectory)).dependencies ?? {})["@react-router/node"]) {
235
- console.error(colors.red(`No default server entry detected.`));
236
- return;
237
- }
238
- let defaultEntryClient = path$1.resolve(defaultsDirectory, "entry.client.tsx");
239
- let defaultEntryServer = path$1.resolve(defaultsDirectory, `entry.server.node.tsx`);
240
- let isServerEntry = entry === "entry.server";
241
- let contents = isServerEntry ? await createServerEntry(rootDirectory, appDirectory, defaultEntryServer) : await createClientEntry(rootDirectory, appDirectory, defaultEntryClient);
242
- let useTypeScript = flags.typescript ?? true;
243
- let outputEntry = `${entry}.${useTypeScript ? "tsx" : "jsx"}`;
253
+ let pkgJson = await readPackageJSON(rootDirectory);
254
+ let outputExtension = flags.typescript ?? true ? "tsx" : "jsx";
255
+ let defaultEntryClient = path$1.resolve(defaultsDirectory, `entry.client.${outputExtension}`);
256
+ let defaultEntryServer = path$1.resolve(defaultsDirectory, hasNodeDependency(pkgJson.dependencies) && !configResult.value.future.unstable_enableNodeReadableStream ? `entry.server.node.${outputExtension}` : `entry.server.web.${outputExtension}`);
257
+ let contents = entry === "entry.server" ? await createServerEntry(rootDirectory, appDirectory, defaultEntryServer) : await createClientEntry(rootDirectory, appDirectory, defaultEntryClient);
258
+ let outputEntry = `${entry}.${outputExtension}`;
244
259
  outputFile = path$1.resolve(appDirectory, outputEntry);
245
- if (!useTypeScript) {
246
- let javascript = await transpile(contents, {
247
- cwd: rootDirectory,
248
- filename: isServerEntry ? defaultEntryServer : defaultEntryClient
249
- });
250
- await writeFile(outputFile, javascript, "utf-8");
251
- } else await writeFile(outputFile, contents, "utf-8");
260
+ await writeFile(outputFile, contents, "utf-8");
252
261
  }
253
262
  console.log(colors.blue(`Entry file ${entry} created at ${path$1.relative(rootDirectory, outputFile)}.`));
254
263
  }
@@ -341,7 +350,7 @@ ${colors.blueBright("react-router")}
341
350
  --json Print the routes as JSON
342
351
  \`reveal\` Options:
343
352
  --config, -c Use specified Vite config file (string)
344
- --no-typescript Generate plain JavaScript files
353
+ --no-typescript Generate plain JavaScript files (deprecated; will be removed in v9)
345
354
  \`typegen\` Options:
346
355
  --watch Automatically regenerate types whenever route config (\`routes.ts\`) or route modules change
347
356
 
@@ -373,6 +382,18 @@ ${colors.blueBright("react-router")}
373
382
  $ react-router typegen
374
383
  $ react-router typegen --watch
375
384
  `;
385
+ function getBooleanArg(value) {
386
+ return typeof value === "boolean" ? value : void 0;
387
+ }
388
+ function getBooleanStringArg(value) {
389
+ return typeof value === "boolean" || typeof value === "string" ? value : void 0;
390
+ }
391
+ function getNumberArg(value) {
392
+ return typeof value === "string" ? Number(value) : void 0;
393
+ }
394
+ function getStringArg(value) {
395
+ return typeof value === "string" ? value : void 0;
396
+ }
376
397
  /**
377
398
  * Programmatic interface for running the react-router CLI with the given command line
378
399
  * arguments.
@@ -385,43 +406,78 @@ async function run(argv = process.argv.slice(2), { isMain = false } = {}) {
385
406
  let nextArg = argv[argv.indexOf(arg) + 1];
386
407
  return !nextArg || nextArg.startsWith("-");
387
408
  };
388
- let args = arg({
389
- "--force": Boolean,
390
- "--help": Boolean,
391
- "-h": "--help",
392
- "--json": Boolean,
393
- "--token": String,
394
- "--typescript": Boolean,
395
- "--no-typescript": Boolean,
396
- "--version": Boolean,
397
- "-v": "--version",
398
- "--port": Number,
399
- "-p": "--port",
400
- "--config": String,
401
- "-c": "--config",
402
- "--assetsInlineLimit": Number,
403
- "--clearScreen": Boolean,
404
- "--cors": Boolean,
405
- "--emptyOutDir": Boolean,
406
- "--host": isBooleanFlag("--host") ? Boolean : String,
407
- "--logLevel": String,
408
- "-l": "--logLevel",
409
- "--minify": String,
410
- "--mode": String,
411
- "-m": "--mode",
412
- "--open": isBooleanFlag("--open") ? Boolean : String,
413
- "--strictPort": Boolean,
414
- "--profile": Boolean,
415
- "--sourcemapClient": isBooleanFlag("--sourcemapClient") ? Boolean : String,
416
- "--sourcemapServer": isBooleanFlag("--sourcemapServer") ? Boolean : String,
417
- "--watch": Boolean
418
- }, { argv });
419
- let input = args._;
420
- let flags = Object.entries(args).reduce((acc, [key, value]) => {
421
- key = key.replace(/^--/, "");
422
- acc[key] = value;
423
- return acc;
424
- }, {});
409
+ let { values, positionals } = parseArgs({
410
+ args: argv,
411
+ allowPositionals: true,
412
+ options: {
413
+ assetsInlineLimit: { type: "string" },
414
+ clearScreen: { type: "boolean" },
415
+ config: {
416
+ type: "string",
417
+ short: "c"
418
+ },
419
+ cors: { type: "boolean" },
420
+ emptyOutDir: { type: "boolean" },
421
+ force: { type: "boolean" },
422
+ help: {
423
+ type: "boolean",
424
+ short: "h"
425
+ },
426
+ host: { type: isBooleanFlag("--host") ? "boolean" : "string" },
427
+ json: { type: "boolean" },
428
+ logLevel: {
429
+ type: "string",
430
+ short: "l"
431
+ },
432
+ minify: { type: "string" },
433
+ mode: {
434
+ type: "string",
435
+ short: "m"
436
+ },
437
+ "no-typescript": { type: "boolean" },
438
+ open: { type: isBooleanFlag("--open") ? "boolean" : "string" },
439
+ port: {
440
+ type: "string",
441
+ short: "p"
442
+ },
443
+ profile: { type: "boolean" },
444
+ sourcemapClient: { type: isBooleanFlag("--sourcemapClient") ? "boolean" : "string" },
445
+ sourcemapServer: { type: isBooleanFlag("--sourcemapServer") ? "boolean" : "string" },
446
+ strictPort: { type: "boolean" },
447
+ token: { type: "string" },
448
+ typescript: { type: "boolean" },
449
+ version: {
450
+ type: "boolean",
451
+ short: "v"
452
+ },
453
+ watch: { type: "boolean" }
454
+ }
455
+ });
456
+ let input = positionals;
457
+ let flags = {
458
+ assetsInlineLimit: getNumberArg(values.assetsInlineLimit),
459
+ clearScreen: getBooleanArg(values.clearScreen),
460
+ config: getStringArg(values.config),
461
+ cors: getBooleanArg(values.cors),
462
+ emptyOutDir: getBooleanArg(values.emptyOutDir),
463
+ force: getBooleanArg(values.force),
464
+ help: getBooleanArg(values.help),
465
+ host: getBooleanStringArg(values.host),
466
+ json: getBooleanArg(values.json),
467
+ logLevel: getStringArg(values.logLevel),
468
+ minify: getStringArg(values.minify),
469
+ mode: getStringArg(values.mode),
470
+ open: getBooleanStringArg(values.open),
471
+ port: getNumberArg(values.port),
472
+ profile: getBooleanArg(values.profile),
473
+ sourcemapClient: getBooleanStringArg(values.sourcemapClient),
474
+ sourcemapServer: getBooleanStringArg(values.sourcemapServer),
475
+ strictPort: getBooleanArg(values.strictPort),
476
+ token: getStringArg(values.token),
477
+ typescript: getBooleanArg(values.typescript),
478
+ version: getBooleanArg(values.version),
479
+ watch: getBooleanArg(values.watch)
480
+ };
425
481
  if (flags.help) {
426
482
  console.log(helpText);
427
483
  return;
@@ -431,7 +487,10 @@ async function run(argv = process.argv.slice(2), { isMain = false } = {}) {
431
487
  return;
432
488
  }
433
489
  flags.interactive = flags.interactive ?? isMain;
434
- if (args["--no-typescript"]) flags.typescript = false;
490
+ if (values["no-typescript"]) {
491
+ console.warn(colors.yellow("The --no-typescript flag is deprecated and will be removed in React Router v9."));
492
+ flags.typescript = false;
493
+ }
435
494
  switch (input[0]) {
436
495
  case "routes":
437
496
  await routes(input[1], flags);
@@ -15,6 +15,7 @@ import {
15
15
  import routes from "virtual:react-router/unstable_rsc/routes";
16
16
  import routeDiscovery from "virtual:react-router/unstable_rsc/route-discovery";
17
17
  import basename from "virtual:react-router/unstable_rsc/basename";
18
+ import clientVersion from "virtual:react-router/unstable_rsc/client-version";
18
19
  import unstable_reactRouterServeConfig from "virtual:react-router/unstable_rsc/react-router-serve-config";
19
20
 
20
21
  export { unstable_reactRouterServeConfig };
@@ -31,6 +32,8 @@ export function fetchServer(
31
32
  decodeFormState,
32
33
  decodeReply,
33
34
  loadServerAction,
35
+ // Detect stale clients after a new deployment.
36
+ clientVersion,
34
37
  // The incoming request.
35
38
  request,
36
39
  requestContext,
@@ -4,6 +4,7 @@ import {
4
4
  unstable_routeRSCServerRequest as routeRSCServerRequest,
5
5
  unstable_RSCStaticRouter as RSCStaticRouter,
6
6
  } from "react-router";
7
+ import subResourceIntegrity from "virtual:react-router/unstable_rsc/subresource-integrity";
7
8
 
8
9
  export async function generateHTML(
9
10
  request: Request,
@@ -31,6 +32,9 @@ export async function generateHTML(
31
32
  ...options,
32
33
  bootstrapScriptContent,
33
34
  formState,
35
+ importMap: subResourceIntegrity
36
+ ? { integrity: subResourceIntegrity }
37
+ : undefined,
34
38
  signal: request.signal,
35
39
  },
36
40
  );
@@ -0,0 +1,12 @@
1
+ import { startTransition, StrictMode } from "react";
2
+ import { hydrateRoot } from "react-dom/client";
3
+ import { HydratedRouter } from "react-router/dom";
4
+
5
+ startTransition(() => {
6
+ hydrateRoot(
7
+ document,
8
+ <StrictMode>
9
+ <HydratedRouter />
10
+ </StrictMode>,
11
+ );
12
+ });
@@ -0,0 +1,82 @@
1
+ import { PassThrough } from "node:stream";
2
+
3
+ import { createReadableStreamFromReadable } from "@react-router/node";
4
+ import { ServerRouter } from "react-router";
5
+ import { isbot } from "isbot";
6
+
7
+ import { renderToPipeableStream } from "react-dom/server";
8
+
9
+ export const streamTimeout = 5_000;
10
+
11
+ export default function handleRequest(
12
+ request,
13
+ responseStatusCode,
14
+ responseHeaders,
15
+ routerContext,
16
+ loadContext,
17
+ ) {
18
+ // https://httpwg.org/specs/rfc9110.html#HEAD
19
+ if (request.method.toUpperCase() === "HEAD") {
20
+ return new Response(null, {
21
+ status: responseStatusCode,
22
+ headers: responseHeaders,
23
+ });
24
+ }
25
+
26
+ return new Promise((resolve, reject) => {
27
+ let shellRendered = false;
28
+ let userAgent = request.headers.get("user-agent");
29
+
30
+ // Ensure requests from bots and SPA Mode renders wait for all content to load before responding
31
+ // https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
32
+ let readyOption =
33
+ (userAgent && isbot(userAgent)) || routerContext.isSpaMode
34
+ ? "onAllReady"
35
+ : "onShellReady";
36
+
37
+ // Abort the rendering stream after the `streamTimeout` so it has time to
38
+ // flush down the rejected boundaries
39
+ let timeoutId = setTimeout(() => abort(), streamTimeout + 1000);
40
+
41
+ const { pipe, abort } = renderToPipeableStream(
42
+ <ServerRouter context={routerContext} url={request.url} />,
43
+ {
44
+ [readyOption]() {
45
+ shellRendered = true;
46
+ const body = new PassThrough({
47
+ final(callback) {
48
+ // Clear the timeout to prevent retaining the closure and memory leak
49
+ clearTimeout(timeoutId);
50
+ timeoutId = undefined;
51
+ callback();
52
+ },
53
+ });
54
+ const stream = createReadableStreamFromReadable(body);
55
+
56
+ responseHeaders.set("Content-Type", "text/html");
57
+
58
+ pipe(body);
59
+
60
+ resolve(
61
+ new Response(stream, {
62
+ headers: responseHeaders,
63
+ status: responseStatusCode,
64
+ }),
65
+ );
66
+ },
67
+ onShellError(error) {
68
+ reject(error);
69
+ },
70
+ onError(error) {
71
+ responseStatusCode = 500;
72
+ // Log streaming rendering errors from inside the shell. Don't log
73
+ // errors encountered during initial shell rendering since they'll
74
+ // reject and get logged in handleDocumentRequest.
75
+ if (shellRendered) {
76
+ console.error(error);
77
+ }
78
+ },
79
+ },
80
+ );
81
+ });
82
+ }
@@ -0,0 +1,53 @@
1
+ import { ServerRouter } from "react-router";
2
+ import { isbot } from "isbot";
3
+ import { renderToReadableStream } from "react-dom/server";
4
+
5
+ export const streamTimeout = 5_000;
6
+
7
+ export default async function handleRequest(
8
+ request,
9
+ responseStatusCode,
10
+ responseHeaders,
11
+ routerContext,
12
+ _loadContext,
13
+ ) {
14
+ // https://httpwg.org/specs/rfc9110.html#HEAD
15
+ if (request.method.toUpperCase() === "HEAD") {
16
+ return new Response(null, {
17
+ status: responseStatusCode,
18
+ headers: responseHeaders,
19
+ });
20
+ }
21
+
22
+ let shellRendered = false;
23
+ let userAgent = request.headers.get("user-agent");
24
+
25
+ const body = await renderToReadableStream(
26
+ <ServerRouter context={routerContext} url={request.url} />,
27
+ {
28
+ signal: AbortSignal.timeout(streamTimeout + 1000),
29
+ onError(error) {
30
+ responseStatusCode = 500;
31
+ // Log streaming rendering errors from inside the shell. Don't log
32
+ // errors encountered during initial shell rendering since they'll
33
+ // reject and get logged in handleDocumentRequest.
34
+ if (shellRendered) {
35
+ console.error(error);
36
+ }
37
+ },
38
+ },
39
+ );
40
+ shellRendered = true;
41
+
42
+ // Ensure requests from bots and SPA Mode renders wait for all content to load before responding
43
+ // https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
44
+ if ((userAgent && isbot(userAgent)) || routerContext.isSpaMode) {
45
+ await body.allReady;
46
+ }
47
+
48
+ responseHeaders.set("Content-Type", "text/html");
49
+ return new Response(body, {
50
+ headers: responseHeaders,
51
+ status: responseStatusCode,
52
+ });
53
+ }
@@ -0,0 +1,54 @@
1
+ import type { EntryContext, RouterContextProvider } from "react-router";
2
+ import { ServerRouter } from "react-router";
3
+ import { isbot } from "isbot";
4
+ import { renderToReadableStream } from "react-dom/server";
5
+
6
+ export const streamTimeout = 5_000;
7
+
8
+ export default async function handleRequest(
9
+ request: Request,
10
+ responseStatusCode: number,
11
+ responseHeaders: Headers,
12
+ routerContext: EntryContext,
13
+ _loadContext: RouterContextProvider,
14
+ ) {
15
+ // https://httpwg.org/specs/rfc9110.html#HEAD
16
+ if (request.method.toUpperCase() === "HEAD") {
17
+ return new Response(null, {
18
+ status: responseStatusCode,
19
+ headers: responseHeaders,
20
+ });
21
+ }
22
+
23
+ let shellRendered = false;
24
+ let userAgent = request.headers.get("user-agent");
25
+
26
+ const body = await renderToReadableStream(
27
+ <ServerRouter context={routerContext} url={request.url} />,
28
+ {
29
+ signal: AbortSignal.timeout(streamTimeout + 1000),
30
+ onError(error: unknown) {
31
+ responseStatusCode = 500;
32
+ // Log streaming rendering errors from inside the shell. Don't log
33
+ // errors encountered during initial shell rendering since they'll
34
+ // reject and get logged in handleDocumentRequest.
35
+ if (shellRendered) {
36
+ console.error(error);
37
+ }
38
+ },
39
+ },
40
+ );
41
+ shellRendered = true;
42
+
43
+ // Ensure requests from bots and SPA Mode renders wait for all content to load before responding
44
+ // https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
45
+ if ((userAgent && isbot(userAgent)) || routerContext.isSpaMode) {
46
+ await body.allReady;
47
+ }
48
+
49
+ responseHeaders.set("Content-Type", "text/html");
50
+ return new Response(body, {
51
+ headers: responseHeaders,
52
+ status: responseStatusCode,
53
+ });
54
+ }
@@ -38,6 +38,7 @@ type ServerBundlesBuildManifest = BaseBuildManifest & {
38
38
  };
39
39
  type ServerModuleFormat = "esm" | "cjs";
40
40
  interface FutureConfig {
41
+ unstable_enableNodeReadableStream: boolean;
41
42
  unstable_optimizeDeps: boolean;
42
43
  }
43
44
  type SplitRouteModulesOption = boolean | "enforce";
package/dist/config.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
 
2
- import { i as ServerBundlesFunction, n as Preset, r as ReactRouterConfig, t as BuildManifest } from "./config-DvmRcD4Z.js";
2
+ import { i as ServerBundlesFunction, n as Preset, r as ReactRouterConfig, t as BuildManifest } from "./config-t89niiFv.js";
3
3
  export type { BuildManifest, ReactRouterConfig as Config, Preset, ServerBundlesFunction };
package/dist/config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-abd9fc79b
2
+ * @react-router/dev v0.0.0-experimental-2a01b6fd8
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-abd9fc79b
2
+ * @react-router/dev v0.0.0-experimental-2a01b6fd8
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import { g as preloadVite, h as getVite } from "./typegen-DWfipfw8.js";
11
+ import { _ as getVite, v as preloadVite } from "./typegen-CydnYlE9.js";
12
12
  import { n as start, r as stop, t as getSession } from "./cli/index.js";
13
13
  import colors from "picocolors";
14
14
  //#region vite/dev.ts
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-abd9fc79b
2
+ * @react-router/dev v0.0.0-experimental-2a01b6fd8
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-abd9fc79b
2
+ * @react-router/dev v0.0.0-experimental-2a01b6fd8
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -8,5 +8,5 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import { a as prefix, i as layout, n as getAppDirectory, o as relative, r as index, s as route } from "./routes-DW7QRPWA.js";
11
+ import { a as prefix, i as layout, n as getAppDirectory, o as relative, r as index, s as route } from "./routes-BLQxq7lr.js";
12
12
  export { getAppDirectory, index, layout, prefix, relative, route };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-abd9fc79b
2
+ * @react-router/dev v0.0.0-experimental-2a01b6fd8
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import { c as setAppDirectory, l as validateRouteConfig, t as configRoutesToRouteManifest, u as invariant } from "./routes-DW7QRPWA.js";
11
+ import { c as setAppDirectory, l as validateRouteConfig, t as configRoutesToRouteManifest, u as invariant } from "./routes-BLQxq7lr.js";
12
12
  import { createRequire } from "node:module";
13
13
  import colors from "picocolors";
14
14
  import fs from "node:fs";
@@ -66,6 +66,16 @@ function defineOptimizeDepsCompilerOptions(options) {
66
66
  let vite = getVite();
67
67
  return parseInt(vite.version.split(".")[0], 10) >= 8 ? { rolldownOptions: options.rolldown } : { esbuildOptions: options.esbuild };
68
68
  }
69
+ /**
70
+ * Read the user-supplied build options from either `rollupOptions` (Vite <=7)
71
+ * or `rolldownOptions` (Vite >=8).
72
+ */
73
+ function getUserBuildRollupOptions(environment) {
74
+ if (environment?.build) {
75
+ if ("rollupOptions" in environment.build) return environment.build.rollupOptions;
76
+ if ("rolldownOptions" in environment.build) return environment.build.rolldownOptions;
77
+ }
78
+ }
69
79
  //#endregion
70
80
  //#region vite/ssr-externals.ts
71
81
  const ssrExternals = isReactRouterRepo() ? [
@@ -95,7 +105,7 @@ async function createContext$1({ root, mode, customLogger }) {
95
105
  optimizeDeps: { noDiscovery: true },
96
106
  css: { postcss: {} },
97
107
  configFile: false,
98
- envFile: false,
108
+ envDir: false,
99
109
  plugins: [],
100
110
  environments: { __config_loader: {
101
111
  consumer: "server",
@@ -118,7 +128,7 @@ async function createContext$1({ root, mode, customLogger }) {
118
128
  /**
119
129
  * Determine which package manager the user prefers.
120
130
  *
121
- * npm, pnpm and Yarn set the user agent environment variable
131
+ * npm, pnpm, Yarn, Bun, and nub set the user agent environment variable
122
132
  * that can be used to determine which package manager ran
123
133
  * the command.
124
134
  */
@@ -131,6 +141,7 @@ const detectPackageManager = () => {
131
141
  if (pkgManager === "pnpm") return "pnpm";
132
142
  if (pkgManager === "yarn") return "yarn";
133
143
  if (pkgManager === "bun") return "bun";
144
+ if (pkgManager === "nub") return "nub";
134
145
  return;
135
146
  } catch {
136
147
  return;
@@ -283,7 +294,10 @@ async function resolveConfig({ root, viteRunnerContext, reactRouterConfigFile, s
283
294
  if ("unstable_trailingSlashAwareDataRequests" in futureConfig || "v8_trailingSlashAwareDataRequests" in futureConfig) return err("The `future.v8_trailingSlashAwareDataRequests` flag has been removed because trailing slash-aware data requests are now the default behavior");
284
295
  if ("unstable_subResourceIntegrity" in futureConfig) return err("The `future.unstable_subResourceIntegrity` flag has been stabilized and moved to a top-level `config.subResourceIntegrity` field");
285
296
  }
286
- let future = { unstable_optimizeDeps: userAndPresetConfigs.future?.unstable_optimizeDeps ?? false };
297
+ let future = {
298
+ unstable_enableNodeReadableStream: userAndPresetConfigs.future?.unstable_enableNodeReadableStream ?? false,
299
+ unstable_optimizeDeps: userAndPresetConfigs.future?.unstable_optimizeDeps ?? false
300
+ };
287
301
  let allowedActionOrigins = userAndPresetConfigs.allowedActionOrigins ?? false;
288
302
  let splitRouteModules = userAndPresetConfigs.splitRouteModules ?? true;
289
303
  let subResourceIntegrity = userAndPresetConfigs.subResourceIntegrity ?? false;
@@ -432,7 +446,6 @@ async function resolveEntryFiles({ rootDirectory, reactRouterConfig }) {
432
446
  if (!packageJsonPath) throw new Error(`Could not find package.json in ${rootDirectory} or any of its parent directories. Please add a package.json, or provide a custom entry.server.tsx/jsx file in your app directory.`);
433
447
  let packageJsonDirectory = path.dirname(packageJsonPath);
434
448
  let deps = (await readPackageJSON(packageJsonDirectory)).dependencies ?? {};
435
- if (!deps["@react-router/node"]) throw new Error(`Could not determine server runtime. Please install @react-router/node, or provide a custom entry.server.tsx/jsx file in your app directory.`);
436
449
  if (!deps["isbot"]) {
437
450
  console.log("adding `isbot@5` to your package.json, you should commit this change");
438
451
  await updatePackage(packageJsonPath, (pkg) => {
@@ -445,7 +458,7 @@ async function resolveEntryFiles({ rootDirectory, reactRouterConfig }) {
445
458
  stdio: "inherit"
446
459
  });
447
460
  }
448
- entryServerFile = `entry.server.node.tsx`;
461
+ entryServerFile = hasNodeDependency(deps) && !reactRouterConfig.future.unstable_enableNodeReadableStream ? `entry.server.node.tsx` : `entry.server.web.tsx`;
449
462
  }
450
463
  return {
451
464
  entryClientFilePath: userEntryClientFile ? path.resolve(reactRouterConfig.appDirectory, userEntryClientFile) : path.resolve(defaultsDirectory, entryClientFile),
@@ -478,6 +491,9 @@ const entryExts = [
478
491
  ".mjs",
479
492
  ".mts"
480
493
  ];
494
+ function hasNodeDependency(deps) {
495
+ return !deps || Boolean(deps["@react-router/node"] || deps["@react-router/express"] || deps["@react-router/serve"]);
496
+ }
481
497
  function isEntryFile(entryBasename, filename) {
482
498
  return entryExts.some((ext) => filename === `${entryBasename}${ext}`);
483
499
  }
@@ -543,10 +559,14 @@ var babel_exports = /* @__PURE__ */ __exportAll({
543
559
  generate: () => generate,
544
560
  parse: () => parse$1,
545
561
  t: () => t$1,
546
- traverse: () => traverse
562
+ traverse: () => traverse,
563
+ unwrapDefault: () => unwrapDefault
547
564
  });
548
- const traverse = _traverse.default;
549
- const generate = _generate.default;
565
+ function unwrapDefault(value) {
566
+ return value.default ?? value;
567
+ }
568
+ const traverse = unwrapDefault(_traverse);
569
+ const generate = unwrapDefault(_generate);
550
570
  //#endregion
551
571
  //#region typegen/params.ts
552
572
  function parse(fullpath) {
@@ -862,4 +882,4 @@ async function watch(rootDirectory, { mode, logger, rsc }) {
862
882
  return { close: async () => await ctx.configLoader.close() };
863
883
  }
864
884
  //#endregion
865
- export { t$1 as a, createConfigLoader as c, resolveRSCEntryFiles as d, ssrExternals as f, preloadVite as g, getVite as h, parse$1 as i, loadConfig as l, defineOptimizeDepsCompilerOptions as m, watch as n, traverse as o, defineCompilerOptions as p, generate as r, configRouteToBranchRoute as s, run as t, resolveEntryFiles as u };
885
+ export { getVite as _, t$1 as a, createConfigLoader as c, resolveEntryFiles as d, resolveRSCEntryFiles as f, getUserBuildRollupOptions as g, defineOptimizeDepsCompilerOptions as h, parse$1 as i, hasNodeDependency as l, defineCompilerOptions as m, watch as n, traverse as o, ssrExternals as p, generate as r, configRouteToBranchRoute as s, run as t, loadConfig as u, preloadVite as v };
package/dist/vite.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-abd9fc79b
2
+ * @react-router/dev v0.0.0-experimental-2a01b6fd8
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -8,21 +8,22 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import { a as t, c as createConfigLoader, d as resolveRSCEntryFiles, f as ssrExternals, g as preloadVite, h as getVite, i as parse$1, m as defineOptimizeDepsCompilerOptions, n as watch, o as traverse, p as defineCompilerOptions, r as generate, s as configRouteToBranchRoute, u as resolveEntryFiles } from "./typegen-DWfipfw8.js";
12
- import { u as invariant } from "./routes-DW7QRPWA.js";
11
+ import { _ as getVite, a as t, c as createConfigLoader, d as resolveEntryFiles, f as resolveRSCEntryFiles, g as getUserBuildRollupOptions, h as defineOptimizeDepsCompilerOptions, i as parse$1, l as hasNodeDependency, m as defineCompilerOptions, n as watch, o as traverse, p as ssrExternals, r as generate, s as configRouteToBranchRoute, v as preloadVite } from "./typegen-CydnYlE9.js";
12
+ import { u as invariant } from "./routes-BLQxq7lr.js";
13
13
  import { createRequire } from "node:module";
14
14
  import colors from "picocolors";
15
15
  import { existsSync, readFileSync, readdirSync } from "node:fs";
16
16
  import { cp, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
17
17
  import * as path$2 from "node:path";
18
18
  import path from "node:path";
19
+ import { readPackageJSON } from "pkg-types";
19
20
  import { createRequestHandler, matchRoutes, unstable_setDevServerHooks } from "react-router";
20
21
  import * as Path from "pathe";
21
22
  import path$1, { join } from "pathe";
22
23
  import pick from "lodash/pick.js";
23
- import * as babel from "@babel/core";
24
24
  import { createHash } from "node:crypto";
25
25
  import * as url from "node:url";
26
+ import * as babel from "@babel/core";
26
27
  import { createRequest, sendResponse } from "@remix-run/node-fetch-server";
27
28
  import { init, parse } from "es-module-lexer";
28
29
  import jsesc from "jsesc";
@@ -1532,23 +1533,11 @@ const reactRouterVitePlugin = () => {
1532
1533
  async buildApp(builder) {
1533
1534
  invariant(viteConfig);
1534
1535
  viteConfig.logger.info("Using Vite Environment API");
1535
- try {
1536
- let { reactRouterConfig } = ctx;
1537
- await cleanBuildDirectory(viteConfig, ctx);
1538
- await builder.build(builder.environments.client);
1539
- let serverEnvironments = getServerEnvironmentValues(ctx, builder.environments);
1540
- await Promise.all(serverEnvironments.map(builder.build));
1541
- await cleanViteManifests(environments, ctx);
1542
- let { buildManifest } = ctx;
1543
- invariant(buildManifest, "Expected build manifest");
1544
- await reactRouterConfig.buildEnd?.({
1545
- buildManifest,
1546
- reactRouterConfig,
1547
- viteConfig
1548
- });
1549
- } finally {
1550
- await closePluginResources();
1551
- }
1536
+ await cleanBuildDirectory(viteConfig, ctx);
1537
+ await builder.build(builder.environments.client);
1538
+ let serverEnvironments = getServerEnvironmentValues(ctx, builder.environments);
1539
+ await Promise.all(serverEnvironments.map(builder.build));
1540
+ await cleanViteManifests(environments, ctx);
1552
1541
  }
1553
1542
  }
1554
1543
  };
@@ -1592,7 +1581,7 @@ const reactRouterVitePlugin = () => {
1592
1581
  hmr: false
1593
1582
  },
1594
1583
  configFile: false,
1595
- envFile: false,
1584
+ envDir: false,
1596
1585
  plugins: [childCompilerPlugins.filter((plugin) => typeof plugin === "object" && plugin !== null && "name" in plugin && plugin.name !== "react-router" && plugin.name !== "react-router:route-exports" && plugin.name !== "react-router:hmr-updates" && plugin.name !== "react-router:validate-plugin-order").map((plugin) => ({
1597
1586
  ...plugin,
1598
1587
  configureServer: void 0,
@@ -2202,6 +2191,30 @@ const reactRouterVitePlugin = () => {
2202
2191
  }
2203
2192
  }
2204
2193
  }),
2194
+ {
2195
+ name: "react-router-build-end",
2196
+ sharedDuringBuild: true,
2197
+ config: {
2198
+ order: "post",
2199
+ handler({ builder: { buildApp } = {} }) {
2200
+ return { builder: { async buildApp(builder) {
2201
+ try {
2202
+ await buildApp?.(builder);
2203
+ invariant(viteConfig);
2204
+ let { buildManifest, reactRouterConfig } = ctx;
2205
+ invariant(buildManifest, "Expected build manifest");
2206
+ await reactRouterConfig.buildEnd?.({
2207
+ buildManifest,
2208
+ reactRouterConfig,
2209
+ viteConfig
2210
+ });
2211
+ } finally {
2212
+ await closePluginResources();
2213
+ }
2214
+ } } };
2215
+ }
2216
+ }
2217
+ },
2205
2218
  validatePluginOrder(),
2206
2219
  warnOnClientSourceMaps()
2207
2220
  ];
@@ -2545,6 +2558,7 @@ function mergeEnvironmentOptions(base, ...overrides) {
2545
2558
  }
2546
2559
  async function getEnvironmentOptionsResolvers(ctx, viteCommand) {
2547
2560
  let { serverBuildFile, serverModuleFormat } = ctx.reactRouterConfig;
2561
+ let pkgJson = await readPackageJSON(ctx.rootDirectory).catch(() => ({}));
2548
2562
  let packageRoot = path$2.dirname(nodeRequire$1.resolve("@react-router/dev/package.json"));
2549
2563
  let { moduleSyncEnabled } = await import(`file:///${path$2.join(packageRoot, "module-sync-enabled/index.mjs")}`);
2550
2564
  let vite = getVite();
@@ -2567,7 +2581,11 @@ async function getEnvironmentOptionsResolvers(ctx, viteCommand) {
2567
2581
  let maybeModuleSyncConditions = [...moduleSyncEnabled ? ["module-sync"] : []];
2568
2582
  let maybeDevelopmentConditions = viteCommand === "build" ? [] : ["development"];
2569
2583
  let maybeDefaultServerConditions = vite.defaultServerConditions || [];
2570
- let defaultExternalConditions = ["node"];
2584
+ let defaultExternalConditions = vite.defaultExternalConditions ?? ["node"];
2585
+ if (!hasNodeDependency(pkgJson.dependencies)) {
2586
+ maybeDefaultServerConditions = maybeDefaultServerConditions.filter((c) => c !== "node");
2587
+ defaultExternalConditions = defaultExternalConditions.filter((c) => c !== "node");
2588
+ }
2571
2589
  let baseConditions = [...maybeDevelopmentConditions, ...maybeModuleSyncConditions];
2572
2590
  return mergeEnvironmentOptions(getBaseOptions({ viteUserConfig }), {
2573
2591
  resolve: {
@@ -2579,7 +2597,7 @@ async function getEnvironmentOptionsResolvers(ctx, viteCommand) {
2579
2597
  ssrEmitAssets: true,
2580
2598
  copyPublicDir: false,
2581
2599
  rollupOptions: {
2582
- input: viteUserConfig.environments?.ssr?.build?.rollupOptions?.input ?? virtual$1.serverBuild.id,
2600
+ input: getUserBuildRollupOptions(viteUserConfig.environments?.ssr)?.input ?? virtual$1.serverBuild.id,
2583
2601
  output: {
2584
2602
  entryFileNames: serverBuildFile,
2585
2603
  format: serverModuleFormat
@@ -2596,7 +2614,7 @@ async function getEnvironmentOptionsResolvers(ctx, viteCommand) {
2596
2614
  let code = readFileSync(routeFilePath, "utf-8");
2597
2615
  return [`${routeFilePath}${BUILD_CLIENT_ROUTE_QUERY_STRING}`, ...ctx.reactRouterConfig.splitRouteModules && !isRootRoute ? routeChunkExportNames.map((exportName) => code.includes(exportName) ? getRouteChunkModuleId(routeFilePath, exportName) : null) : []].filter(isNonNullable);
2598
2616
  })],
2599
- output: viteUserConfig?.environments?.client?.build?.rollupOptions?.output ?? { entryFileNames: ({ moduleIds }) => {
2617
+ output: getUserBuildRollupOptions(viteUserConfig?.environments?.client)?.output ?? { entryFileNames: ({ moduleIds }) => {
2600
2618
  let routeChunkModuleId = moduleIds.find(isRouteChunkModuleId);
2601
2619
  let routeChunkName = routeChunkModuleId ? getRouteChunkNameFromModuleId(routeChunkModuleId)?.replace("unstable_", "") : null;
2602
2620
  let routeChunkSuffix = routeChunkName ? `-${kebabCase(routeChunkName)}` : "";
@@ -2706,7 +2724,7 @@ const nodeRequire = createRequire(import.meta.url);
2706
2724
  function hasDependency({ name, rootDirectory }) {
2707
2725
  try {
2708
2726
  return Boolean(nodeRequire.resolve(name, { paths: [rootDirectory] }));
2709
- } catch (e) {
2727
+ } catch {
2710
2728
  return false;
2711
2729
  }
2712
2730
  }
@@ -3166,6 +3184,10 @@ function reactRouterRSCVitePlugin() {
3166
3184
  jsxDev: viteCommand !== "build"
3167
3185
  })).code;
3168
3186
  }
3187
+ async function getClientVersion(source) {
3188
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(source));
3189
+ return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("").slice(0, 8);
3190
+ }
3169
3191
  return [
3170
3192
  {
3171
3193
  name: "react-router/rsc",
@@ -3189,7 +3211,6 @@ function reactRouterRSCVitePlugin() {
3189
3211
  if (userConfig.buildEnd) errors.push("buildEnd");
3190
3212
  if (userConfig.presets?.length) errors.push("presets");
3191
3213
  if (userConfig.serverBundles) errors.push("serverBundles");
3192
- if (userConfig.subResourceIntegrity) errors.push("subResourceIntegrity");
3193
3214
  if (errors.length) return `RSC Framework Mode does not currently support the following React Router config:\n${errors.map((x) => ` - ${x}`).join("\n")}\n`;
3194
3215
  }
3195
3216
  });
@@ -3394,6 +3415,29 @@ format: config.serverModuleFormat }
3394
3415
  (await typegenWatcherPromise)?.close();
3395
3416
  }
3396
3417
  } : null,
3418
+ (() => {
3419
+ let sri;
3420
+ return {
3421
+ name: "react-router/rsc/subresource-integrity",
3422
+ sharedDuringBuild: true,
3423
+ resolveId(id) {
3424
+ if (id === virtual.subResourceIntegrity.id) return virtual.subResourceIntegrity.resolvedId;
3425
+ },
3426
+ load(id) {
3427
+ if (id === virtual.subResourceIntegrity.resolvedId) return `export default ${JSON.stringify(sri)};`;
3428
+ },
3429
+ writeBundle(_options, bundle) {
3430
+ if (this.environment.name !== "client" || !config.subResourceIntegrity) return;
3431
+ sri = {};
3432
+ for (let output of Object.values(bundle)) {
3433
+ if (!output.fileName.endsWith(".js")) continue;
3434
+ let contents = output.type === "chunk" ? output.code : output.source;
3435
+ let hash = createHash("sha384").update(contents).digest("base64");
3436
+ sri[`${resolvedViteConfig.base}${output.fileName}`] = `sha384-${hash}`;
3437
+ }
3438
+ }
3439
+ };
3440
+ })(),
3397
3441
  {
3398
3442
  name: "react-router/rsc/virtual-route-config",
3399
3443
  resolveId(id) {
@@ -3438,6 +3482,27 @@ format: config.serverModuleFormat }
3438
3482
  if (id === virtual.routeDiscovery.resolvedId) return `export default ${JSON.stringify(config.ssr === false ? { mode: "initial" } : config.routeDiscovery ?? { mode: "lazy" })};`;
3439
3483
  }
3440
3484
  },
3485
+ {
3486
+ name: "react-router/rsc/virtual-client-version",
3487
+ resolveId(id) {
3488
+ if (id === virtual.clientVersion.id) return virtual.clientVersion.resolvedId;
3489
+ },
3490
+ load(id) {
3491
+ if (id === virtual.clientVersion.resolvedId) return viteCommand === "build" ? `
3492
+ import assetsManifest from "virtual:vite-rsc/assets-manifest";
3493
+ export default assetsManifest.clientVersion;
3494
+ ` : `export default "development";`;
3495
+ },
3496
+ generateBundle: {
3497
+ order: "post",
3498
+ async handler() {
3499
+ if (this.environment.name !== "client") return;
3500
+ const assetsManifest = resolvedViteConfig.plugins.find((plugin) => plugin.name === "rsc:minimal")?.api?.manager?.buildAssetsManifest;
3501
+ invariant(assetsManifest, "Vite RSC assets manifest not found");
3502
+ assetsManifest.clientVersion = await getClientVersion(JSON.stringify(assetsManifest));
3503
+ }
3504
+ }
3505
+ },
3441
3506
  {
3442
3507
  name: "react-router/rsc/hmr/inject-runtime",
3443
3508
  enforce: "pre",
@@ -3550,7 +3615,9 @@ format: config.serverModuleFormat }
3550
3615
  }
3551
3616
  const virtual = {
3552
3617
  routeConfig: create("unstable_rsc/routes"),
3618
+ subResourceIntegrity: create("unstable_rsc/subresource-integrity"),
3553
3619
  routeDiscovery: create("unstable_rsc/route-discovery"),
3620
+ clientVersion: create("unstable_rsc/client-version"),
3554
3621
  injectHmrRuntime: create("unstable_rsc/inject-hmr-runtime"),
3555
3622
  basename: create("unstable_rsc/basename"),
3556
3623
  reactRouterServeConfig: create("unstable_rsc/react-router-serve-config")
package/package.json CHANGED
@@ -1,8 +1,15 @@
1
1
  {
2
2
  "name": "@react-router/dev",
3
3
  "type": "module",
4
- "version": "0.0.0-experimental-abd9fc79b",
4
+ "version": "0.0.0-experimental-2a01b6fd8",
5
5
  "description": "Dev tools and CLI for React Router",
6
+ "keywords": [
7
+ "react",
8
+ "router",
9
+ "react-router",
10
+ "vite",
11
+ "vite-plugin"
12
+ ],
6
13
  "homepage": "https://reactrouter.com",
7
14
  "bugs": {
8
15
  "url": "https://github.com/remix-run/react-router/issues"
@@ -33,6 +40,10 @@
33
40
  "./package.json": "./package.json"
34
41
  },
35
42
  "imports": {
43
+ "#development-condition-enabled": {
44
+ "development": "./development-condition-enabled/true.mjs",
45
+ "default": "./development-condition-enabled/false.mjs"
46
+ },
36
47
  "#module-sync-enabled": {
37
48
  "module-sync": "./module-sync-enabled/true.mjs",
38
49
  "default": "./module-sync-enabled/false.cjs"
@@ -41,6 +52,17 @@
41
52
  "bin": {
42
53
  "react-router": "bin.cjs"
43
54
  },
55
+ "compatiblePackages": {
56
+ "schemaVersion": 1,
57
+ "rolldown": {
58
+ "type": "incompatible",
59
+ "reason": "Uses Vite-specific APIs"
60
+ },
61
+ "rollup": {
62
+ "type": "incompatible",
63
+ "reason": "Uses Vite-specific APIs"
64
+ }
65
+ },
44
66
  "wireit": {
45
67
  "build": {
46
68
  "command": "tsdown",
@@ -48,6 +70,7 @@
48
70
  "../../pnpm-workspace.yaml",
49
71
  "cli/**",
50
72
  "config/**",
73
+ "development-condition-enabled/**",
51
74
  "module-sync-enabled/**",
52
75
  "typegen/**",
53
76
  "vite/**",
@@ -65,12 +88,9 @@
65
88
  "@babel/core": "^7.29.7",
66
89
  "@babel/generator": "^7.29.7",
67
90
  "@babel/parser": "^7.29.7",
68
- "@babel/plugin-syntax-jsx": "^7.29.7",
69
- "@babel/preset-typescript": "^7.29.7",
70
91
  "@babel/traverse": "^7.29.7",
71
92
  "@babel/types": "^7.29.7",
72
93
  "@remix-run/node-fetch-server": "^0.13.3",
73
- "arg": "^5.0.1",
74
94
  "babel-dead-code-elimination": "^1.0.12",
75
95
  "chokidar": "^5.0.0",
76
96
  "dedent": "^1.7.2",
@@ -83,14 +103,15 @@
83
103
  "pathe": "^2.0.3",
84
104
  "picocolors": "^1.1.1",
85
105
  "pkg-types": "^2.3.1",
86
- "prettier": "^3.8.3",
87
106
  "react-refresh": "^0.18.0",
88
107
  "semver": "^7.8.1",
89
108
  "tinyglobby": "^0.2.16",
90
109
  "valibot": "^1.4.1",
91
- "@react-router/node": "0.0.0-experimental-abd9fc79b"
110
+ "@react-router/node": "0.0.0-experimental-2a01b6fd8"
92
111
  },
93
112
  "devDependencies": {
113
+ "@babel/plugin-syntax-jsx": "^7.29.7",
114
+ "@babel/preset-typescript": "^7.29.7",
94
115
  "@types/babel__core": "^7.20.5",
95
116
  "@types/babel__generator": "^7.27.0",
96
117
  "@types/babel__traverse": "^7.28.0",
@@ -105,22 +126,23 @@
105
126
  "esbuild-register": "^3.6.0",
106
127
  "execa": "9.6.1",
107
128
  "fast-glob": "3.3.3",
129
+ "prettier": "^3.8.3",
108
130
  "tsdown": "^0.22.0",
109
131
  "typescript": "^6.0.3",
110
132
  "vite": "^7.0.0",
111
133
  "wireit": "0.14.12",
112
134
  "wrangler": "^4.95.0",
113
- "@react-router/serve": "0.0.0-experimental-abd9fc79b",
114
- "react-router": "^0.0.0-experimental-abd9fc79b"
135
+ "@react-router/serve": "0.0.0-experimental-2a01b6fd8",
136
+ "react-router": "^0.0.0-experimental-2a01b6fd8"
115
137
  },
116
138
  "peerDependencies": {
117
139
  "@vitejs/plugin-rsc": "~0.5.26",
118
140
  "react-server-dom-webpack": "^19.2.7",
119
- "typescript": "^5.1.0 || ^6.0.0",
141
+ "typescript": "^5.1.0 || ^6.0.0 || ^7.0.0",
120
142
  "vite": "^7.0.0 || ^8.0.0",
121
143
  "wrangler": "^4.0.0",
122
- "react-router": "^0.0.0-experimental-abd9fc79b",
123
- "@react-router/serve": "^0.0.0-experimental-abd9fc79b"
144
+ "react-router": "^0.0.0-experimental-2a01b6fd8",
145
+ "@react-router/serve": "^0.0.0-experimental-2a01b6fd8"
124
146
  },
125
147
  "peerDependenciesMeta": {
126
148
  "@vitejs/plugin-rsc": {
@@ -144,6 +166,7 @@
144
166
  },
145
167
  "files": [
146
168
  "dist/",
169
+ "development-condition-enabled/",
147
170
  "module-sync-enabled/",
148
171
  "bin.cjs",
149
172
  "rsc-types.d.ts",
package/rsc-types.d.ts CHANGED
@@ -15,6 +15,11 @@ declare module "virtual:react-router/unstable_rsc/ssr" {
15
15
  export default ssr;
16
16
  }
17
17
 
18
+ declare module "virtual:react-router/unstable_rsc/subresource-integrity" {
19
+ const subResourceIntegrity: Record<string, string> | undefined;
20
+ export default subResourceIntegrity;
21
+ }
22
+
18
23
  declare module "virtual:react-router/unstable_rsc/react-router-serve-config" {
19
24
  const unstable_reactRouterServeConfig: {
20
25
  publicPath: string;
@@ -35,4 +40,9 @@ declare module "virtual:react-router/unstable_rsc/route-discovery" {
35
40
  export default routeDiscovery;
36
41
  }
37
42
 
43
+ declare module "virtual:react-router/unstable_rsc/client-version" {
44
+ const clientVersion: string;
45
+ export default clientVersion;
46
+ }
47
+
38
48
  declare module "virtual:react-router/unstable_rsc/inject-hmr-runtime" {}