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(remix-server-runtime): RRR 1.1 - handleResourceRequest #4245

Merged
merged 23 commits into from
Sep 29, 2022
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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/light-chairs-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@remix-run/server-runtime": patch
---

add handleResourceRequest implementation based on @remix-run/router behind experimental build flag
4 changes: 4 additions & 0 deletions .github/workflows/reusable-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ on:
# but we want to pass an array (node_version: "[14, 16, 18]"),
# so we'll need to manually stringify it for now
type: string
enable_react_router:
required: false
type: string

env:
CI: true
CYPRESS_INSTALL_BINARY: 0
ENABLE_REACT_ROUTER: ${{ inputs.enable_react_router }}

jobs:
build:
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,10 @@ jobs:
uses: ./.github/workflows/reusable-test.yml
with:
node_version: '["latest"]'

test-experimental:
if: github.repository == 'remix-run/remix'
uses: ./.github/workflows/reusable-test.yml
with:
node_version: '["latest"]'
enable_react_router: '1'
5 changes: 4 additions & 1 deletion integration/helpers/create-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ export async function createFixture(init: FixtureInit) {

let requestDocument = async (href: string, init?: RequestInit) => {
let url = new URL(href, "test://test");
let request = new Request(url.toString(), init);
let request = new Request(url.toString(), {
...init,
signal: new AbortController().signal,
});
return handler(request);
};

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"@rollup/plugin-babel": "^5.2.2",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^11.0.1",
"@rollup/plugin-replace": "^4.0.0",
"@testing-library/cypress": "^8.0.2",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^13.3.0",
Expand Down
8 changes: 6 additions & 2 deletions packages/remix-server-runtime/__tests__/data-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,9 @@ describe("loaders", () => {
try {
possibleError = await callRouteLoader({
request,
match,
loader: match.route.module.loader,
routeId: match.route.id,
params: match.params,
loadContext: {},
});
} catch (error) {
Expand Down Expand Up @@ -206,7 +208,9 @@ describe("actions", () => {
try {
possibleError = await callRouteAction({
request,
match,
action: match.route.module.action,
routeId: match.route.id,
params: match.params,
loadContext: {},
});
} catch (error) {
Expand Down
1 change: 1 addition & 0 deletions packages/remix-server-runtime/__tests__/handler-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe("createRequestHandler", () => {
headers: {
"X-Foo": "bar",
},
signal: new AbortController().signal,
})
);

Expand Down
68 changes: 50 additions & 18 deletions packages/remix-server-runtime/__tests__/server-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { ServerMode } from "../mode";
import type { ServerBuild } from "../build";
import { mockServerBuild } from "./utils";

const DATA_CALL_MULTIPIER = process.env.ENABLE_REACT_ROUTER ? 2 : 1;

function spyConsole() {
// https://github.com/facebook/react/issues/7047
let spy: any = {};
Expand Down Expand Up @@ -121,13 +123,16 @@ describe("shared server runtime", () => {
});
let handler = createRequestHandler(build, ServerMode.Test);

let request = new Request(`${baseUrl}/resource`, { method: "get" });
let request = new Request(`${baseUrl}/resource`, {
method: "get",
signal: new AbortController().signal,
});

let result = await handler(request);
expect(result.status).toBe(200);
expect(await result.json()).toBe("resource");
expect(rootLoader.mock.calls.length).toBe(0);
expect(resourceLoader.mock.calls.length).toBe(1);
expect(resourceLoader.mock.calls.length).toBe(1 * DATA_CALL_MULTIPIER);
});

test("calls sub resource route loader", async () => {
Expand Down Expand Up @@ -156,14 +161,17 @@ describe("shared server runtime", () => {
});
let handler = createRequestHandler(build, ServerMode.Test);

let request = new Request(`${baseUrl}/resource/sub`, { method: "get" });
let request = new Request(`${baseUrl}/resource/sub`, {
method: "get",
signal: new AbortController().signal,
});

let result = await handler(request);
expect(result.status).toBe(200);
expect(await result.json()).toBe("sub");
expect(rootLoader.mock.calls.length).toBe(0);
expect(resourceLoader.mock.calls.length).toBe(0);
expect(subResourceLoader.mock.calls.length).toBe(1);
expect(subResourceLoader.mock.calls.length).toBe(1 * DATA_CALL_MULTIPIER);
});

test("resource route loader allows thrown responses", async () => {
Expand All @@ -185,13 +193,16 @@ describe("shared server runtime", () => {
});
let handler = createRequestHandler(build, ServerMode.Test);

let request = new Request(`${baseUrl}/resource`, { method: "get" });
let request = new Request(`${baseUrl}/resource`, {
method: "get",
signal: new AbortController().signal,
});

let result = await handler(request);
expect(result.status).toBe(200);
expect(await result.text()).toBe("resource");
expect(rootLoader.mock.calls.length).toBe(0);
expect(resourceLoader.mock.calls.length).toBe(1);
expect(resourceLoader.mock.calls.length).toBe(1 * DATA_CALL_MULTIPIER);
});

test("resource route loader responds with generic error when thrown", async () => {
Expand All @@ -207,7 +218,10 @@ describe("shared server runtime", () => {
});
let handler = createRequestHandler(build, ServerMode.Test);

let request = new Request(`${baseUrl}/resource`, { method: "get" });
let request = new Request(`${baseUrl}/resource`, {
method: "get",
signal: new AbortController().signal,
});

let result = await handler(request);
expect(await result.text()).toBe("Unexpected Server Error");
Expand All @@ -226,11 +240,14 @@ describe("shared server runtime", () => {
});
let handler = createRequestHandler(build, ServerMode.Development);

let request = new Request(`${baseUrl}/resource`, { method: "get" });
let request = new Request(`${baseUrl}/resource`, {
method: "get",
signal: new AbortController().signal,
});

let result = await handler(request);
expect((await result.text()).includes(error.message)).toBe(true);
expect(spy.console.mock.calls.length).toBe(1);
expect(spy.console.mock.calls.length).toBe(1 * DATA_CALL_MULTIPIER);
});

test("calls resource route action", async () => {
Expand All @@ -252,13 +269,16 @@ describe("shared server runtime", () => {
});
let handler = createRequestHandler(build, ServerMode.Test);

let request = new Request(`${baseUrl}/resource`, { method: "post" });
let request = new Request(`${baseUrl}/resource`, {
method: "post",
signal: new AbortController().signal,
});

let result = await handler(request);
expect(result.status).toBe(200);
expect(await result.json()).toBe("resource");
expect(rootAction.mock.calls.length).toBe(0);
expect(resourceAction.mock.calls.length).toBe(1);
expect(resourceAction.mock.calls.length).toBe(1 * DATA_CALL_MULTIPIER);
});

test("calls sub resource route action", async () => {
Expand Down Expand Up @@ -287,14 +307,17 @@ describe("shared server runtime", () => {
});
let handler = createRequestHandler(build, ServerMode.Test);

let request = new Request(`${baseUrl}/resource/sub`, { method: "post" });
let request = new Request(`${baseUrl}/resource/sub`, {
method: "post",
signal: new AbortController().signal,
});

let result = await handler(request);
expect(result.status).toBe(200);
expect(await result.json()).toBe("sub");
expect(rootAction.mock.calls.length).toBe(0);
expect(resourceAction.mock.calls.length).toBe(0);
expect(subResourceAction.mock.calls.length).toBe(1);
expect(subResourceAction.mock.calls.length).toBe(1 * DATA_CALL_MULTIPIER);
});

test("resource route action allows thrown responses", async () => {
Expand All @@ -316,13 +339,16 @@ describe("shared server runtime", () => {
});
let handler = createRequestHandler(build, ServerMode.Test);

let request = new Request(`${baseUrl}/resource`, { method: "post" });
let request = new Request(`${baseUrl}/resource`, {
method: "post",
signal: new AbortController().signal,
});

let result = await handler(request);
expect(result.status).toBe(200);
expect(await result.text()).toBe("resource");
expect(rootAction.mock.calls.length).toBe(0);
expect(resourceAction.mock.calls.length).toBe(1);
expect(resourceAction.mock.calls.length).toBe(1 * DATA_CALL_MULTIPIER);
});

test("resource route action responds with generic error when thrown", async () => {
Expand All @@ -338,7 +364,10 @@ describe("shared server runtime", () => {
});
let handler = createRequestHandler(build, ServerMode.Test);

let request = new Request(`${baseUrl}/resource`, { method: "post" });
let request = new Request(`${baseUrl}/resource`, {
method: "post",
signal: new AbortController().signal,
});

let result = await handler(request);
expect(await result.text()).toBe("Unexpected Server Error");
Expand All @@ -357,11 +386,14 @@ describe("shared server runtime", () => {
});
let handler = createRequestHandler(build, ServerMode.Development);

let request = new Request(`${baseUrl}/resource`, { method: "post" });
let request = new Request(`${baseUrl}/resource`, {
method: "post",
signal: new AbortController().signal,
});

let result = await handler(request);
expect((await result.text()).includes(message)).toBe(true);
expect(spy.console.mock.calls.length).toBe(1);
expect(spy.console.mock.calls.length).toBe(1 * DATA_CALL_MULTIPIER);
});
});

Expand Down
37 changes: 22 additions & 15 deletions packages/remix-server-runtime/data.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import type { RouteMatch } from "./routeMatching";
import type { ServerRoute } from "./routes";
import { json, isResponse, isRedirectResponse } from "./responses";
import type {
ActionFunction,
DataFunctionArgs,
LoaderFunction,
} from "./routeModules";

/**
* An object of unknown type for route loaders and actions provided by the
Expand All @@ -17,15 +20,17 @@ export type AppData = any;

export async function callRouteAction({
loadContext,
match,
routeId,
action,
params,
request,
}: {
loadContext: AppLoadContext;
match: RouteMatch<ServerRoute>;
routeId: string;
action?: ActionFunction;
params: DataFunctionArgs["params"];
request: Request;
}) {
let action = match.route.module.action;

if (!action) {
let response = new Response(null, { status: 405 });
response.headers.set("X-Remix-Catch", "yes");
Expand All @@ -37,7 +42,7 @@ export async function callRouteAction({
result = await action({
request: stripDataParam(stripIndexParam(request)),
context: loadContext,
params: match.params,
params,
});
} catch (error: unknown) {
if (!isResponse(error)) {
Expand All @@ -52,7 +57,7 @@ export async function callRouteAction({

if (result === undefined) {
throw new Error(
`You defined an action for route "${match.route.id}" but didn't return ` +
`You defined an action for route "${routeId}" but didn't return ` +
`anything from your \`action\` function. Please return a value or \`null\`.`
);
}
Expand All @@ -62,19 +67,21 @@ export async function callRouteAction({

export async function callRouteLoader({
loadContext,
match,
routeId,
loader,
params,
request,
}: {
request: Request;
match: RouteMatch<ServerRoute>;
routeId: string;
loader?: LoaderFunction;
params: DataFunctionArgs["params"];
loadContext: AppLoadContext;
}) {
let loader = match.route.module.loader;

if (!loader) {
throw new Error(
`You made a ${request.method} request to ${request.url} but did not provide ` +
`a default component or \`loader\` for route "${match.route.id}", ` +
`a default component or \`loader\` for route "${routeId}", ` +
`so there is no way to handle the request.`
);
}
Expand All @@ -84,7 +91,7 @@ export async function callRouteLoader({
result = await loader({
request: stripDataParam(stripIndexParam(request)),
context: loadContext,
params: match.params,
params,
});
} catch (error: unknown) {
if (!isResponse(error)) {
Expand All @@ -99,7 +106,7 @@ export async function callRouteLoader({

if (result === undefined) {
throw new Error(
`You defined a loader for route "${match.route.id}" but didn't return ` +
`You defined a loader for route "${routeId}" but didn't return ` +
`anything from your \`loader\` function. Please return a value or \`null\`.`
);
}
Expand Down
1 change: 1 addition & 0 deletions packages/remix-server-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"typings": "dist/index.d.ts",
"module": "dist/esm/index.js",
"dependencies": {
"@remix-run/router": "1.0.0",
"@types/cookie": "^0.4.0",
"@web3-storage/multipart-parser": "^1.0.0",
"cookie": "^0.4.1",
Expand Down
Loading