Skip to content

Commit

Permalink
Add react-router.config.ts (#12251)
Browse files Browse the repository at this point in the history
* Add initial config loader logic

* Fix build

* Exclude packages nested within packages from build

* Roll back test changes

* Reinstate missing test files

* Remove console logs

* Roll back lockfile diff

* Hook up config loader to plugin

* Add Config type

* Migrate integration tests to new config format

* Fix playground routes export

* Fix compiler-spa playground config

* Move config types

* Update prerender tests

* Migrate typegen, re-enable test

* Clean up typegen change logic

* Reintroduce basename validation, remove unused vite-node context

* Remove unused arg

* Improve logging

* Provide configChanged arg

* Remove unused isEqualJson util

* Enforce command arg for config loader

* Change command arg to watch

* Remove old config logic

* Move ssrExternals into config directory

* Throw when watching for changes when watch mode disabled

* Remove redundant config from playground

* Remove comment

* Fix onChange

* Migrate typegen

* Update comments

* Add defineConfig helper

* Review feedback and clean up

* Revert defineConfig

* Update docs and changesets

---------

Co-authored-by: Pedro Cattori <[email protected]>
  • Loading branch information
markdalgleish and pcattori authored Nov 15, 2024
1 parent 5a7b291 commit 2bd6740
Show file tree
Hide file tree
Showing 29 changed files with 793 additions and 732 deletions.
24 changes: 11 additions & 13 deletions .changeset/prerendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,17 @@
- `prerender` can either be an array of string paths, or a function (sync or async) that returns an array of strings so that you can dynamically generate the paths by talking to your CMS, etc.

```ts
export default defineConfig({
plugins: [
reactRouter({
async prerender() {
let slugs = await fakeGetSlugsFromCms();
// Prerender these paths into `.html` files at build time, and `.data`
// files if they have loaders
return ["/", "/about", ...slugs.map((slug) => `/product/${slug}`)];
},
}),
tsconfigPaths(),
],
});
// react-router.config.ts
import type { Config } from "@react-router/dev/config";

export default {
async prerender() {
let slugs = await fakeGetSlugsFromCms();
// Prerender these paths into `.html` files at build time, and `.data`
// files if they have loaders
return ["/", "/about", ...slugs.map((slug) => `/product/${slug}`)];
},
} satisfies Config;

async function fakeGetSlugsFromCms() {
await new Promise((r) => setTimeout(r, 1000));
Expand Down
25 changes: 11 additions & 14 deletions .changeset/remove-manifest-option.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,18 @@ The `manifest` option been superseded by the more powerful `buildEnd` hook since

If you were using the `manifest` option, you can replace it with a `buildEnd` hook that writes the manifest to disk like this:

```js
import { reactRouter } from "@react-router/dev/vite";
```ts
// react-router.config.ts
import type { Config } from "@react-router/dev/config";
import { writeFile } from "node:fs/promises";

export default {
plugins: [
reactRouter({
async buildEnd({ buildManifest }) {
await writeFile(
"build/manifest.json",
JSON.stringify(buildManifest, null, 2),
"utf-8"
);
},
}),
],
};
async buildEnd({ buildManifest }) {
await writeFile(
"build/manifest.json",
JSON.stringify(buildManifest, null, 2),
"utf-8"
);
},
} satisfies Config;
```
2 changes: 1 addition & 1 deletion docs/start/framework/rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Server side rendering requires a deployment that supports it. Though it's a glob

## Static Pre-rendering

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

export default {
Expand Down
19 changes: 7 additions & 12 deletions docs/upgrading/component-routes.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,19 +261,14 @@ The first few routes you migrate are the hardest because you often have to acces
If you want to enable server rendering and static pre-rendering, you can do so with the `ssr` and `prerender` options in the bundler plugin.

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

export default defineConfig({
plugins: [
reactRouter({
ssr: true,
async prerender() {
return ["/", "/about", "/contact"];
},
}),
],
});
export default {
ssr: true,
async prerender() {
return ["/", "/about", "/contact"];
},
} satisfies Config;
```

See [Deploying][deploying] for more information on deploying a server.
Expand Down
19 changes: 7 additions & 12 deletions docs/upgrading/router-provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,17 +242,12 @@ The first few routes you migrate are the hardest because you often have to acces
If you want to enable server rendering and static pre-rendering, you can do so with the `ssr` and `prerender` options in the bundler plugin. For SSR you'll need to also deploy the server build to a server. See [Deploying](../start/deploying) for more information.

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

export default defineConfig({
plugins: [
reactRouter({
ssr: true,
async prerender() {
return ["/", "/pages/about"];
},
}),
],
});
export default {
ssr: true,
async prerender() {
return ["/", "/pages/about"];
},
} satisfies Config;
```
14 changes: 12 additions & 2 deletions integration/helpers/create-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
import { createRequestHandler as createExpressHandler } from "@react-router/express";
import { createReadableStreamFromReadable } from "@react-router/node";

import { viteConfig } from "./vite.js";
import { viteConfig, reactRouterConfig } from "./vite.js";

const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
const root = path.join(__dirname, "../..");
Expand Down Expand Up @@ -357,6 +357,10 @@ export async function createFixtureProject(
filename.startsWith("vite.config.")
);

let hasReactRouterConfig = Object.keys(init.files ?? {}).some((filename) =>
filename.startsWith("react-router.config.")
);

let { spaMode } = init;

await writeTestFiles(
Expand All @@ -366,7 +370,13 @@ export async function createFixtureProject(
: {
"vite.config.js": await viteConfig.basic({
port,
spaMode,
}),
}),
...(hasReactRouterConfig
? {}
: {
"react-router.config.ts": reactRouterConfig({
ssr: !spaMode,
}),
}),
...init.files,
Expand Down
39 changes: 28 additions & 11 deletions integration/helpers/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import glob from "glob";
import dedent from "dedent";
import type { Page } from "@playwright/test";
import { test as base, expect } from "@playwright/test";
import type { ReactRouterConfig } from "@react-router/dev/vite";
import type { Config } from "@react-router/dev/config";

const require = createRequire(import.meta.url);

Expand All @@ -23,6 +23,31 @@ const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
const root = path.resolve(__dirname, "../..");
const TMP_DIR = path.join(root, ".tmp/integration");

export const reactRouterConfig = ({
ssr,
basename,
prerender,
appDirectory,
}: {
ssr?: boolean;
basename?: string;
prerender?: boolean | string[];
appDirectory?: string;
}) => {
let config: Config = {
ssr,
basename,
prerender,
appDirectory,
};

return dedent`
import type { Config } from "@react-router/dev/config";
export default ${JSON.stringify(config)} satisfies Config;
`;
};

export const viteConfig = {
server: async (args: { port: number; fsAllow?: string[] }) => {
let { port, fsAllow } = args;
Expand All @@ -37,15 +62,7 @@ export const viteConfig = {
`;
return text;
},
basic: async (args: {
port: number;
fsAllow?: string[];
spaMode?: boolean;
}) => {
let config: ReactRouterConfig = {
ssr: !args.spaMode,
};

basic: async (args: { port: number; fsAllow?: string[] }) => {
return dedent`
import { reactRouter } from "@react-router/dev/vite";
import { envOnlyMacros } from "vite-env-only";
Expand All @@ -54,7 +71,7 @@ export const viteConfig = {
export default {
${await viteConfig.server(args)}
plugins: [
reactRouter(${JSON.stringify(config)}),
reactRouter(),
envOnlyMacros(),
tsconfigPaths()
],
Expand Down
15 changes: 4 additions & 11 deletions integration/single-fetch-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
js,
} from "./helpers/create-fixture.js";
import { PlaywrightFixture } from "./helpers/playwright-fixture.js";
import { reactRouterConfig } from "./helpers/vite.js";

const ISO_DATE = "2024-03-12T12:00:00.000Z";

Expand Down Expand Up @@ -1378,17 +1379,9 @@ test.describe("single-fetch", () => {
let fixture = await createFixture({
files: {
...files,
"vite.config.ts": js`
import { defineConfig } from "vite";
import { reactRouter } from "@react-router/dev/vite";
export default defineConfig({
plugins: [
reactRouter({
basename: '/base',
}),
],
});
`,
"react-router.config.ts": reactRouterConfig({
basename: "/base",
}),
"app/routes/data.tsx": js`
import { redirect } from 'react-router';
export function loader() {
Expand Down
8 changes: 3 additions & 5 deletions integration/typegen-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,10 @@ test.describe("typegen", () => {

test("custom app dir", async () => {
const cwd = await createProject({
"vite.config.ts": tsx`
import { reactRouter } from "@react-router/dev/vite";
"react-router.config.ts": tsx`
export default {
plugins: [reactRouter({ appDirectory: "src/myapp" })],
};
appDirectory: "src/myapp",
}
`,
"app/expect-type.ts": expectType,
"app/routes/products.$id.tsx": tsx`
Expand Down
34 changes: 17 additions & 17 deletions integration/vite-basename-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
dev,
viteDevCmd,
reactRouterServe,
reactRouterConfig,
} from "./helpers/vite.js";
import { js } from "./helpers/create-fixture.js";

Expand Down Expand Up @@ -55,7 +56,7 @@ const sharedFiles = {
`,
};

async function viteConfigFile({
async function configFiles({
port,
base,
basename,
Expand All @@ -64,21 +65,20 @@ async function viteConfigFile({
base?: string;
basename?: string;
}) {
return js`
return {
"react-router.config.ts": reactRouterConfig({
basename: basename !== "/" ? basename : undefined,
}),
"vite.config.js": js`
import { reactRouter } from "@react-router/dev/vite";
export default {
${base !== "/" ? 'base: "' + base + '",' : ""}
${await viteConfig.server({ port })}
plugins: [
${
basename !== "/"
? 'reactRouter({ basename: "' + basename + '" }),'
: "reactRouter(),"
}
]
plugins: [reactRouter()]
}
`;
`,
};
}

const customServerFile = ({
Expand Down Expand Up @@ -146,15 +146,15 @@ test.describe("Vite base / React Router basename / Vite dev", () => {
}) {
port = await getPort();
cwd = await createProject({
"vite.config.js": await viteConfigFile({ port, base, basename }),
...(await configFiles({ port, base, basename })),
...(files || sharedFiles),
});
if (startServer !== false) {
stop = await dev({ cwd, port, basename });
}
}

test.afterAll(async () => await stop());
test.afterAll(async () => await stop?.());

test("works when the base and basename are the same", async ({ page }) => {
await setup({ base: "/mybase/", basename: "/mybase/" });
Expand Down Expand Up @@ -268,7 +268,7 @@ test.describe("Vite base / React Router basename / express dev", async () => {
}) {
port = await getPort();
cwd = await createProject({
"vite.config.js": await viteConfigFile({ port, base, basename }),
...(await configFiles({ port, base, basename })),
"server.mjs": customServerFile({ port, basename }),
...sharedFiles,
});
Expand Down Expand Up @@ -394,7 +394,7 @@ test.describe("Vite base / React Router basename / vite build", () => {
}) {
port = await getPort();
cwd = await createProject({
"vite.config.js": await viteConfigFile({ port, base, basename }),
...(await configFiles({ port, base, basename })),
...sharedFiles,
});
build({ cwd });
Expand Down Expand Up @@ -440,7 +440,7 @@ test.describe("Vite base / React Router basename / express build", async () => {
}) {
port = await getPort();
cwd = await createProject({
"vite.config.js": await viteConfigFile({ port, base, basename }),
...(await configFiles({ port, base, basename })),
"server.mjs": customServerFile({ port, base, basename }),
...sharedFiles,
});
Expand Down Expand Up @@ -478,11 +478,11 @@ test.describe("Vite base / React Router basename / express build", async () => {
test("works when when base is an absolute external URL", async ({ page }) => {
port = await getPort();
cwd = await createProject({
"vite.config.js": await viteConfigFile({
...(await configFiles({
port,
base: "https://cdn.example.com/assets/",
basename: "/app/",
}),
})),
// Slim server that only serves basename (route) requests from the React Router handler
"server.mjs": String.raw`
import { createRequestHandler } from "@react-router/express";
Expand Down
Loading

0 comments on commit 2bd6740

Please sign in to comment.