Skip to content

Commit

Permalink
Fix a few small issues with prerendering in v7 (#11833)
Browse files Browse the repository at this point in the history
  • Loading branch information
brophdawg11 committed Sep 19, 2024
1 parent 2a23323 commit 312bddb
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 89 deletions.
74 changes: 56 additions & 18 deletions integration/helpers/create-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface FixtureInit {
files?: { [filename: string]: string };
useReactRouterServe?: boolean;
spaMode?: boolean;
prerender?: boolean;
port?: number;
}

Expand Down Expand Up @@ -56,24 +57,38 @@ export async function createFixture(init: FixtureInit, mode?: ServerMode) {
);
};

let isSpaMode = init.spaMode;

if (isSpaMode) {
let requestDocument = () => {
let html = fse.readFileSync(
path.join(projectDir, "build/client/index.html")
);
return new Response(html, {
headers: {
"Content-Type": "text/html",
},
});
};
if (init.spaMode || init.prerender) {
let requestDocument = init.spaMode
? () => {
let html = fse.readFileSync(
path.join(projectDir, "build/client/index.html")
);
return new Response(html, {
headers: {
"Content-Type": "text/html",
},
});
}
: (href: string) => {
let pathname = new URL(href, "test://test").pathname;
let file = pathname.endsWith(".data")
? pathname
: pathname + "/index.html";
let html = fse.readFileSync(
path.join(projectDir, "build/client" + file)
);
return new Response(html, {
headers: {
"Content-Type": "text/html",
},
});
};

return {
projectDir,
build: null,
isSpaMode,
isSpaMode: init.spaMode,
prerender: init.prerender,
requestDocument,
requestResource: () => {
throw new Error("Cannot requestResource in SPA Mode tests");
Expand Down Expand Up @@ -141,7 +156,8 @@ export async function createFixture(init: FixtureInit, mode?: ServerMode) {
return {
projectDir,
build: app,
isSpaMode,
isSpaMode: init.spaMode,
prerender: init.prerender,
requestDocument,
requestResource,
requestSingleFetchData,
Expand Down Expand Up @@ -224,16 +240,38 @@ export async function createAppFixture(fixture: Fixture, mode?: ServerMode) {
let app = express();
app.use(express.static(path.join(fixture.projectDir, "build/client")));
app.get("*", (_, res, next) =>
res.sendFile(path.join(fixture.projectDir, "build/client/index.html"))
);
let server = app.listen(port);
accept({ stop: server.close.bind(server), port });
});
}

if (fixture.prerender) {
return new Promise(async (accept) => {
let port = await getPort();
let app = express();
app.use(express.static(path.join(fixture.projectDir, "build/client")));
app.get("*", (req, res, next) => {
let file = req.path.endsWith(".data")
? req.path
: req.path + "/index.html";
res.sendFile(
path.join(fixture.projectDir, "build/client/index.html"),
path.join(fixture.projectDir, "build/client", file),
next
)
);
);
});
let server = app.listen(port);
accept({ stop: server.close.bind(server), port });
});
}

if (!fixture.build) {
return Promise.reject(
new Error("Cannot start app server without a build")
);
}

return new Promise(async (accept) => {
let port = await getPort();
let app = express();
Expand Down
93 changes: 84 additions & 9 deletions integration/vite-prerender-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ let files = {
`,
"app/root.tsx": js`
import * as React from "react";
import { Form, Link, Links, Meta, Outlet, Scripts } from "react-router";
import { Form, Link, Links, Meta, Outlet, Scripts, useRouteError } from "react-router";
export function meta({ data }) {
return [{
title: "Root Title"
}];
}
export default function Root() {
export function Layout({ children }) {
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => setMounted(true), []);
return (
Expand All @@ -49,13 +49,26 @@ let files = {
<nav>
<Link to="/">Home</Link><br/>
<Link to="/about">About</Link><br/>
<Link to="/not-found">Not Found</Link><br/>
</nav>
<Outlet />
{children}
<Scripts />
</body>
</html>
);
}
export default function Root() {
return <Outlet />
}
export function ErrorBoundary() {
let error = useRouteError();
let msg = 'status' in error ?
error.status + " " + error.statusText :
error.message;
return <p data-error>{msg}</p>;
}
`,
"app/routes/_index.tsx": js`
import * as React from "react";
Expand Down Expand Up @@ -141,6 +154,7 @@ test.describe("Prerendering", () => {

test("Prerenders known static routes when true is specified", async () => {
fixture = await createFixture({
prerender: true,
files: {
...files,
"app/routes/parent.tsx": js`
Expand Down Expand Up @@ -182,6 +196,7 @@ test.describe("Prerendering", () => {

let clientDir = path.join(fixture.projectDir, "build", "client");
expect(listAllFiles(clientDir).sort()).toEqual([
"__manifest",
"_root.data",
"about.data",
"about/index.html",
Expand Down Expand Up @@ -209,6 +224,7 @@ test.describe("Prerendering", () => {

test("Prerenders a static array of routes", async () => {
fixture = await createFixture({
prerender: true,
files: {
...files,
"vite.config.ts": js`
Expand All @@ -233,6 +249,7 @@ test.describe("Prerendering", () => {

let clientDir = path.join(fixture.projectDir, "build", "client");
expect(listAllFiles(clientDir).sort()).toEqual([
"__manifest",
"_root.data",
"about.data",
"about/index.html",
Expand Down Expand Up @@ -288,6 +305,7 @@ test.describe("Prerendering", () => {

let clientDir = path.join(fixture.projectDir, "build", "client");
expect(listAllFiles(clientDir).sort()).toEqual([
"__manifest",
"_root.data",
"a.data",
"a/index.html",
Expand Down Expand Up @@ -316,14 +334,16 @@ test.describe("Prerendering", () => {

test("Hydrates into a navigable app", async ({ page }) => {
fixture = await createFixture({
prerender: true,
files,
});
appFixture = await createAppFixture(fixture);

let requests: string[] = [];
page.on("request", (request) => {
if (request.url().endsWith(".data")) {
requests.push(request.url());
let pathname = new URL(request.url()).pathname;
if (pathname.endsWith(".data") || pathname.endsWith("__manifest")) {
requests.push(pathname);
}
});

Expand All @@ -332,12 +352,16 @@ test.describe("Prerendering", () => {
await page.waitForSelector("[data-mounted]");
await app.clickLink("/about");
await page.waitForSelector("[data-route]:has-text('About')");
expect(requests.length).toBe(1);
expect(requests[0]).toMatch(/\/about.data$/);
expect(requests).toEqual(["/__manifest", "/about.data"]);
});

test("Serves the prerendered HTML file", async ({ page }) => {
test("Serves the prerendered HTML file alongside runtime routes", async ({
page,
}) => {
fixture = await createFixture({
// Even thogh we are prerendering, we want a running server so we can
// hit the pre-rendered HTML file and a non-prerendered route
prerender: false,
files: {
...files,
"vite.config.ts": js`
Expand Down Expand Up @@ -390,10 +414,24 @@ test.describe("Prerendering", () => {
expect(await app.getHtml()).toContain("<span>NOT-PRERENDERED-false</span>");
});

test("Renders/ down to the proper HydrateFallback", async ({ page }) => {
test("Renders down to the proper HydrateFallback", async ({ page }) => {
fixture = await createFixture({
prerender: true,
files: {
...files,
"vite.config.ts": js`
import { defineConfig } from "vite";
import { reactRouter } from "@react-router/dev/vite";
export default defineConfig({
build: { manifest: true },
plugins: [
reactRouter({
prerender: ['/', '/parent', '/parent/child'],
})
],
});
`,
"app/routes/parent.tsx": js`
import { Outlet, useLoaderData } from 'react-router';
export function loader() {
Expand Down Expand Up @@ -440,4 +478,41 @@ test.describe("Prerendering", () => {
await page.waitForSelector("[data-mounted]");
expect(await app.getHtml()).toMatch("Index: INDEX");
});

test("Handles 404s on data requests", async ({ page }) => {
fixture = await createFixture({
prerender: true,
files: {
...files,
"app/routes/$slug.tsx": js`
import * as React from "react";
import { useLoaderData } from "react-router";
export async function loader() {
return null;
}
export default function Component() {
return <h2>Slug</h2>
}
`,
},
});
appFixture = await createAppFixture(fixture);

let requests: string[] = [];
page.on("request", (request) => {
let pathname = new URL(request.url()).pathname;
if (pathname.endsWith(".data")) {
requests.push(pathname);
}
});

let app = new PlaywrightFixture(appFixture, page);
await app.goto("/");
await page.waitForSelector("[data-mounted]");
await app.clickLink("/not-found");
await page.waitForSelector("[data-error]:has-text('404 Not Found')");
expect(requests).toEqual(["/not-found.data"]);
});
});
Loading

0 comments on commit 312bddb

Please sign in to comment.