Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add routes.ts support behind future flag #10107

Merged
merged 25 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a0d1526
Add support for `routes.ts`
markdalgleish Oct 14, 2024
003199f
Merge branch 'dev' into markdalgleish/routes-ts
markdalgleish Oct 14, 2024
87bf880
Clean paths for relative snapshot tests
markdalgleish Oct 14, 2024
c2bb8af
Force new page in Webkit integration tests
markdalgleish Oct 14, 2024
feb10d3
Revert
markdalgleish Oct 14, 2024
3a48dfe
Fix integration test in Webkit
markdalgleish Oct 14, 2024
c743f72
Move routes.ts API to `route-config` package
markdalgleish Oct 16, 2024
3c413ff
Add fs-routes package
markdalgleish Oct 16, 2024
7601e67
Add routes-option-adapter package
markdalgleish Oct 17, 2024
b755f79
Dedupe flat routes implementation
markdalgleish Oct 17, 2024
7b9806f
Add routes.ts docs to future flags guide
markdalgleish Oct 18, 2024
210d09e
Rename routes option adapter function
markdalgleish Oct 18, 2024
0cf86a0
Mention Vite requirement in docs
markdalgleish Oct 18, 2024
713d049
Reduce git diff
markdalgleish Oct 18, 2024
40509e0
Expand changeset, update docs
markdalgleish Oct 18, 2024
63eb78c
Fix tsconfig outdir
markdalgleish Oct 18, 2024
cf7c6d8
Remove unused minimatch dep
markdalgleish Oct 18, 2024
ec9aa74
Remove exports fields
markdalgleish Oct 18, 2024
a995410
Update docs/start/future-flags.md
markdalgleish Oct 18, 2024
143f140
Fix changeset typo
markdalgleish Oct 18, 2024
78f7634
Merge branch 'dev' into markdalgleish/routes-ts
markdalgleish Oct 18, 2024
122b7b0
Merge branch 'dev' into markdalgleish/routes-ts
markdalgleish Oct 28, 2024
72511f7
Add `v3_routeConfig` future flag
markdalgleish Oct 29, 2024
0df3455
Update package name in future flags guide
markdalgleish Oct 29, 2024
5f98394
Update changeset
markdalgleish Oct 29, 2024
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
5 changes: 5 additions & 0 deletions .changeset/popular-humans-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@remix-run/dev": minor
---

Add support for `routes.ts`
348 changes: 348 additions & 0 deletions integration/vite-route-config-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,348 @@
import fs from "node:fs/promises";
import path from "node:path";
import { expect, type BrowserContext, type Page } from "@playwright/test";

import {
type Files,
createProject,
viteBuild,
test,
viteConfig,
createEditor,
} from "./helpers/vite.js";

const js = String.raw;

// This is a workaround for caching issues in WebKit
async function reloadPage({
browserName,
page,
context,
}: {
browserName: string;
page: Page;
context: BrowserContext;
}): Promise<Page> {
if (browserName === "webkit") {
let newPage = await context.newPage();
let url = page.url();
await page.close();
await newPage.goto(url, { waitUntil: "networkidle" });
return newPage;
}

await page.reload();
return page;
}

test.describe("route config", () => {
test("fails the build if routes option is used", async () => {
let cwd = await createProject({
"vite.config.js": `
import { vitePlugin as remix } from "@remix-run/dev";

export default {
plugins: [remix({
routes: () => {},
})]
}
`,
"app/routes.ts": `export default INVALID(`,
markdalgleish marked this conversation as resolved.
Show resolved Hide resolved
});
let buildResult = viteBuild({ cwd });
expect(buildResult.status).toBe(1);
expect(buildResult.stderr.toString()).toContain(
'The "routes" config option is not supported when a "routes.ts" file is present. You should migrate these routes into "routes.ts".'
);
});

test("fails the dev process if routes option is used", async ({
viteDev,
}) => {
let files: Files = async ({ port }) => ({
"vite.config.js": `
import { vitePlugin as remix } from "@remix-run/dev";

export default {
${await viteConfig.server({ port })}
plugins: [remix({
routes: () => {},
})]
}
`,
"app/routes.ts": `export default INVALID(`,
markdalgleish marked this conversation as resolved.
Show resolved Hide resolved
});
let devError: Error | undefined;
try {
await viteDev(files);
} catch (error: any) {
devError = error;
}
expect(devError?.toString()).toContain(
'The "routes" config option is not supported when a "routes.ts" file is present. You should migrate these routes into "routes.ts".'
);
});

test("fails the build if route config is invalid", async () => {
let cwd = await createProject({
"app/routes.ts": `export default INVALID(`,
});
let buildResult = viteBuild({ cwd });
expect(buildResult.status).toBe(1);
expect(buildResult.stderr.toString()).toContain(
'Route config in "routes.ts" is invalid.'
);
});

test("fails the dev process if route config is initially invalid", async ({
viteDev,
}) => {
let files: Files = async ({ port }) => ({
"vite.config.js": await viteConfig.basic({ port }),
"app/routes.ts": `export default INVALID(`,
});
let devError: Error | undefined;
try {
await viteDev(files);
} catch (error: any) {
devError = error;
}
expect(devError?.toString()).toContain(
'Route config in "routes.ts" is invalid.'
);
});

test("supports correcting an invalid route config", async ({
browserName,
page,
context,
viteDev,
}) => {
let files: Files = async ({ port }) => ({
"vite.config.js": await viteConfig.basic({ port }),
"app/routes.ts": js`
import { type RouteConfig } from "@react-router/dev/routes";

export const routes: RouteConfig = [
{
file: "test-route-1.tsx",
index: true,
},
];
`,
"app/test-route-1.tsx": `
export default function TestRoute1() {
return <div data-test-route>Test route 1</div>
}
`,
"app/test-route-2.tsx": `
export default function TestRoute2() {
return <div data-test-route>Test route 2</div>
}
`,
});
let { cwd, port } = await viteDev(files);

await page.goto(`http://localhost:${port}/`, { waitUntil: "networkidle" });
await expect(page.locator("[data-test-route]")).toHaveText("Test route 1");

let edit = createEditor(cwd);

// Make config invalid
await edit("app/routes.ts", (contents) => contents + "INVALID");

// Ensure dev server is still running with old config + HMR
await edit("app/test-route-1.tsx", (contents) =>
contents.replace("Test route 1", "Test route 1 updated")
);
await expect(page.locator("[data-test-route]")).toHaveText(
"Test route 1 updated"
);

// Fix config with new route
await edit("app/routes.ts", (contents) =>
contents.replace("INVALID", "").replace("test-route-1", "test-route-2")
);

await expect(async () => {
// Reload to pick up new route for current path
page = await reloadPage({ browserName, page, context });
await expect(page.locator("[data-test-route]")).toHaveText(
"Test route 2"
);
}).toPass();
});

test("supports correcting an invalid route config module graph", async ({
page,
context,
browserName,
viteDev,
}) => {
let files: Files = async ({ port }) => ({
"vite.config.js": await viteConfig.basic({ port }),
"app/routes.ts": js`
export { routes } from "./actual-routes";
`,
"app/actual-routes.ts": js`
import { type RouteConfig } from "@react-router/dev/routes";

export const routes: RouteConfig = [
{
file: "test-route-1.tsx",
index: true,
},
];
`,
"app/test-route-1.tsx": `
export default function TestRoute1() {
return <div data-test-route>Test route 1</div>
}
`,
"app/test-route-2.tsx": `
export default function TestRoute2() {
return <div data-test-route>Test route 2</div>
}
`,
});
let { cwd, port } = await viteDev(files);

await page.goto(`http://localhost:${port}/`, { waitUntil: "networkidle" });
await expect(page.locator("[data-test-route]")).toHaveText("Test route 1");

let edit = createEditor(cwd);

// Make config invalid
await edit("app/actual-routes.ts", (contents) => contents + "INVALID");

// Ensure dev server is still running with old config + HMR
await edit("app/test-route-1.tsx", (contents) =>
contents.replace("Test route 1", "Test route 1 updated")
);
await expect(page.locator("[data-test-route]")).toHaveText(
"Test route 1 updated"
);

// Fix config with new route
await edit("app/actual-routes.ts", (contents) =>
contents.replace("INVALID", "").replace("test-route-1", "test-route-2")
);

await expect(async () => {
// Reload to pick up new route for current path
page = await reloadPage({ browserName, page, context });
await expect(page.locator("[data-test-route]")).toHaveText(
"Test route 2"
);
}).toPass();
});

test("supports correcting a missing route config", async ({
browserName,
page,
context,
viteDev,
}) => {
let files: Files = async ({ port }) => ({
"vite.config.js": await viteConfig.basic({ port }),
"app/routes.ts": js`
import { type RouteConfig } from "@react-router/dev/routes";

export const routes: RouteConfig = [
{
file: "test-route-1.tsx",
index: true,
},
];
`,
"app/test-route-1.tsx": `
export default function TestRoute1() {
return <div data-test-route>Test route 1</div>
}
`,
"app/test-route-2.tsx": `
export default function TestRoute2() {
return <div data-test-route>Test route 2</div>
}
`,
"app/routes/_index.tsx": `
export default function FsRoute() {
return <div data-test-route>FS route</div>
}
`,
});
let { cwd, port } = await viteDev(files);

await page.goto(`http://localhost:${port}/`, { waitUntil: "networkidle" });
await expect(page.locator("[data-test-route]")).toHaveText("Test route 1");

let edit = createEditor(cwd);

let INVALID_FILENAME = "app/routes.ts.oops";

// Rename config to make it missing
await fs.rename(
path.join(cwd, "app/routes.ts"),
path.join(cwd, INVALID_FILENAME)
);

await expect(async () => {
// Reload to pick up classic FS routes
page = await reloadPage({ browserName, page, context });
await expect(page.locator("[data-test-route]")).toHaveText("FS route");
}).toPass();

// Ensure dev server falls back to FS routes + HMR
await edit("app/routes/_index.tsx", (contents) =>
contents.replace("FS route", "FS route updated")
);
await expect(page.locator("[data-test-route]")).toHaveText(
"FS route updated"
);

// Add new route
await edit(INVALID_FILENAME, (contents) =>
contents.replace("test-route-1", "test-route-2")
);

// Rename config to bring it back
await fs.rename(
path.join(cwd, INVALID_FILENAME),
path.join(cwd, "app/routes.ts")
);

await expect(async () => {
// Reload to pick up new route for current path
page = await reloadPage({ browserName, page, context });
await expect(page.locator("[data-test-route]")).toHaveText(
"Test route 2"
);
}).toPass();
});

test("supports absolute route file paths", async ({ page, viteDev }) => {
let files: Files = async ({ port }) => ({
"vite.config.js": await viteConfig.basic({ port }),
"app/routes.ts": js`
import path from "node:path";
import { type RouteConfig } from "@react-router/dev/routes";

export const routes: RouteConfig = [
{
file: path.resolve(import.meta.dirname, "test-route.tsx"),
index: true,
},
];
`,
"app/test-route.tsx": `
export default function TestRoute() {
return <div data-test-route>Test route</div>
}
`,
});
let { port } = await viteDev(files);

await page.goto(`http://localhost:${port}/`, { waitUntil: "networkidle" });
await expect(page.locator("[data-test-route]")).toHaveText("Test route");
});
});
Loading