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 future flag for Single Fetch #8773

Merged
merged 62 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
29a1e30
Bump to RR experimental
brophdawg11 Feb 6, 2024
78c011e
Initial implementation of single fetch for loaders
brophdawg11 Feb 6, 2024
e837499
Add future flag
brophdawg11 Feb 6, 2024
05ef1de
Bump RR experimental to allow boolean loaders
brophdawg11 Feb 6, 2024
59758b3
Handle clientLoaders with single fetch enabled
brophdawg11 Feb 6, 2024
1c5978b
Use turbo-stream for single fetch responses
brophdawg11 Feb 8, 2024
195cd18
POC of streamiong loader data down in action response
brophdawg11 Feb 8, 2024
cc8a03f
Move back to separate action and revalidation requests
brophdawg11 Feb 9, 2024
918d340
WIP POC of granular revalidation
brophdawg11 Feb 9, 2024
18378f1
Support fine-grained revalidation
brophdawg11 Feb 13, 2024
ce9b27d
Fix action bodies
brophdawg11 Feb 13, 2024
a4631bd
Bump RR experimental
brophdawg11 Feb 14, 2024
dd0203b
Move single fetch implementation out of browser.tsx
brophdawg11 Feb 14, 2024
77ac53e
Fix css/loading parallelization issues by passing singleFetch through…
brophdawg11 Feb 14, 2024
a0fffd7
Bump RR Experimental
brophdawg11 Feb 14, 2024
a2dc7a4
Streamline routes code and fix a few e2e tests bugs
brophdawg11 Feb 14, 2024
49b46be
Bump turbo-stream
brophdawg11 Feb 15, 2024
bbc0728
Switch from X-Remix-Routes header to _routes query param
brophdawg11 Feb 15, 2024
c962cf7
Update to latest RR
brophdawg11 Feb 15, 2024
3d7c4eb
Proxy action status back through DecodedResponse
brophdawg11 Feb 16, 2024
36887bb
Bump RR experimental
brophdawg11 Feb 16, 2024
308b010
RR experimental
brophdawg11 Feb 16, 2024
d5bed65
Fix unit tests
brophdawg11 Feb 16, 2024
5cfdec3
Bump RR Experimental
brophdawg11 Feb 16, 2024
509863d
Bump RR Experimental
brophdawg11 Feb 29, 2024
45f07c8
Minor updates and fixes from E2E test runs
brophdawg11 Feb 29, 2024
b5fb702
Duplicate a bunch of E2E tests to run with single fetch enabled
brophdawg11 Feb 29, 2024
1e6d13d
Merge branch 'dev' into brophdawg11/single-fetch
brophdawg11 Feb 29, 2024
38ff342
Fix exports test
brophdawg11 Feb 29, 2024
3670378
Fix lint issue
brophdawg11 Feb 29, 2024
729336c
Switch from DecodedResponse to HandlerResult
brophdawg11 Mar 1, 2024
7a80a6b
bump RR experimental
brophdawg11 Mar 1, 2024
c6750a4
Leverage turnbo-stream for document streaming when single fetch is en…
brophdawg11 Mar 1, 2024
4f9ea7b
Ficx up some E2E tests - still have some errors in error-sanitization
brophdawg11 Mar 1, 2024
1d39835
E2E test updates
brophdawg11 Mar 4, 2024
522f9e4
More E2E tests - need to do transition test next
brophdawg11 Mar 4, 2024
98ebcd5
Bump router experimental and fix lint issues
brophdawg11 Mar 5, 2024
782af76
fix unit tests
brophdawg11 Mar 5, 2024
23ca0b8
Add more E2E tests
brophdawg11 Mar 5, 2024
47a2a3b
Enable single fetch prefetching
brophdawg11 Mar 5, 2024
291bb3b
Update defer E2E tests
brophdawg11 Mar 5, 2024
14de37c
Fix up defer and a few lingering E2E tests
brophdawg11 Mar 6, 2024
903d483
Add changeset
brophdawg11 Mar 6, 2024
dce624d
Merge branch 'dev' into brophdawg11/single-fetch
brophdawg11 Mar 6, 2024
be316ac
Update pnpm-lock
brophdawg11 Mar 6, 2024
6cce524
Add remix-run/router as a dep to the integration package
brophdawg11 Mar 6, 2024
439a6cb
Merge branch 'dev' into brophdawg11/single-fetch
brophdawg11 Mar 6, 2024
e72932b
Fix prefetching unit test
brophdawg11 Mar 6, 2024
3cd4512
Fix E2E test
brophdawg11 Mar 7, 2024
9bc58ce
Merge branch 'dev' into brophdawg11/single-fetch
brophdawg11 Mar 7, 2024
4123e1f
Updates from code review
brophdawg11 Mar 8, 2024
674ac4a
Bump RR version
brophdawg11 Mar 11, 2024
b81780a
Updates for latest RR version
brophdawg11 Mar 11, 2024
2939707
Silly pnpm deps
brophdawg11 Mar 11, 2024
395078d
Update response signature ofr internal server handlers
brophdawg11 Mar 11, 2024
faf262e
Lift redirects to the root for single fetch loaders
brophdawg11 Mar 11, 2024
2d20749
Merge branch 'dev' into brophdawg11/single-fetch
brophdawg11 Mar 11, 2024
00e414c
Revert "Lift redirects to the root for single fetch loaders"
brophdawg11 Mar 11, 2024
ac3133e
Lift redirect to the top via Symbol
brophdawg11 Mar 11, 2024
8e53388
Fix E2E tests
brophdawg11 Mar 11, 2024
f672242
Fix EntryContext typings
brophdawg11 Mar 12, 2024
8d4c012
Single fetch specific E2E tests
brophdawg11 Mar 12, 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
15 changes: 15 additions & 0 deletions .changeset/single-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"@remix-run/dev": minor
"@remix-run/react": minor
"@remix-run/server-runtime": minor
"@remix-run/testing": minor
---

New `future.unstable_singleFetch` flag

- Naked objects returned from loaders/actions are no longer automatically converted to JSON responses. They'll be streamed as-is via `turbo-stream` so `Date`'s will become `Date` through `useLoaderData()`
- You can return naked objects with `Promise`'s without needing to use `defer()` - including nested `Promise`'s
- If you need to return a custom status code or custom response headers, you can still use the `defer` utility
- `<RemixServer abortDelay>` is no longer used. Instead, you should `export const streamTimeout` from `entry.server.tsx` and the remix server runtime will use that as the delay to abort the streamed response
- If you export your own streamTimeout, you should decouple that from aborting the react `renderToPipeableStream`. You should always ensure that react is aborted _afer_ the stream is aborted so that abort rejections can be flushed down
- Actions no longer automatically revalidate on 4xx/5xx responses (via RR `future.unstable_skipActionErrorRevalidation` flag) - you can return a 2xx to opt-into revalidation or use `shouldRevalidate`
215 changes: 215 additions & 0 deletions integration/action-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,218 @@ test.describe("actions", () => {
expect(await app.getHtml()).toMatch(PAGE_TEXT);
});
});

// Duplicate suite of the tests above running with single fetch enabled
// TODO(v3): remove the above suite of tests and just keep these
test.describe("single fetch", () => {
test.describe("actions", () => {
let fixture: Fixture;
let appFixture: AppFixture;

let FIELD_NAME = "message";
let WAITING_VALUE = "Waiting...";
let SUBMITTED_VALUE = "Submission";
let THROWS_REDIRECT = "redirect-throw";
let REDIRECT_TARGET = "page";
let PAGE_TEXT = "PAGE_TEXT";

test.beforeAll(async () => {
fixture = await createFixture({
config: {
future: {
unstable_singleFetch: true,
},
},
files: {
"app/routes/urlencoded.tsx": js`
import { Form, useActionData } from "@remix-run/react";

export let action = async ({ request }) => {
let formData = await request.formData();
return formData.get("${FIELD_NAME}");
};

export default function Actions() {
let data = useActionData()

return (
<Form method="post" id="form">
<p id="text">
{data ? <span id="action-text">{data}</span> : "${WAITING_VALUE}"}
</p>
<p>
<input type="text" defaultValue="${SUBMITTED_VALUE}" name="${FIELD_NAME}" />
<button type="submit" id="submit">Go</button>
</p>
</Form>
);
}
`,

"app/routes/request-text.tsx": js`
import { Form, useActionData } from "@remix-run/react";

export let action = async ({ request }) => {
let text = await request.text();
return text;
};

export default function Actions() {
let data = useActionData()

return (
<Form method="post" id="form">
<p id="text">
{data ? <span id="action-text">{data}</span> : "${WAITING_VALUE}"}
</p>
<p>
<input name="a" defaultValue="1" />
<input name="b" defaultValue="2" />
<button type="submit" id="submit">Go</button>
</p>
</Form>
);
}
`,

[`app/routes/${THROWS_REDIRECT}.jsx`]: js`
import { redirect } from "@remix-run/node";
import { Form } from "@remix-run/react";

export function action() {
throw redirect("/${REDIRECT_TARGET}")
}

export default function () {
return (
<Form method="post">
<button type="submit">Go</button>
</Form>
)
}
`,

[`app/routes/${REDIRECT_TARGET}.jsx`]: js`
export default function () {
return <div id="${REDIRECT_TARGET}">${PAGE_TEXT}</div>
}
`,

"app/routes/no-action.tsx": js`
import { Form } from "@remix-run/react";

export default function Component() {
return (
<Form method="post">
<button type="submit">Submit without action</button>
</Form>
);
}
`,
},
});

appFixture = await createAppFixture(fixture);
});

test.afterAll(() => {
appFixture.close();
});

let logs: string[] = [];

test.beforeEach(({ page }) => {
page.on("console", (msg) => {
logs.push(msg.text());
});
});

test.afterEach(() => {
expect(logs).toHaveLength(0);
});

test("is not called on document GET requests", async () => {
let res = await fixture.requestDocument("/urlencoded");
let html = selectHtml(await res.text(), "#text");
expect(html).toMatch(WAITING_VALUE);
});

test("is called on document POST requests", async () => {
let FIELD_VALUE = "cheeseburger";

let params = new URLSearchParams();
params.append(FIELD_NAME, FIELD_VALUE);

let res = await fixture.postDocument("/urlencoded", params);

let html = selectHtml(await res.text(), "#text");
expect(html).toMatch(FIELD_VALUE);
});

test("is called on script transition POST requests", async ({ page }) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto(`/urlencoded`);
await page.waitForSelector(`#text:has-text("${WAITING_VALUE}")`);

await page.click("button[type=submit]");
await page.waitForSelector("#action-text");
await page.waitForSelector(`#text:has-text("${SUBMITTED_VALUE}")`);
});

test("throws a 405 when no action exists", async ({ page }) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto(`/no-action`);
await page.click("button[type=submit]");
await page.waitForSelector(`h1:has-text("405 Method Not Allowed")`);
expect(logs.length).toBe(2);
expect(logs[0]).toMatch(
'Route "routes/no-action" does not have an action'
);
// logs[1] is the raw ErrorResponse instance from the boundary but playwright
// seems to just log the name of the constructor, which in the minified code
// is meaningless so we don't bother asserting

// The rest of the tests in this suite assert no logs, so clear this out to
// avoid failures in afterEach
logs = [];
});

test("properly encodes form data for request.text() usage", async ({
page,
}) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto(`/request-text`);
await page.waitForSelector(`#text:has-text("${WAITING_VALUE}")`);

await page.click("button[type=submit]");
await page.waitForSelector("#action-text");
expect(await app.getHtml("#action-text")).toBe(
'<span id="action-text">a=1&amp;b=2</span>'
);
});

test("redirects a thrown response on document requests", async () => {
let params = new URLSearchParams();
let res = await fixture.postDocument(`/${THROWS_REDIRECT}`, params);
expect(res.status).toBe(302);
expect(res.headers.get("Location")).toBe(`/${REDIRECT_TARGET}`);
});

test("redirects a thrown response on script transitions", async ({
page,
}) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto(`/${THROWS_REDIRECT}`);
let responses = app.collectSingleFetchResponses();
await app.clickSubmitButton(`/${THROWS_REDIRECT}`);

await page.waitForSelector(`#${REDIRECT_TARGET}`);

expect(responses.length).toBe(1);
expect(responses[0].status()).toBe(200);

expect(new URL(page.url()).pathname).toBe(`/${REDIRECT_TARGET}`);
expect(await app.getHtml()).toMatch(PAGE_TEXT);
});
});
});
Loading