@react-router/dev 0.0.0-experimental-567b27f71 → 0.0.0-experimental-cb0fdf114

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,56 @@
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
+
3
54
  ## 7.13.1
4
55
 
5
56
  ### 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-567b27f71
3
+ * @react-router/dev v0.0.0-experimental-cb0fdf114
4
4
  *
5
5
  * Copyright (c) Remix Software Inc.
6
6
  *
@@ -511,7 +511,7 @@ async function resolveConfig({
511
511
  unstable_previewServerPrerendering: userAndPresetConfigs.future?.unstable_previewServerPrerendering ?? false,
512
512
  v8_middleware: userAndPresetConfigs.future?.v8_middleware ?? false,
513
513
  v8_splitRouteModules: userAndPresetConfigs.future?.v8_splitRouteModules ?? false,
514
- v8_viteEnvironmentApi: userAndPresetConfigs.future?.v8_viteEnvironmentApi ?? false
514
+ v8_viteEnvironmentApi: (userAndPresetConfigs.future?.v8_viteEnvironmentApi || userAndPresetConfigs.future?.unstable_previewServerPrerendering) ?? false
515
515
  };
516
516
  let allowedActionOrigins = userAndPresetConfigs.allowedActionOrigins ?? false;
517
517
  let reactRouterConfig = deepFreeze({
@@ -587,13 +587,11 @@ async function createConfigLoader({
587
587
  if (!fsWatcher) {
588
588
  fsWatcher = import_chokidar.default.watch([root, appDirectory], {
589
589
  ignoreInitial: true,
590
- ignored: (path10) => {
591
- let dirname5 = import_pathe3.default.dirname(path10);
592
- return !dirname5.startsWith(appDirectory) && // Ensure we're only watching files outside of the app directory
593
- // that are at the root level, not nested in subdirectories
594
- path10 !== root && // Watch the root directory itself
595
- dirname5 !== root;
596
- }
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}`));
597
595
  });
598
596
  fsWatcher.on("all", async (...args) => {
599
597
  let [event, rawFilepath] = args;
@@ -734,6 +732,25 @@ function isEntryFileDependency(moduleGraph, entryFilepath, filepath, visited = /
734
732
  }
735
733
  return false;
736
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
+ }
737
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;
738
755
  var init_config = __esm({
739
756
  "config/config.ts"() {
@@ -1166,7 +1183,7 @@ function getRouteAnnotations({
1166
1183
  module: Module
1167
1184
  }>
1168
1185
  ` + "\n\n" + generate(matchesType).code + "\n\n" + import_dedent.default`
1169
- type Annotations = GetAnnotations<Info & { module: Module, matches: Matches }, ${ctx.rsc}>;
1186
+ type Annotations = GetAnnotations<Info & { module: Module, matches: Matches }>;
1170
1187
 
1171
1188
  export namespace Route {
1172
1189
  // links
@@ -1203,11 +1220,20 @@ function getRouteAnnotations({
1203
1220
  // HydrateFallback
1204
1221
  export type HydrateFallbackProps = Annotations["HydrateFallbackProps"];
1205
1222
 
1223
+ // ServerHydrateFallback
1224
+ export type ServerHydrateFallbackProps = Annotations["ServerHydrateFallbackProps"];
1225
+
1206
1226
  // Component
1207
1227
  export type ComponentProps = Annotations["ComponentProps"];
1208
1228
 
1229
+ // ServerComponent
1230
+ export type ServerComponentProps = Annotations["ServerComponentProps"];
1231
+
1209
1232
  // ErrorBoundary
1210
1233
  export type ErrorBoundaryProps = Annotations["ErrorBoundaryProps"];
1234
+
1235
+ // ServerErrorBoundary
1236
+ export type ServerErrorBoundaryProps = Annotations["ServerErrorBoundaryProps"];
1211
1237
  }
1212
1238
  `;
1213
1239
  return { filename: filename2, content };
@@ -1353,7 +1379,8 @@ async function hasReactRouterRscPlugin({
1353
1379
  root,
1354
1380
  viteBuildOptions: { config, logLevel, mode }
1355
1381
  }) {
1356
- const vite2 = await import("vite");
1382
+ await preloadVite();
1383
+ const vite2 = getVite();
1357
1384
  const viteConfig = await vite2.resolveConfig(
1358
1385
  {
1359
1386
  configFile: config,
@@ -1375,6 +1402,7 @@ async function hasReactRouterRscPlugin({
1375
1402
  var init_has_rsc_plugin = __esm({
1376
1403
  "vite/has-rsc-plugin.ts"() {
1377
1404
  "use strict";
1405
+ init_vite();
1378
1406
  }
1379
1407
  });
1380
1408
 
@@ -2289,12 +2317,16 @@ async function dev2(root, options = {}) {
2289
2317
  var clientEntries = ["entry.client.tsx", "entry.client.js", "entry.client.jsx"];
2290
2318
  var serverEntries = ["entry.server.tsx", "entry.server.js", "entry.server.jsx"];
2291
2319
  var entries = ["entry.client", "entry.server"];
2320
+ var rscEntries = ["entry.client", "entry.rsc", "entry.ssr"];
2292
2321
  var conjunctionListFormat = new Intl.ListFormat("en", {
2293
2322
  style: "long",
2294
2323
  type: "conjunction"
2295
2324
  });
2296
2325
  async function generateEntry(entry, rootDirectory, flags = {}) {
2297
2326
  rootDirectory = resolveRootDirectory(rootDirectory, flags);
2327
+ let configDir = "defaults";
2328
+ let entriesToUse = entries;
2329
+ let isRsc = false;
2298
2330
  if (await hasReactRouterRscPlugin({
2299
2331
  root: rootDirectory,
2300
2332
  viteBuildOptions: {
@@ -2302,12 +2334,15 @@ async function generateEntry(entry, rootDirectory, flags = {}) {
2302
2334
  mode: flags.mode
2303
2335
  }
2304
2336
  })) {
2305
- console.error(
2306
- import_picocolors8.default.red(
2307
- `The reveal command is currently not supported in RSC Framework Mode.`
2308
- )
2309
- );
2310
- 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;
2311
2346
  }
2312
2347
  if (!entry) {
2313
2348
  await generateEntry("entry.client", rootDirectory, flags);
@@ -2323,46 +2358,65 @@ async function generateEntry(entry, rootDirectory, flags = {}) {
2323
2358
  return;
2324
2359
  }
2325
2360
  let appDirectory = configResult.value.appDirectory;
2326
- if (!entries.includes(entry)) {
2327
- let entriesArray = Array.from(entries);
2361
+ if (!entriesToUse.includes(entry)) {
2362
+ let entriesArray = Array.from(entriesToUse);
2328
2363
  let list = conjunctionListFormat.format(entriesArray);
2329
2364
  console.error(
2330
2365
  import_picocolors8.default.red(`Invalid entry file. Valid entry files are ${list}`)
2331
2366
  );
2332
2367
  return;
2333
2368
  }
2334
- let { readPackageJSON } = await import("pkg-types");
2335
- let pkgJson = await readPackageJSON(rootDirectory);
2336
- let deps = pkgJson.dependencies ?? {};
2337
- if (!deps["@react-router/node"]) {
2338
- console.error(import_picocolors8.default.red(`No default server entry detected.`));
2339
- return;
2340
- }
2341
2369
  let defaultsDirectory = path9.resolve(
2342
2370
  path9.dirname(require.resolve("@react-router/dev/package.json")),
2343
2371
  "dist",
2344
2372
  "config",
2345
- "defaults"
2346
- );
2347
- let defaultEntryClient = path9.resolve(defaultsDirectory, "entry.client.tsx");
2348
- let defaultEntryServer = path9.resolve(
2349
- defaultsDirectory,
2350
- `entry.server.node.tsx`
2373
+ configDir
2351
2374
  );
2352
- let isServerEntry = entry === "entry.server";
2353
- let contents = isServerEntry ? await createServerEntry(rootDirectory, appDirectory, defaultEntryServer) : await createClientEntry(rootDirectory, appDirectory, defaultEntryClient);
2354
- let useTypeScript = flags.typescript ?? true;
2355
- let outputExtension = useTypeScript ? "tsx" : "jsx";
2356
- let outputEntry = `${entry}.${outputExtension}`;
2357
- let outputFile = path9.resolve(appDirectory, outputEntry);
2358
- if (!useTypeScript) {
2359
- let javascript = await transpile(contents, {
2360
- cwd: rootDirectory,
2361
- filename: isServerEntry ? defaultEntryServer : defaultEntryClient
2362
- });
2363
- 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);
2364
2385
  } else {
2365
- 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
+ }
2366
2420
  }
2367
2421
  console.log(
2368
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.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-567b27f71
2
+ * @react-router/dev v0.0.0-experimental-cb0fdf114
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-567b27f71
2
+ * @react-router/dev v0.0.0-experimental-cb0fdf114
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-567b27f71
2
+ * @react-router/dev v0.0.0-experimental-cb0fdf114
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -537,7 +537,7 @@ async function resolveConfig({
537
537
  unstable_previewServerPrerendering: userAndPresetConfigs.future?.unstable_previewServerPrerendering ?? false,
538
538
  v8_middleware: userAndPresetConfigs.future?.v8_middleware ?? false,
539
539
  v8_splitRouteModules: userAndPresetConfigs.future?.v8_splitRouteModules ?? false,
540
- v8_viteEnvironmentApi: userAndPresetConfigs.future?.v8_viteEnvironmentApi ?? false
540
+ v8_viteEnvironmentApi: (userAndPresetConfigs.future?.v8_viteEnvironmentApi || userAndPresetConfigs.future?.unstable_previewServerPrerendering) ?? false
541
541
  };
542
542
  let allowedActionOrigins = userAndPresetConfigs.allowedActionOrigins ?? false;
543
543
  let reactRouterConfig = deepFreeze({
@@ -613,13 +613,11 @@ async function createConfigLoader({
613
613
  if (!fsWatcher) {
614
614
  fsWatcher = import_chokidar.default.watch([root, appDirectory], {
615
615
  ignoreInitial: true,
616
- ignored: (path3) => {
617
- let dirname = import_pathe3.default.dirname(path3);
618
- return !dirname.startsWith(appDirectory) && // Ensure we're only watching files outside of the app directory
619
- // that are at the root level, not nested in subdirectories
620
- path3 !== root && // Watch the root directory itself
621
- dirname !== root;
622
- }
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}`));
623
621
  });
624
622
  fsWatcher.on("all", async (...args) => {
625
623
  let [event, rawFilepath] = args;
@@ -761,6 +759,25 @@ function isEntryFileDependency(moduleGraph, entryFilepath, filepath, visited = /
761
759
  }
762
760
  return false;
763
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
+ }
764
781
 
765
782
  // vite/cloudflare-dev-proxy.ts
766
783
  var serverBuildId = "virtual:react-router/server-build";