@react-router/dev 8.1.0 → 8.2.0

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,30 @@
1
1
  # `@react-router/dev`
2
2
 
3
+ ## v8.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add a Web Streams default server entry for non-Node Framework mode apps ([#15290](https://github.com/remix-run/react-router/pull/15290))
8
+ - Apps using `@react-router/node`, `@react-router/express`, or `@react-router/serve` continue to use the `renderToPipeableStream` default server entry
9
+ - Apps without those Node server adapter dependencies use a `renderToReadableStream` default server entry
10
+ - 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
11
+ - Detect `nub` as a supported package manager when installing framework dependencies ([#15276](https://github.com/remix-run/react-router/pull/15276))
12
+
13
+ ### Patch Changes
14
+
15
+ - Detect user `rolldownOptions` config in Vite 8+ ([#15278](https://github.com/remix-run/react-router/pull/15278))
16
+
17
+ ### Unstable Changes
18
+
19
+ ⚠️ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_
20
+
21
+ - 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))
22
+ - This flag has no effect if you have your own `entry.server.tsx`
23
+ - Updated dependencies:
24
+ - [`react-router@8.2.0`](https://github.com/remix-run/react-router/releases/tag/react-router@8.2.0)
25
+ - [`@react-router/node@8.2.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@8.2.0)
26
+ - [`@react-router/serve@8.2.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/serve@8.2.0)
27
+
3
28
  ## v8.1.0
4
29
 
5
30
  ### Patch Changes
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v8.1.0
2
+ * @react-router/dev v8.2.0
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-BdAwfkLl.js";
11
+ import { _ as getVite, v as preloadVite } from "./typegen-CNpfI4aI.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 v8.1.0
3
+ * @react-router/dev v8.2.0
4
4
  *
5
5
  * Copyright (c) Remix Software Inc.
6
6
  *
@@ -9,7 +9,7 @@
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-BdAwfkLl.js";
12
+ import { _ as getVite, l as hasNodeDependency, n as watch, t as run$1, u as loadConfig, v as preloadVite } from "../typegen-CNpfI4aI.js";
13
13
  import { createRequire } from "node:module";
14
14
  import { parseArgs } from "node:util";
15
15
  import semver from "semver";
@@ -141,7 +141,7 @@ async function routes(rootDirectory, flags = {}) {
141
141
  }
142
142
  async function build(root, options = {}) {
143
143
  root = resolveRootDirectory(root, options);
144
- let { build } = await import("../build-9Do1SAoZ.js");
144
+ let { build } = await import("../build-r3D94V53.js");
145
145
  if (options.profile) await start();
146
146
  try {
147
147
  await build(root, options);
@@ -150,7 +150,7 @@ async function build(root, options = {}) {
150
150
  }
151
151
  }
152
152
  async function dev(root, options = {}) {
153
- let { dev } = await import("../dev-Bnoz8B89.js");
153
+ let { dev } = await import("../dev-BfZiothP.js");
154
154
  if (options.profile) await start();
155
155
  exitHook(() => stop(console.info));
156
156
  root = resolveRootDirectory(root, options);
@@ -231,12 +231,9 @@ async function generateEntry(entry, rootDirectory, flags = {}) {
231
231
  }
232
232
  await copyFile(defaultEntry, outputFile);
233
233
  } else {
234
- if (!((await readPackageJSON(rootDirectory)).dependencies ?? {})["@react-router/node"]) {
235
- console.error(colors.red(`No default server entry detected.`));
236
- return;
237
- }
234
+ let pkgJson = await readPackageJSON(rootDirectory);
238
235
  let defaultEntryClient = path$1.resolve(defaultsDirectory, "entry.client.tsx");
239
- let defaultEntryServer = path$1.resolve(defaultsDirectory, `entry.server.node.tsx`);
236
+ let defaultEntryServer = path$1.resolve(defaultsDirectory, hasNodeDependency(pkgJson.dependencies) && !configResult.value.future.unstable_enableNodeReadableStream ? `entry.server.node.tsx` : `entry.server.web.tsx`);
240
237
  let isServerEntry = entry === "entry.server";
241
238
  let contents = isServerEntry ? await createServerEntry(rootDirectory, appDirectory, defaultEntryServer) : await createClientEntry(rootDirectory, appDirectory, defaultEntryClient);
242
239
  let useTypeScript = flags.typescript ?? true;
@@ -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 v8.1.0
2
+ * @react-router/dev v8.2.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v8.1.0
2
+ * @react-router/dev v8.2.0
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-BdAwfkLl.js";
11
+ import { _ as getVite, v as preloadVite } from "./typegen-CNpfI4aI.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 v8.1.0
2
+ * @react-router/dev v8.2.0
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 v8.1.0
2
+ * @react-router/dev v8.2.0
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-DGRRTCk8.js";
11
+ import { a as prefix, i as layout, n as getAppDirectory, o as relative, r as index, s as route } from "./routes-CdiIrVIn.js";
12
12
  export { getAppDirectory, index, layout, prefix, relative, route };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v8.1.0
2
+ * @react-router/dev v8.2.0
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-DGRRTCk8.js";
11
+ import { c as setAppDirectory, l as validateRouteConfig, t as configRoutesToRouteManifest, u as invariant } from "./routes-CdiIrVIn.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() ? [
@@ -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
  }
@@ -866,4 +882,4 @@ async function watch(rootDirectory, { mode, logger, rsc }) {
866
882
  return { close: async () => await ctx.configLoader.close() };
867
883
  }
868
884
  //#endregion
869
- 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 v8.1.0
2
+ * @react-router/dev v8.2.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -8,8 +8,8 @@
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-BdAwfkLl.js";
12
- import { u as invariant } from "./routes-DGRRTCk8.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-CNpfI4aI.js";
12
+ import { u as invariant } from "./routes-CdiIrVIn.js";
13
13
  import { createRequire } from "node:module";
14
14
  import colors from "picocolors";
15
15
  import { existsSync, readFileSync, readdirSync } from "node:fs";
@@ -2582,7 +2582,7 @@ async function getEnvironmentOptionsResolvers(ctx, viteCommand) {
2582
2582
  let maybeDevelopmentConditions = viteCommand === "build" ? [] : ["development"];
2583
2583
  let maybeDefaultServerConditions = vite.defaultServerConditions || [];
2584
2584
  let defaultExternalConditions = vite.defaultExternalConditions ?? ["node"];
2585
- if (!(!pkgJson.dependencies || pkgJson.dependencies["@react-router/node"] || pkgJson.dependencies["@react-router/express"] || pkgJson.dependencies["@react-router/serve"])) {
2585
+ if (!hasNodeDependency(pkgJson.dependencies)) {
2586
2586
  maybeDefaultServerConditions = maybeDefaultServerConditions.filter((c) => c !== "node");
2587
2587
  defaultExternalConditions = defaultExternalConditions.filter((c) => c !== "node");
2588
2588
  }
@@ -2597,7 +2597,7 @@ async function getEnvironmentOptionsResolvers(ctx, viteCommand) {
2597
2597
  ssrEmitAssets: true,
2598
2598
  copyPublicDir: false,
2599
2599
  rollupOptions: {
2600
- input: viteUserConfig.environments?.ssr?.build?.rollupOptions?.input ?? virtual$1.serverBuild.id,
2600
+ input: getUserBuildRollupOptions(viteUserConfig.environments?.ssr)?.input ?? virtual$1.serverBuild.id,
2601
2601
  output: {
2602
2602
  entryFileNames: serverBuildFile,
2603
2603
  format: serverModuleFormat
@@ -2614,7 +2614,7 @@ async function getEnvironmentOptionsResolvers(ctx, viteCommand) {
2614
2614
  let code = readFileSync(routeFilePath, "utf-8");
2615
2615
  return [`${routeFilePath}${BUILD_CLIENT_ROUTE_QUERY_STRING}`, ...ctx.reactRouterConfig.splitRouteModules && !isRootRoute ? routeChunkExportNames.map((exportName) => code.includes(exportName) ? getRouteChunkModuleId(routeFilePath, exportName) : null) : []].filter(isNonNullable);
2616
2616
  })],
2617
- output: viteUserConfig?.environments?.client?.build?.rollupOptions?.output ?? { entryFileNames: ({ moduleIds }) => {
2617
+ output: getUserBuildRollupOptions(viteUserConfig?.environments?.client)?.output ?? { entryFileNames: ({ moduleIds }) => {
2618
2618
  let routeChunkModuleId = moduleIds.find(isRouteChunkModuleId);
2619
2619
  let routeChunkName = routeChunkModuleId ? getRouteChunkNameFromModuleId(routeChunkModuleId)?.replace("unstable_", "") : null;
2620
2620
  let routeChunkSuffix = routeChunkName ? `-${kebabCase(routeChunkName)}` : "";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@react-router/dev",
3
3
  "type": "module",
4
- "version": "8.1.0",
4
+ "version": "8.2.0",
5
5
  "description": "Dev tools and CLI for React Router",
6
6
  "keywords": [
7
7
  "react",
@@ -105,7 +105,7 @@
105
105
  "semver": "^7.8.1",
106
106
  "tinyglobby": "^0.2.16",
107
107
  "valibot": "^1.4.1",
108
- "@react-router/node": "8.1.0"
108
+ "@react-router/node": "8.2.0"
109
109
  },
110
110
  "devDependencies": {
111
111
  "@types/babel__core": "^7.20.5",
@@ -127,8 +127,8 @@
127
127
  "vite": "^7.0.0",
128
128
  "wireit": "0.14.12",
129
129
  "wrangler": "^4.95.0",
130
- "@react-router/serve": "8.1.0",
131
- "react-router": "^8.1.0"
130
+ "@react-router/serve": "8.2.0",
131
+ "react-router": "^8.2.0"
132
132
  },
133
133
  "peerDependencies": {
134
134
  "@vitejs/plugin-rsc": "~0.5.26",
@@ -136,8 +136,8 @@
136
136
  "typescript": "^5.1.0 || ^6.0.0",
137
137
  "vite": "^7.0.0 || ^8.0.0",
138
138
  "wrangler": "^4.0.0",
139
- "react-router": "^8.1.0",
140
- "@react-router/serve": "^8.1.0"
139
+ "@react-router/serve": "^8.2.0",
140
+ "react-router": "^8.2.0"
141
141
  },
142
142
  "peerDependenciesMeta": {
143
143
  "@vitejs/plugin-rsc": {