diff --git a/.changeset/rude-clocks-prove.md b/.changeset/rude-clocks-prove.md new file mode 100644 index 00000000000..7496b7fc931 --- /dev/null +++ b/.changeset/rude-clocks-prove.md @@ -0,0 +1,6 @@ +--- +"@remix-run/dev": minor +--- + +- Log deprecation warnings for v3 future flags +- Add `@deprecated` annotations to `json`/`defer` utilities diff --git a/integration/vite-build-test.ts b/integration/vite-build-test.ts index dd6c7681b63..6918143be14 100644 --- a/integration/vite-build-test.ts +++ b/integration/vite-build-test.ts @@ -40,7 +40,15 @@ test.beforeAll(async () => { }, plugins: [ mdx(), - remix(), + remix({ + future: { + v3_fetcherPersist: true, + v3_lazyRouteDiscovery: true, + v3_relativeSplatPath: true, + v3_singleFetch: true, + v3_throwAbortReason: true, + } + }), ], }); `, diff --git a/integration/vite-css-test.ts b/integration/vite-css-test.ts index de37c65ce2c..77bc5ccc2f6 100644 --- a/integration/vite-css-test.ts +++ b/integration/vite-css-test.ts @@ -44,7 +44,7 @@ const files = { `, "app/entry.client.tsx": js` import "./entry.client.css"; - + import { RemixBrowser } from "@remix-run/react"; import { startTransition, StrictMode } from "react"; import { hydrateRoot } from "react-dom/client"; @@ -156,7 +156,18 @@ const VITE_CONFIG = async (port: number) => dedent` export default { ${await viteConfig.server({ port })} - plugins: [remix(), vanillaExtractPlugin()], + plugins: [ + remix({ + future: { + v3_fetcherPersist: true, + v3_lazyRouteDiscovery: true, + v3_relativeSplatPath: true, + v3_singleFetch: true, + v3_throwAbortReason: true, + } + }), + vanillaExtractPlugin() + ], } `; diff --git a/integration/vite-presets-test.ts b/integration/vite-presets-test.ts index e190f2289d5..1859c028a1c 100644 --- a/integration/vite-presets-test.ts +++ b/integration/vite-presets-test.ts @@ -26,6 +26,13 @@ const files = { assetsDir: "custom-assets-dir", }, plugins: [remix({ + future: { + v3_fetcherPersist: true, + v3_lazyRouteDiscovery: true, + v3_relativeSplatPath: true, + v3_singleFetch: true, + v3_throwAbortReason: true, + }, presets: [ // Ensure user config is passed to remixConfig hook { @@ -35,7 +42,16 @@ const files = { throw new Error("Remix user config doesn't have presets array."); } - let expected = JSON.stringify({ appDirectory: "app"}); + let expected = JSON.stringify({ + future: { + v3_fetcherPersist: true, + v3_lazyRouteDiscovery: true, + v3_relativeSplatPath: true, + v3_singleFetch: true, + v3_throwAbortReason: true, + }, + appDirectory: "app", + }); let actual = JSON.stringify(restUserConfig); if (actual !== expected) { @@ -49,7 +65,7 @@ const files = { return {}; }, }, - + // Ensure preset config takes lower precedence than user config { name: "test-preset", diff --git a/packages/remix-dev/config.ts b/packages/remix-dev/config.ts index 247a4c3f8cb..0d94a5f2287 100644 --- a/packages/remix-dev/config.ts +++ b/packages/remix-dev/config.ts @@ -1,17 +1,17 @@ +import type { NodePolyfillsOptions as EsbuildPluginsNodeModulesPolyfillOptions } from "esbuild-plugins-node-modules-polyfill"; +import fse from "fs-extra"; import { execSync } from "node:child_process"; import path from "node:path"; import { pathToFileURL } from "node:url"; -import fse from "fs-extra"; -import type { NodePolyfillsOptions as EsbuildPluginsNodeModulesPolyfillOptions } from "esbuild-plugins-node-modules-polyfill"; -import type { RouteManifest, DefineRoutesFunction } from "./config/routes"; -import { defineRoutes } from "./config/routes"; -import { ServerMode, isValidServerMode } from "./config/serverModes"; -import { serverBuildVirtualModule } from "./compiler/server/virtualModules"; -import { flatRoutes } from "./config/flat-routes"; import { detectPackageManager } from "./cli/detectPackageManager"; -import { tryLoadPackageJson } from "./cli/tryLoadPackageJson"; import { detectServerRuntime } from "./cli/detectServerRuntime"; +import { tryLoadPackageJson } from "./cli/tryLoadPackageJson"; +import { serverBuildVirtualModule } from "./compiler/server/virtualModules"; +import { flatRoutes } from "./config/flat-routes"; +import type { DefineRoutesFunction, RouteManifest } from "./config/routes"; +import { defineRoutes } from "./config/routes"; +import { ServerMode, isValidServerMode } from "./config/serverModes"; import { logger } from "./tux"; export interface RemixMdxConfig { @@ -628,6 +628,8 @@ export async function resolveConfig( } } + logFutureFlagWarnings(future); + return { appDirectory, cacheDirectory, @@ -692,3 +694,53 @@ export function findConfig( return undefined; } + +function logFutureFlagWarning(args: { flag: string; message: string }) { + logger.warn(args.message, { + key: args.flag, + details: [ + `You can use the \`${args.flag}\` future flag to opt-in early.`, + `-> https://remix.run/docs/en/2.13.1/start/future-flags#${args.flag}`, + ], + }); +} + +export function logFutureFlagWarnings(future: FutureConfig) { + if (!future.v3_fetcherPersist) { + logFutureFlagWarning({ + flag: "v3_fetcherPersist", + message: "Fetcher persistence behavior is changing in React Router v7", + }); + } + + if (!future.v3_lazyRouteDiscovery) { + logFutureFlagWarning({ + flag: "v3_lazyRouteDiscovery", + message: + "Route discovery/manifest behavior is changing in React Router v7", + }); + } + + if (!future.v3_relativeSplatPath) { + logFutureFlagWarning({ + flag: "v3_relativeSplatPath", + message: + "Relative routing behavior for splat routes is changing in React Router v7", + }); + } + + if (!future.v3_singleFetch) { + logFutureFlagWarning({ + flag: "v3_singleFetch", + message: "Data fetching is changing to a single fetch in React Router v7", + }); + } + + if (!future.v3_throwAbortReason) { + logFutureFlagWarning({ + flag: "v3_throwAbortReason", + message: + "The format of errors thrown on aborted requests is changing in React Router v7", + }); + } +} diff --git a/packages/remix-server-runtime/responses.ts b/packages/remix-server-runtime/responses.ts index 57b13d7e680..424b4296aaa 100644 --- a/packages/remix-server-runtime/responses.ts +++ b/packages/remix-server-runtime/responses.ts @@ -41,6 +41,11 @@ export type TypedResponse = Omit & { * This is a shortcut for creating `application/json` responses. Converts `data` * to JSON and sets the `Content-Type` header. * + * @deprecated This utility is deprecated in favor of opting into Single Fetch + * via `future.v3_singleFetch` and returning raw objects. This method will be + * removed in React Router v7. If you need to return a JSON Response, you can + * use `Response.json()`. + * * @see https://remix.run/utils/json */ export const json: JsonFunction = (data, init = {}) => { @@ -50,6 +55,10 @@ export const json: JsonFunction = (data, init = {}) => { /** * This is a shortcut for creating Remix deferred responses * + * @deprecated This utility is deprecated in favor of opting into Single Fetch + * via `future.v3_singleFetch` and returning raw objects. This method will be + * removed in React Router v7. + * * @see https://remix.run/utils/defer */ export const defer: DeferFunction = (data, init = {}) => {