@react-router/dev 0.0.0-experimental-56b72276e → 0.0.0-experimental-58b44aa83

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,65 @@
1
1
  # `@react-router/dev`
2
2
 
3
+ ## 7.13.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Fix `react-router dev` crash when Unix socket files exist in the project root ([#14854](https://github.com/remix-run/react-router/pull/14854))
8
+
9
+ - Escape redirect locations in prerendered redirect HTML ([#14880](https://github.com/remix-run/react-router/pull/14880))
10
+
11
+ - Add `future.unstable_passThroughRequests` flag ([#14775](https://github.com/remix-run/react-router/pull/14775))
12
+
13
+ By default, React Router normalizes the `request.url` passed to your `loader`, `action`, and `middleware` functions by removing React Router's internal implementation details (`.data` suffixes, `index` + `_routes` query params).
14
+
15
+ Enabling this flag removes that normalization and passes the raw HTTP `request` instance to your handlers. This provides a few benefits:
16
+ - Reduces server-side overhead by eliminating multiple `new Request()` calls on the critical path
17
+ - Allows you to distinguish document from data requests in your handlers base don the presence of a `.data` suffix (useful for observability purposes)
18
+
19
+ If you were previously relying on the normalization of `request.url`, you can switch to use the new sibling `unstable_url` parameter which contains a `URL` instance representing the normalized location:
20
+
21
+ ```tsx
22
+ // ❌ Before: you could assume there was no `.data` suffix in `request.url`
23
+ export async function loader({ request }: Route.LoaderArgs) {
24
+ let url = new URL(request.url);
25
+ if (url.pathname === "/path") {
26
+ // This check will fail with the flag enabled because the `.data` suffix will
27
+ // exist on data requests
28
+ }
29
+ }
30
+
31
+ // ✅ After: use `unstable_url` for normalized routing logic and `request.url`
32
+ // for raw routing logic
33
+ export async function loader({ request, unstable_url }: Route.LoaderArgs) {
34
+ if (unstable_url.pathname === "/path") {
35
+ // This will always have the `.data` suffix stripped
36
+ }
37
+
38
+ // And now you can distinguish between document versus data requests
39
+ let isDataRequest = new URL(request.url).pathname.endsWith(".data");
40
+ }
41
+ ```
42
+
43
+ - Add a new `unstable_url: URL` parameter to route handler methods (`loader`, `action`, `middleware`, etc.) representing the normalized URL the application is navigating to or fetching, with React Router implementation details removed (`.data`suffix, `index`/`_routes` query params) ([#14775](https://github.com/remix-run/react-router/pull/14775))
44
+
45
+ This is being added alongside the new `future.unstable_passthroughRequests` future flag so that users still have a way to access the normalized URL when that flag is enabled and non-normalized `request`'s are being passed to your handlers. When adopting this flag, you will only need to start leveraging this new parameter if you are relying on the normalization of `request.url` in your application code.
46
+
47
+ If you don't have the flag enabled, then `unstable_url` will match `request.url`.
48
+
49
+ - Updated dependencies:
50
+ - `react-router@7.13.2`
51
+ - `@react-router/node@7.13.2`
52
+ - `@react-router/serve@7.13.2`
53
+
54
+ ## 7.13.1
55
+
56
+ ### Patch Changes
57
+
58
+ - Updated dependencies:
59
+ - `react-router@7.13.1`
60
+ - `@react-router/node@7.13.1`
61
+ - `@react-router/serve@7.13.1`
62
+
3
63
  ## 7.13.0
4
64
 
5
65
  ### Patch Changes
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-56b72276e
3
+ * @react-router/dev v0.0.0-experimental-58b44aa83
4
4
  *
5
5
  * Copyright (c) Remix Software Inc.
6
6
  *
@@ -505,12 +505,13 @@ async function resolveConfig({
505
505
  }
506
506
  let future = {
507
507
  unstable_optimizeDeps: userAndPresetConfigs.future?.unstable_optimizeDeps ?? false,
508
+ unstable_passThroughRequests: userAndPresetConfigs.future?.unstable_passThroughRequests ?? false,
508
509
  unstable_subResourceIntegrity: userAndPresetConfigs.future?.unstable_subResourceIntegrity ?? false,
509
510
  unstable_trailingSlashAwareDataRequests: userAndPresetConfigs.future?.unstable_trailingSlashAwareDataRequests ?? false,
510
511
  unstable_previewServerPrerendering: userAndPresetConfigs.future?.unstable_previewServerPrerendering ?? false,
511
512
  v8_middleware: userAndPresetConfigs.future?.v8_middleware ?? false,
512
513
  v8_splitRouteModules: userAndPresetConfigs.future?.v8_splitRouteModules ?? false,
513
- v8_viteEnvironmentApi: userAndPresetConfigs.future?.v8_viteEnvironmentApi ?? false
514
+ v8_viteEnvironmentApi: (userAndPresetConfigs.future?.v8_viteEnvironmentApi || userAndPresetConfigs.future?.unstable_previewServerPrerendering) ?? false
514
515
  };
515
516
  let allowedActionOrigins = userAndPresetConfigs.allowedActionOrigins ?? false;
516
517
  let reactRouterConfig = deepFreeze({
@@ -586,13 +587,11 @@ async function createConfigLoader({
586
587
  if (!fsWatcher) {
587
588
  fsWatcher = import_chokidar.default.watch([root, appDirectory], {
588
589
  ignoreInitial: true,
589
- ignored: (path10) => {
590
- let dirname5 = import_pathe3.default.dirname(path10);
591
- return !dirname5.startsWith(appDirectory) && // Ensure we're only watching files outside of the app directory
592
- // that are at the root level, not nested in subdirectories
593
- path10 !== root && // Watch the root directory itself
594
- dirname5 !== root;
595
- }
590
+ ignored: (path10) => isIgnoredByWatcher(path10, { root, appDirectory })
591
+ });
592
+ fsWatcher.on("error", (error) => {
593
+ let message = error instanceof Error ? error.message : String(error);
594
+ console.warn(import_picocolors.default.yellow(`File watcher error: ${message}`));
596
595
  });
597
596
  fsWatcher.on("all", async (...args) => {
598
597
  let [event, rawFilepath] = args;
@@ -733,6 +732,25 @@ function isEntryFileDependency(moduleGraph, entryFilepath, filepath, visited = /
733
732
  }
734
733
  return false;
735
734
  }
735
+ function isIgnoredByWatcher(path10, { root, appDirectory }) {
736
+ let dirname5 = import_pathe3.default.dirname(path10);
737
+ let ignoredByPath = !dirname5.startsWith(appDirectory) && // Ensure we're only watching files outside of the app directory
738
+ // that are at the root level, not nested in subdirectories
739
+ path10 !== root && // Watch the root directory itself
740
+ dirname5 !== root;
741
+ if (ignoredByPath) {
742
+ return true;
743
+ }
744
+ try {
745
+ let stat = import_node_fs.default.statSync(path10, { throwIfNoEntry: false });
746
+ if (stat && !stat.isFile() && !stat.isDirectory()) {
747
+ return true;
748
+ }
749
+ } catch {
750
+ return true;
751
+ }
752
+ return false;
753
+ }
736
754
  var import_node_fs, import_node_child_process, import_pathe3, import_chokidar, import_picocolors, import_pick2, import_omit, import_cloneDeep, import_isEqual, excludedConfigPresetKeys, mergeReactRouterConfig, deepFreeze, entryExts;
737
755
  var init_config = __esm({
738
756
  "config/config.ts"() {
@@ -1165,7 +1183,7 @@ function getRouteAnnotations({
1165
1183
  module: Module
1166
1184
  }>
1167
1185
  ` + "\n\n" + generate(matchesType).code + "\n\n" + import_dedent.default`
1168
- type Annotations = GetAnnotations<Info & { module: Module, matches: Matches }, ${ctx.rsc}>;
1186
+ type Annotations = GetAnnotations<Info & { module: Module, matches: Matches }>;
1169
1187
 
1170
1188
  export namespace Route {
1171
1189
  // links
@@ -1202,11 +1220,20 @@ function getRouteAnnotations({
1202
1220
  // HydrateFallback
1203
1221
  export type HydrateFallbackProps = Annotations["HydrateFallbackProps"];
1204
1222
 
1223
+ // ServerHydrateFallback
1224
+ export type ServerHydrateFallbackProps = Annotations["ServerHydrateFallbackProps"];
1225
+
1205
1226
  // Component
1206
1227
  export type ComponentProps = Annotations["ComponentProps"];
1207
1228
 
1229
+ // ServerComponent
1230
+ export type ServerComponentProps = Annotations["ServerComponentProps"];
1231
+
1208
1232
  // ErrorBoundary
1209
1233
  export type ErrorBoundaryProps = Annotations["ErrorBoundaryProps"];
1234
+
1235
+ // ServerErrorBoundary
1236
+ export type ServerErrorBoundaryProps = Annotations["ServerErrorBoundaryProps"];
1210
1237
  }
1211
1238
  `;
1212
1239
  return { filename: filename2, content };
@@ -1352,7 +1379,8 @@ async function hasReactRouterRscPlugin({
1352
1379
  root,
1353
1380
  viteBuildOptions: { config, logLevel, mode }
1354
1381
  }) {
1355
- const vite2 = await import("vite");
1382
+ await preloadVite();
1383
+ const vite2 = getVite();
1356
1384
  const viteConfig = await vite2.resolveConfig(
1357
1385
  {
1358
1386
  configFile: config,
@@ -1374,6 +1402,7 @@ async function hasReactRouterRscPlugin({
1374
1402
  var init_has_rsc_plugin = __esm({
1375
1403
  "vite/has-rsc-plugin.ts"() {
1376
1404
  "use strict";
1405
+ init_vite();
1377
1406
  }
1378
1407
  });
1379
1408
 
@@ -2288,12 +2317,16 @@ async function dev2(root, options = {}) {
2288
2317
  var clientEntries = ["entry.client.tsx", "entry.client.js", "entry.client.jsx"];
2289
2318
  var serverEntries = ["entry.server.tsx", "entry.server.js", "entry.server.jsx"];
2290
2319
  var entries = ["entry.client", "entry.server"];
2320
+ var rscEntries = ["entry.client", "entry.rsc", "entry.ssr"];
2291
2321
  var conjunctionListFormat = new Intl.ListFormat("en", {
2292
2322
  style: "long",
2293
2323
  type: "conjunction"
2294
2324
  });
2295
2325
  async function generateEntry(entry, rootDirectory, flags = {}) {
2296
2326
  rootDirectory = resolveRootDirectory(rootDirectory, flags);
2327
+ let configDir = "defaults";
2328
+ let entriesToUse = entries;
2329
+ let isRsc = false;
2297
2330
  if (await hasReactRouterRscPlugin({
2298
2331
  root: rootDirectory,
2299
2332
  viteBuildOptions: {
@@ -2301,12 +2334,15 @@ async function generateEntry(entry, rootDirectory, flags = {}) {
2301
2334
  mode: flags.mode
2302
2335
  }
2303
2336
  })) {
2304
- console.error(
2305
- import_picocolors8.default.red(
2306
- `The reveal command is currently not supported in RSC Framework Mode.`
2307
- )
2308
- );
2309
- process.exit(1);
2337
+ if (!entry) {
2338
+ await generateEntry("entry.client", rootDirectory, flags);
2339
+ await generateEntry("entry.rsc", rootDirectory, flags);
2340
+ await generateEntry("entry.ssr", rootDirectory, flags);
2341
+ return;
2342
+ }
2343
+ configDir = "default-rsc-entries";
2344
+ entriesToUse = rscEntries;
2345
+ isRsc = true;
2310
2346
  }
2311
2347
  if (!entry) {
2312
2348
  await generateEntry("entry.client", rootDirectory, flags);
@@ -2322,46 +2358,65 @@ async function generateEntry(entry, rootDirectory, flags = {}) {
2322
2358
  return;
2323
2359
  }
2324
2360
  let appDirectory = configResult.value.appDirectory;
2325
- if (!entries.includes(entry)) {
2326
- let entriesArray = Array.from(entries);
2361
+ if (!entriesToUse.includes(entry)) {
2362
+ let entriesArray = Array.from(entriesToUse);
2327
2363
  let list = conjunctionListFormat.format(entriesArray);
2328
2364
  console.error(
2329
2365
  import_picocolors8.default.red(`Invalid entry file. Valid entry files are ${list}`)
2330
2366
  );
2331
2367
  return;
2332
2368
  }
2333
- let { readPackageJSON } = await import("pkg-types");
2334
- let pkgJson = await readPackageJSON(rootDirectory);
2335
- let deps = pkgJson.dependencies ?? {};
2336
- if (!deps["@react-router/node"]) {
2337
- console.error(import_picocolors8.default.red(`No default server entry detected.`));
2338
- return;
2339
- }
2340
2369
  let defaultsDirectory = path9.resolve(
2341
2370
  path9.dirname(require.resolve("@react-router/dev/package.json")),
2342
2371
  "dist",
2343
2372
  "config",
2344
- "defaults"
2345
- );
2346
- let defaultEntryClient = path9.resolve(defaultsDirectory, "entry.client.tsx");
2347
- let defaultEntryServer = path9.resolve(
2348
- defaultsDirectory,
2349
- `entry.server.node.tsx`
2373
+ configDir
2350
2374
  );
2351
- let isServerEntry = entry === "entry.server";
2352
- let contents = isServerEntry ? await createServerEntry(rootDirectory, appDirectory, defaultEntryServer) : await createClientEntry(rootDirectory, appDirectory, defaultEntryClient);
2353
- let useTypeScript = flags.typescript ?? true;
2354
- let outputExtension = useTypeScript ? "tsx" : "jsx";
2355
- let outputEntry = `${entry}.${outputExtension}`;
2356
- let outputFile = path9.resolve(appDirectory, outputEntry);
2357
- if (!useTypeScript) {
2358
- let javascript = await transpile(contents, {
2359
- cwd: rootDirectory,
2360
- filename: isServerEntry ? defaultEntryServer : defaultEntryClient
2361
- });
2362
- await (0, import_promises4.writeFile)(outputFile, javascript, "utf-8");
2375
+ let outputFile;
2376
+ if (isRsc) {
2377
+ let defaultEntry = path9.resolve(defaultsDirectory, `${entry}.tsx`);
2378
+ outputFile = path9.resolve(appDirectory, `${entry}.tsx`);
2379
+ if ((0, import_node_fs4.existsSync)(outputFile)) {
2380
+ let relative7 = path9.relative(rootDirectory, outputFile);
2381
+ console.error(import_picocolors8.default.red(`Entry file ${relative7} already exists.`));
2382
+ return;
2383
+ }
2384
+ await (0, import_promises4.copyFile)(defaultEntry, outputFile);
2363
2385
  } else {
2364
- await (0, import_promises4.writeFile)(outputFile, contents, "utf-8");
2386
+ let { readPackageJSON } = await import("pkg-types");
2387
+ let pkgJson = await readPackageJSON(rootDirectory);
2388
+ let deps = pkgJson.dependencies ?? {};
2389
+ if (!deps["@react-router/node"]) {
2390
+ console.error(import_picocolors8.default.red(`No default server entry detected.`));
2391
+ return;
2392
+ }
2393
+ let defaultEntryClient = path9.resolve(
2394
+ defaultsDirectory,
2395
+ "entry.client.tsx"
2396
+ );
2397
+ let defaultEntryServer = path9.resolve(
2398
+ defaultsDirectory,
2399
+ `entry.server.node.tsx`
2400
+ );
2401
+ let isServerEntry = entry === "entry.server";
2402
+ let contents = isServerEntry ? await createServerEntry(rootDirectory, appDirectory, defaultEntryServer) : await createClientEntry(
2403
+ rootDirectory,
2404
+ appDirectory,
2405
+ defaultEntryClient
2406
+ );
2407
+ let useTypeScript = flags.typescript ?? true;
2408
+ let outputExtension = useTypeScript ? "tsx" : "jsx";
2409
+ let outputEntry = `${entry}.${outputExtension}`;
2410
+ outputFile = path9.resolve(appDirectory, outputEntry);
2411
+ if (!useTypeScript) {
2412
+ let javascript = await transpile(contents, {
2413
+ cwd: rootDirectory,
2414
+ filename: isServerEntry ? defaultEntryServer : defaultEntryClient
2415
+ });
2416
+ await (0, import_promises4.writeFile)(outputFile, javascript, "utf-8");
2417
+ } else {
2418
+ await (0, import_promises4.writeFile)(outputFile, contents, "utf-8");
2419
+ }
2365
2420
  }
2366
2421
  console.log(
2367
2422
  import_picocolors8.default.blue(
@@ -24,7 +24,6 @@ setServerCallback(
24
24
  );
25
25
 
26
26
  createFromReadableStream<RSCPayload>(getRSCStream()).then((payload) => {
27
- // @ts-expect-error - on 18 types, requires 19.
28
27
  startTransition(async () => {
29
28
  const formState =
30
29
  payload.type === "render" ? await payload.formState : undefined;
@@ -33,12 +32,11 @@ createFromReadableStream<RSCPayload>(getRSCStream()).then((payload) => {
33
32
  document,
34
33
  <StrictMode>
35
34
  <RSCHydratedRouter
36
- payload={payload}
37
35
  createFromReadableStream={createFromReadableStream}
36
+ payload={payload}
38
37
  />
39
38
  </StrictMode>,
40
39
  {
41
- // @ts-expect-error - no types for this yet
42
40
  formState,
43
41
  },
44
42
  );
@@ -13,6 +13,7 @@ import {
13
13
 
14
14
  // Import the routes generated by routes.ts
15
15
  import routes from "virtual:react-router/unstable_rsc/routes";
16
+ import routeDiscovery from "virtual:react-router/unstable_rsc/route-discovery";
16
17
  import basename from "virtual:react-router/unstable_rsc/basename";
17
18
  import unstable_reactRouterServeConfig from "virtual:react-router/unstable_rsc/react-router-serve-config";
18
19
 
@@ -35,6 +36,8 @@ export function fetchServer(
35
36
  requestContext,
36
37
  // The app routes.
37
38
  routes,
39
+ // The route discovery configuration.
40
+ routeDiscovery,
38
41
  // Encode the match with the React Server implementation.
39
42
  generateResponse(match, options) {
40
43
  return new Response(renderToReadableStream(match.payload, options), {
@@ -1,5 +1,4 @@
1
1
  import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
2
- // @ts-expect-error - no types for this, can import from root once on latest 19
3
2
  import { renderToReadableStream } from "react-dom/server.edge";
4
3
  import {
5
4
  unstable_routeRSCServerRequest as routeRSCServerRequest,
package/dist/config.d.ts CHANGED
@@ -38,6 +38,7 @@ type ServerBundlesBuildManifest = BaseBuildManifest & {
38
38
  type ServerModuleFormat = "esm" | "cjs";
39
39
  interface FutureConfig {
40
40
  unstable_optimizeDeps: boolean;
41
+ unstable_passThroughRequests: boolean;
41
42
  unstable_subResourceIntegrity: boolean;
42
43
  unstable_trailingSlashAwareDataRequests: boolean;
43
44
  /**
package/dist/config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-56b72276e
2
+ * @react-router/dev v0.0.0-experimental-58b44aa83
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-56b72276e
2
+ * @react-router/dev v0.0.0-experimental-58b44aa83
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-56b72276e
2
+ * @react-router/dev v0.0.0-experimental-58b44aa83
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -531,12 +531,13 @@ async function resolveConfig({
531
531
  }
532
532
  let future = {
533
533
  unstable_optimizeDeps: userAndPresetConfigs.future?.unstable_optimizeDeps ?? false,
534
+ unstable_passThroughRequests: userAndPresetConfigs.future?.unstable_passThroughRequests ?? false,
534
535
  unstable_subResourceIntegrity: userAndPresetConfigs.future?.unstable_subResourceIntegrity ?? false,
535
536
  unstable_trailingSlashAwareDataRequests: userAndPresetConfigs.future?.unstable_trailingSlashAwareDataRequests ?? false,
536
537
  unstable_previewServerPrerendering: userAndPresetConfigs.future?.unstable_previewServerPrerendering ?? false,
537
538
  v8_middleware: userAndPresetConfigs.future?.v8_middleware ?? false,
538
539
  v8_splitRouteModules: userAndPresetConfigs.future?.v8_splitRouteModules ?? false,
539
- v8_viteEnvironmentApi: userAndPresetConfigs.future?.v8_viteEnvironmentApi ?? false
540
+ v8_viteEnvironmentApi: (userAndPresetConfigs.future?.v8_viteEnvironmentApi || userAndPresetConfigs.future?.unstable_previewServerPrerendering) ?? false
540
541
  };
541
542
  let allowedActionOrigins = userAndPresetConfigs.allowedActionOrigins ?? false;
542
543
  let reactRouterConfig = deepFreeze({
@@ -612,13 +613,11 @@ async function createConfigLoader({
612
613
  if (!fsWatcher) {
613
614
  fsWatcher = import_chokidar.default.watch([root, appDirectory], {
614
615
  ignoreInitial: true,
615
- ignored: (path3) => {
616
- let dirname = import_pathe3.default.dirname(path3);
617
- return !dirname.startsWith(appDirectory) && // Ensure we're only watching files outside of the app directory
618
- // that are at the root level, not nested in subdirectories
619
- path3 !== root && // Watch the root directory itself
620
- dirname !== root;
621
- }
616
+ ignored: (path3) => isIgnoredByWatcher(path3, { root, appDirectory })
617
+ });
618
+ fsWatcher.on("error", (error) => {
619
+ let message = error instanceof Error ? error.message : String(error);
620
+ console.warn(import_picocolors.default.yellow(`File watcher error: ${message}`));
622
621
  });
623
622
  fsWatcher.on("all", async (...args) => {
624
623
  let [event, rawFilepath] = args;
@@ -760,6 +759,25 @@ function isEntryFileDependency(moduleGraph, entryFilepath, filepath, visited = /
760
759
  }
761
760
  return false;
762
761
  }
762
+ function isIgnoredByWatcher(path3, { root, appDirectory }) {
763
+ let dirname = import_pathe3.default.dirname(path3);
764
+ let ignoredByPath = !dirname.startsWith(appDirectory) && // Ensure we're only watching files outside of the app directory
765
+ // that are at the root level, not nested in subdirectories
766
+ path3 !== root && // Watch the root directory itself
767
+ dirname !== root;
768
+ if (ignoredByPath) {
769
+ return true;
770
+ }
771
+ try {
772
+ let stat = import_node_fs.default.statSync(path3, { throwIfNoEntry: false });
773
+ if (stat && !stat.isFile() && !stat.isDirectory()) {
774
+ return true;
775
+ }
776
+ } catch {
777
+ return true;
778
+ }
779
+ return false;
780
+ }
763
781
 
764
782
  // vite/cloudflare-dev-proxy.ts
765
783
  var serverBuildId = "virtual:react-router/server-build";