diff --git a/.changeset/large-lobsters-grin.md b/.changeset/large-lobsters-grin.md new file mode 100644 index 0000000000..41d1eb914e --- /dev/null +++ b/.changeset/large-lobsters-grin.md @@ -0,0 +1,50 @@ +--- +"@react-router/dev": minor +--- + +Automatic types for future flags + +Some future flags alter the way types should work in React Router. +Previously, you had to remember to manually opt-in to the new types. + +For example, for `unstable_middleware`: + +```ts +// react-router.config.ts + +// Step 1: Enable middleware +export default { + future: { + unstable_middleware: true, + }, +}; + +// Step 2: Enable middleware types +declare module "react-router" { + interface Future { + unstable_middleware: true; // 👈 Enable middleware types + } +} +``` + +It was up to you to keep the runtime future flags synced with the types for those future flags. +This was confusing and error-prone. + +Now, React Router will automatically enable types for future flags. +That means you only need to specify the runtime future flag: + +```ts +// react-router.config.ts + +// Step 1: Enable middleware +export default { + future: { + unstable_middleware: true, + }, +}; + +// No step 2! That's it! +``` + +Behind the scenes, React Router will generate the corresponding `declare module` into `.react-router/types`. +Currently this is done in `.react-router/types/+register.ts` but this is an implementation detail that may change in the future. diff --git a/packages/react-router-dev/typegen/index.ts b/packages/react-router-dev/typegen/index.ts index 05e2f1e0a1..4168aa9bf8 100644 --- a/packages/react-router-dev/typegen/index.ts +++ b/packages/react-router-dev/typegen/index.ts @@ -31,21 +31,23 @@ export async function watch( await writeAll(ctx); logger?.info(pc.green("generated types"), { timestamp: true, clear: true }); - ctx.configLoader.onChange(async ({ result, routeConfigChanged }) => { - if (!result.ok) { - logger?.error(pc.red(result.error), { timestamp: true, clear: true }); - return; - } + ctx.configLoader.onChange( + async ({ result, configChanged, routeConfigChanged }) => { + if (!result.ok) { + logger?.error(pc.red(result.error), { timestamp: true, clear: true }); + return; + } - ctx.config = result.value; - if (routeConfigChanged) { - await writeAll(ctx); - logger?.info(pc.green("regenerated types"), { - timestamp: true, - clear: true, - }); + ctx.config = result.value; + if (configChanged || routeConfigChanged) { + await writeAll(ctx); + logger?.info(pc.green("regenerated types"), { + timestamp: true, + clear: true, + }); + } } - }); + ); return { close: async () => await ctx.configLoader.close(), @@ -103,6 +105,10 @@ function register(ctx: Context) { interface Register { params: Params; } + + interface Future { + unstable_middleware: ${ctx.config.future.unstable_middleware} + } } `; diff --git a/playground/middleware/react-router.config.ts b/playground/middleware/react-router.config.ts index 2cb7be3730..8ebd8738ed 100644 --- a/playground/middleware/react-router.config.ts +++ b/playground/middleware/react-router.config.ts @@ -1,11 +1,4 @@ import type { Config } from "@react-router/dev/config"; -import type { Future } from "react-router"; - -declare module "react-router" { - interface Future { - unstable_middleware: true; - } -} export default { future: {