Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/stabilize-split-route-modules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@react-router/dev": minor
---

Stabilize `future.v8_splitRouteModules`, replacing `future.unstable_splitRouteModules`

- ⚠️ This is a breaking change if you have begun using `future.unstable_splitRouteModules`. Please update your `react-router.config.ts`.
7 changes: 7 additions & 0 deletions .changeset/stabilize-vite-environment-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@react-router/dev": minor
---

Stabilize `future.v8_viteEnvironmentApi`, replacing `future.unstable_viteEnvironmentApi`

- ⚠️ This is a breaking change if you have begun using `future.unstable_viteEnvironmentApi`. Please update your `react-router.config.ts`.
2 changes: 1 addition & 1 deletion docs/how-to/react-server-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ For the initial unstable release, the following options from `react-router.confi
- `routeDiscovery`
- `serverBundles`
- `ssr: false` (SPA Mode)
- `future.unstable_splitRouteModules`
- `future.v8_splitRouteModules`
- `future.unstable_subResourceIntegrity`

Custom build entry files are also not yet supported.
Expand Down
56 changes: 56 additions & 0 deletions docs/upgrading/future.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,60 @@ If you're using `react-router-serve`, then you should not need to make any updat

You should only need to update your code if you are using the `context` parameter in `loader` and `action` functions. This only applies if you have a custom server with a `getLoadContext` function. Please see the docs on the middleware [`getLoadContext` changes](../how-to/middleware#changes-to-getloadcontextapploadcontext) and the instructions to [migrate to the new API](../how-to/middleware#migration-from-apploadcontext).

## `future.v8_splitRouteModules`

[MODES: framework]

<br/>
<br/>

**Background**

This feature enables splitting client-side route exports (`clientLoader`, `clientAction`, `clientMiddleware`, `HydrateFallback`) into separate chunks that can be loaded independently from the route component. This allows these exports to be fetched and executed while the component code is still downloading, improving performance for client-side data loading.

This can be set to `true` for opt-in behavior, or `"enforce"` to require all routes to be splittable (which will cause build failures for routes that cannot be split due to shared code).

👉 **Enable the Flag**

```ts filename=react-router.config.ts
import type { Config } from "@react-router/dev/config";

export default {
future: {
v8_splitRouteModules: true,
},
} satisfies Config;
```

**Update your Code**

No code changes are required. This is an optimization feature that works automatically once enabled.

## `future.v8_viteEnvironmentApi`

[MODES: framework]

<br/>
<br/>

**Background**

This enables support for the experimental Vite Environment API, which provides a more flexible and powerful way to configure Vite environments. This is only available when using Vite 6+.

👉 **Enable the Flag**

```ts filename=react-router.config.ts
import type { Config } from "@react-router/dev/config";

export default {
future: {
v8_viteEnvironmentApi: true,
},
} satisfies Config;
```

**Update your Code**

No code changes are required unless you have custom Vite configuration that needs to be updated for the Environment API. Most users won't need to make any changes.

[Response]: https://developer.mozilla.org/en-US/docs/Web/API/Response
10 changes: 5 additions & 5 deletions integration/client-data-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,17 +125,17 @@ test.describe("Client Data", () => {
}

test.describe(`template: ${templateName}`, () => {
for (const splitRouteModules of [true, false]) {
test.describe(`splitRouteModules: ${splitRouteModules}`, () => {
for (const v8_splitRouteModules of [true, false]) {
test.describe(`v8_splitRouteModules: ${v8_splitRouteModules}`, () => {
test.skip(
templateName.includes("rsc") && splitRouteModules,
templateName.includes("rsc") && v8_splitRouteModules,
"RSC Framework Mode doesn't support splitRouteModules",
);

test.skip(
({ browserName }) =>
Boolean(process.env.CI) &&
splitRouteModules &&
v8_splitRouteModules &&
(browserName === "webkit" || process.platform === "win32"),
"Webkit/Windows tests only run on a single worker in CI and splitRouteModules is not OS/browser-specific",
);
Expand All @@ -149,7 +149,7 @@ test.describe("Client Data", () => {
templateName,
files: {
"react-router.config.ts": reactRouterConfig({
splitRouteModules,
v8_splitRouteModules,
}),
"app/root.tsx": js`
import { Form, Outlet, Scripts } from "react-router"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import type { Config } from "@react-router/dev/config";

export default {
future: {
unstable_viteEnvironmentApi: true,
v8_viteEnvironmentApi: true,
},
} satisfies Config;
14 changes: 6 additions & 8 deletions integration/helpers/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,18 @@ export const reactRouterConfig = ({
basename,
prerender,
appDirectory,
splitRouteModules,
viteEnvironmentApi,
v8_middleware,
v8_splitRouteModules,
v8_viteEnvironmentApi,
routeDiscovery,
}: {
ssr?: boolean;
basename?: string;
prerender?: boolean | string[];
appDirectory?: string;
splitRouteModules?: NonNullable<
Config["future"]
>["unstable_splitRouteModules"];
viteEnvironmentApi?: boolean;
v8_middleware?: boolean;
v8_splitRouteModules?: NonNullable<Config["future"]>["v8_splitRouteModules"];
v8_viteEnvironmentApi?: boolean;
routeDiscovery?: Config["routeDiscovery"];
}) => {
let config: Config = {
Expand All @@ -51,9 +49,9 @@ export const reactRouterConfig = ({
appDirectory,
routeDiscovery,
future: {
unstable_splitRouteModules: splitRouteModules,
unstable_viteEnvironmentApi: viteEnvironmentApi,
v8_middleware,
v8_splitRouteModules,
v8_viteEnvironmentApi,
},
};

Expand Down
4 changes: 2 additions & 2 deletions integration/middleware-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ test.describe("Middleware", () => {
"react-router.config.ts": reactRouterConfig({
ssr: false,
v8_middleware: true,
splitRouteModules: true,
v8_splitRouteModules: true,
}),
"vite.config.ts": js`
import { defineConfig } from "vite";
Expand Down Expand Up @@ -1061,7 +1061,7 @@ test.describe("Middleware", () => {
files: {
"react-router.config.ts": reactRouterConfig({
v8_middleware: true,
splitRouteModules: true,
v8_splitRouteModules: true,
}),
"vite.config.ts": js`
import { defineConfig } from "vite";
Expand Down
30 changes: 21 additions & 9 deletions integration/split-route-modules-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,15 +250,17 @@ async function unblockClientLoader(page: Page) {

test.describe("Split route modules", async () => {
test.describe("enabled", () => {
let splitRouteModules = true;
let v8_splitRouteModules = true;
let port: number;
let cwd: string;
let stop: Awaited<ReturnType<typeof reactRouterServe>>;

test.beforeAll(async () => {
port = await getPort();
cwd = await createProject({
"react-router.config.ts": reactRouterConfig({ splitRouteModules }),
"react-router.config.ts": reactRouterConfig({
v8_splitRouteModules,
}),
"vite.config.js": await viteConfig.basic({ port }),
...files,
});
Expand Down Expand Up @@ -355,15 +357,17 @@ test.describe("Split route modules", async () => {
});

test.describe("disabled", () => {
let splitRouteModules = false;
let v8_splitRouteModules = false;
let port: number;
let cwd: string;
let stop: Awaited<ReturnType<typeof reactRouterServe>>;

test.beforeAll(async () => {
port = await getPort();
cwd = await createProject({
"react-router.config.ts": reactRouterConfig({ splitRouteModules }),
"react-router.config.ts": reactRouterConfig({
v8_splitRouteModules,
}),
"vite.config.js": await viteConfig.basic({ port }),
...files,
});
Expand Down Expand Up @@ -444,15 +448,17 @@ test.describe("Split route modules", async () => {
});

test.describe("enforce", () => {
let splitRouteModules = "enforce" as const;
let v8_splitRouteModules = "enforce" as const;
let port: number;
let cwd: string;

test.describe("splittable routes", () => {
test.beforeAll(async () => {
port = await getPort();
cwd = await createProject({
"react-router.config.ts": reactRouterConfig({ splitRouteModules }),
"react-router.config.ts": reactRouterConfig({
v8_splitRouteModules,
}),
"vite.config.js": await viteConfig.basic({ port }),
// Make unsplittable routes valid so the build can pass
"app/routes/unsplittable.tsx": "export default function(){}",
Expand All @@ -470,7 +476,9 @@ test.describe("Split route modules", async () => {
test.beforeAll(async () => {
port = await getPort();
cwd = await createProject({
"react-router.config.ts": reactRouterConfig({ splitRouteModules }),
"react-router.config.ts": reactRouterConfig({
v8_splitRouteModules,
}),
"vite.config.js": await viteConfig.basic({ port }),
"app/root.tsx": js`
import { Outlet } from "react-router";
Expand All @@ -496,7 +504,9 @@ test.describe("Split route modules", async () => {
test.beforeAll(async () => {
port = await getPort();
cwd = await createProject({
"react-router.config.ts": reactRouterConfig({ splitRouteModules }),
"react-router.config.ts": reactRouterConfig({
v8_splitRouteModules,
}),
"vite.config.js": await viteConfig.basic({ port }),
"app/root.tsx": js`
import { Outlet } from "react-router";
Expand All @@ -523,7 +533,9 @@ test.describe("Split route modules", async () => {
test.beforeAll(async () => {
port = await getPort();
cwd = await createProject({
"react-router.config.ts": reactRouterConfig({ splitRouteModules }),
"react-router.config.ts": reactRouterConfig({
v8_splitRouteModules,
}),
"vite.config.js": await viteConfig.basic({ port }),
...files,
// Ensure we're only testing the mixed route
Expand Down
8 changes: 4 additions & 4 deletions integration/vite-build-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ let stop: () => void;
const js = String.raw;

test.describe("Build", () => {
[false, true].forEach((viteEnvironmentApi) => {
[false, true].forEach((v8_viteEnvironmentApi) => {
viteMajorTemplates.forEach(({ templateName, templateDisplayName }) => {
// Vite 5 doesn't support the Environment API
if (templateName === "vite-5-template" && viteEnvironmentApi) {
if (templateName === "vite-5-template" && v8_viteEnvironmentApi) {
return;
}

test.describe(`${templateDisplayName}${
viteEnvironmentApi ? " with Vite Environment API" : ""
v8_viteEnvironmentApi ? " with Vite Environment API" : ""
}`, () => {
test.beforeAll(async () => {
port = await getPort();
Expand All @@ -38,7 +38,7 @@ test.describe("Build", () => {
ENV_VAR_FROM_DOTENV_FILE=true
`,
"react-router.config.ts": reactRouterConfig({
viteEnvironmentApi,
v8_viteEnvironmentApi,
}),
"vite.config.ts": js`
import { defineConfig } from "vite";
Expand Down
12 changes: 6 additions & 6 deletions integration/vite-cloudflare-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import type { Files } from "./helpers/vite.js";
import { reactRouterConfig, test, viteConfig } from "./helpers/vite.js";

function getFiles({
viteEnvironmentApi,
v8_viteEnvironmentApi,
}: {
viteEnvironmentApi: boolean;
v8_viteEnvironmentApi: boolean;
}): Files {
return async ({ port }) => ({
"react-router.config.ts": reactRouterConfig({ viteEnvironmentApi }),
"react-router.config.ts": reactRouterConfig({ v8_viteEnvironmentApi }),
"vite.config.ts": `
import { reactRouter } from "@react-router/dev/vite";
import { cloudflareDevProxy } from "@react-router/dev/vite/cloudflare";
Expand Down Expand Up @@ -144,9 +144,9 @@ function getFiles({
}

test.describe("Cloudflare Dev Proxy", () => {
[false, true].forEach((viteEnvironmentApi) => {
test.describe(`viteEnvironmentApi enabled: ${viteEnvironmentApi}`, () => {
const files = getFiles({ viteEnvironmentApi });
[false, true].forEach((v8_viteEnvironmentApi) => {
test.describe(`viteEnvironmentApi enabled: ${v8_viteEnvironmentApi}`, () => {
const files = getFiles({ v8_viteEnvironmentApi });

test("vite dev", async ({ page, dev }) => {
let { port } = await dev(files, "cloudflare-dev-proxy-template");
Expand Down
10 changes: 5 additions & 5 deletions integration/vite-css-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ test.describe("Vite CSS", () => {
cwd = await createProject(
{
"react-router.config.ts": reactRouterConfig({
viteEnvironmentApi: templateName !== "vite-5-template",
v8_viteEnvironmentApi: templateName !== "vite-5-template",
}),
"vite.config.ts": await viteConfig.basic({
port,
Expand Down Expand Up @@ -257,7 +257,7 @@ test.describe("Vite CSS", () => {
cwd = await createProject(
{
"react-router.config.ts": reactRouterConfig({
viteEnvironmentApi: templateName !== "vite-5-template",
v8_viteEnvironmentApi: templateName !== "vite-5-template",
basename: base,
}),
"vite.config.ts": await viteConfig.basic({
Expand Down Expand Up @@ -300,7 +300,7 @@ test.describe("Vite CSS", () => {
cwd = await createProject(
{
"react-router.config.ts": reactRouterConfig({
viteEnvironmentApi: templateName !== "vite-5-template",
v8_viteEnvironmentApi: templateName !== "vite-5-template",
}),
"vite.config.ts": await viteConfig.basic({
port,
Expand Down Expand Up @@ -342,7 +342,7 @@ test.describe("Vite CSS", () => {
cwd = await createProject(
{
"react-router.config.ts": reactRouterConfig({
viteEnvironmentApi: templateName !== "vite-5-template",
v8_viteEnvironmentApi: templateName !== "vite-5-template",
}),
"vite.config.ts": await viteConfig.basic({
port,
Expand Down Expand Up @@ -413,7 +413,7 @@ test.describe("Vite CSS", () => {
cwd = await createProject(
{
"react-router.config.ts": reactRouterConfig({
viteEnvironmentApi: templateName !== "vite-5-template",
v8_viteEnvironmentApi: templateName !== "vite-5-template",
}),
"vite.config.ts": await viteConfig.basic({
port,
Expand Down
Loading