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

feat: added entry.server handleDataRequest #329

Merged
merged 1 commit into from
Oct 29, 2021
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
17 changes: 16 additions & 1 deletion docs/api/app.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,16 @@ Remix uses `app/entry.server.tsx` to generate the HTTP response when rendering o

This module should render the markup for the current page using a `<Remix>` element with the `context` and `url` for the current request. This markup will (optionally) be re-hydrated once JavaScript loads in the browser using the [browser entry module]("../entry.client").

You can also export an optional `handleDataRequest` function that will allow you to modify the response of a data request. These are the requests that do not render HTML, but rather return the loader and action data to the browser once client side hydration has occured.

Here's a basic example:

```tsx
import ReactDOMServer from "react-dom/server";
import type { EntryContext } from "remix";
import type {
EntryContext,
HandleDataRequestFunction,
} from "remix";
import { RemixServer } from "remix";

export default function handleRequest(
Expand All @@ -160,6 +165,16 @@ export default function handleRequest(
headers: responseHeaders,
});
}

// this is an optional export
export let handleDataRequest: HandleDataRequestFunction = (
response: Response,
// same args that get passed to the action or loader that was called
{ request, params, context }
) => {
response.headers.set("x-custom", "yay!");
return response;
};
```

# Route Module API
Expand Down
5 changes: 5 additions & 0 deletions fixtures/gists-app/app/entry.server.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,8 @@ export default function handleRequest(
headers: responseHeaders
});
}

export function handleDataRequest(response) {
response.headers.set("x-hdr", "yes");
return response;
}
34 changes: 34 additions & 0 deletions fixtures/gists-app/tests/handle-data-request-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Browser, Page } from "puppeteer";
import puppeteer from "puppeteer";

import * as Utils from "./utils";

const testPort = 3000;
const testServer = `http://localhost:${testPort}`;

describe("handle data request function", () => {
let browser: Browser;
let page: Page;
beforeEach(async () => {
browser = await puppeteer.launch();
page = await browser.newPage();
});

afterEach(() => browser.close());

describe("is called", () => {
it("on client side navigation", async () => {
let responses = Utils.collectDataResponses(page);
await page.goto(`${testServer}/`);
await Utils.reactIsHydrated(page);

await page.click('a[href="/gists"]');
await page.waitForSelector('[data-test-id="/gists/index"]');

expect(responses.length).toEqual(2);
responses.forEach(response =>
expect(response.headers()["x-hdr"]).toBe("yes")
);
});
});
});
22 changes: 16 additions & 6 deletions packages/remix-server-runtime/build.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { DataFunctionArgs } from "./routeModules";
import type { EntryContext, AssetsManifest } from "./entry";
import type { ServerRouteManifest } from "./routes";

Expand All @@ -12,15 +13,24 @@ export interface ServerBuild {
assets: AssetsManifest;
}

export interface HandleDocumentRequestFunction {
(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
context: EntryContext
): Promise<Response> | Response;
}

export interface HandleDataRequestFunction {
(response: Response, args: DataFunctionArgs): Promise<Response> | Response;
}

/**
* A module that serves as the entry point for a Remix app during server
* rendering.
*/
export interface ServerEntryModule {
default(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
context: EntryContext
): Promise<Response>;
default: HandleDocumentRequestFunction;
handleDataRequest?: HandleDataRequestFunction;
}
8 changes: 7 additions & 1 deletion packages/remix-server-runtime/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export type { ServerBuild, ServerEntryModule } from "./build";
export type {
ServerBuild,
ServerEntryModule,
HandleDataRequestFunction,
HandleDocumentRequestFunction
} from "./build";

export type {
CookieParseOptions,
Expand All @@ -23,6 +28,7 @@ export type { ServerPlatform } from "./platform";

export type {
ActionFunction,
DataFunctionArgs,
ErrorBoundaryComponent,
HeadersFunction,
LinksFunction,
Expand Down
15 changes: 12 additions & 3 deletions packages/remix-server-runtime/routeModules.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Location } from "history";
import type { ComponentType } from "react";
import type { Params } from "react-router"; // TODO: import/export from react-router-dom
import type { Params } from "react-router-dom";

import type { AppLoadContext, AppData } from "./data";
import type { LinkDescriptor } from "./links";
Expand All @@ -10,11 +10,20 @@ export interface RouteModules<RouteModule> {
[routeId: string]: RouteModule;
}

/**
* The arguments passed to ActionFunction and LoaderFunction.
*/
export interface DataFunctionArgs {
request: Request;
context: AppLoadContext;
params: Params;
}

/**
* A function that handles data mutations for a route.
*/
export interface ActionFunction {
(args: { request: Request; context: AppLoadContext; params: Params }):
(args: DataFunctionArgs):
| Promise<Response>
| Response
| Promise<AppData>
Expand Down Expand Up @@ -55,7 +64,7 @@ export interface LinksFunction {
* A function that loads data for a route.
*/
export interface LoaderFunction {
(args: { request: Request; context: AppLoadContext; params: Params }):
(args: DataFunctionArgs):
| Promise<Response>
| Response
| Promise<AppData>
Expand Down
13 changes: 11 additions & 2 deletions packages/remix-server-runtime/server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AppLoadContext} from "./data";
import type { AppLoadContext } from "./data";
import { extractData, isCatchResponse } from "./data";
import { loadRouteData, callRouteAction } from "./data";
import type { ComponentDidCatchEmulator } from "./errors";
Expand Down Expand Up @@ -127,7 +127,7 @@ async function handleDataRequest(
);
} catch (error: any) {
let formattedError = (await platform.formatServerError?.(error)) || error;
return json(await serializeError(formattedError), {
response = json(await serializeError(formattedError), {
status: 500,
headers: {
"X-Remix-Error": "unfortunately, yes"
Expand All @@ -149,6 +149,15 @@ async function handleDataRequest(
});
}

if (build.entry.module.handleDataRequest) {
clonedRequest = stripIndexParam(stripDataParam(request));
return build.entry.module.handleDataRequest(response, {
request: clonedRequest,
context: loadContext,
params: routeMatch.params
});
}

return response;
}

Expand Down