Skip to content

Commit 232d15d

Browse files
committed
refactor(dev/vite): use @mjackson/node-fetch-server
1 parent a21290c commit 232d15d

File tree

5 files changed

+20
-114
lines changed

5 files changed

+20
-114
lines changed

packages/react-router-dev/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"@babel/preset-typescript": "^7.27.1",
7272
"@babel/traverse": "^7.27.7",
7373
"@babel/types": "^7.27.7",
74+
"@mjackson/node-fetch-server": "^0.7.0",
7475
"@npmcli/package-json": "^4.0.1",
7576
"@react-router/node": "workspace:*",
7677
"arg": "^5.0.1",
@@ -86,7 +87,6 @@
8687
"prettier": "^2.7.1",
8788
"react-refresh": "^0.14.0",
8889
"semver": "^7.3.7",
89-
"set-cookie-parser": "^2.6.0",
9090
"tinyglobby": "^0.2.14",
9191
"valibot": "^0.41.0",
9292
"vite-node": "^3.2.2"
@@ -103,7 +103,6 @@
103103
"@types/node": "^20.0.0",
104104
"@types/npmcli__package-json": "^4.0.0",
105105
"@types/prettier": "^2.7.3",
106-
"@types/set-cookie-parser": "^2.4.1",
107106
"@types/semver": "^7.7.0",
108107
"esbuild-register": "^3.6.0",
109108
"execa": "5.1.1",

packages/react-router-dev/vite/cloudflare-dev-proxy.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { sendResponse } from "@mjackson/node-fetch-server";
12
import { createRequestHandler } from "react-router";
23
import {
34
type AppLoadContext,
@@ -8,7 +9,7 @@ import {
89
import { type Plugin } from "vite";
910
import { type GetPlatformProxyOptions, type PlatformProxy } from "wrangler";
1011

11-
import { fromNodeRequest, toNodeRequest } from "./node-adapter";
12+
import { fromNodeRequest } from "./node-adapter";
1213
import { preloadVite, getVite } from "./vite";
1314
import { type ResolvedReactRouterConfig, loadConfig } from "../config/config";
1415

@@ -142,7 +143,7 @@ export const cloudflareDevProxyVitePlugin = <Env, Cf extends CfProperties>(
142143
? await getLoadContext({ request: req, context })
143144
: context;
144145
let res = await handler(req, loadContext);
145-
await toNodeRequest(res, nodeRes);
146+
await sendResponse(nodeRes, res);
146147
} catch (error) {
147148
next(error);
148149
}
Lines changed: 5 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
import { once } from "node:events";
2-
import type { IncomingMessage, ServerResponse } from "node:http";
3-
import { TLSSocket } from "node:tls";
4-
import { Readable } from "node:stream";
5-
import { splitCookiesString } from "set-cookie-parser";
6-
import { createReadableStreamFromReadable } from "@react-router/node";
1+
import type { ServerResponse } from "node:http";
2+
3+
import { createRequest } from "@mjackson/node-fetch-server";
74
import type * as Vite from "vite";
85

96
import invariant from "../invariant";
@@ -13,110 +10,16 @@ export type NodeRequestHandler = (
1310
res: ServerResponse
1411
) => Promise<void>;
1512

16-
function fromNodeHeaders(nodeReq: IncomingMessage): Headers {
17-
let nodeHeaders = nodeReq.headers;
18-
19-
if (nodeReq.httpVersionMajor >= 2) {
20-
nodeHeaders = { ...nodeHeaders };
21-
if (nodeHeaders[":authority"]) {
22-
nodeHeaders.host = nodeHeaders[":authority"] as string;
23-
}
24-
delete nodeHeaders[":authority"];
25-
delete nodeHeaders[":method"];
26-
delete nodeHeaders[":path"];
27-
delete nodeHeaders[":scheme"];
28-
}
29-
30-
let headers = new Headers();
31-
32-
for (let [key, values] of Object.entries(nodeHeaders)) {
33-
if (values) {
34-
if (Array.isArray(values)) {
35-
for (let value of values) {
36-
headers.append(key, value);
37-
}
38-
} else {
39-
headers.set(key, values);
40-
}
41-
}
42-
}
43-
44-
return headers;
45-
}
46-
47-
// Based on `createRemixRequest` in packages/react-router-express/server.ts
4813
export function fromNodeRequest(
4914
nodeReq: Vite.Connect.IncomingMessage,
5015
nodeRes: ServerResponse<Vite.Connect.IncomingMessage>
5116
): Request {
52-
let protocol =
53-
nodeReq.socket instanceof TLSSocket && nodeReq.socket.encrypted
54-
? "https"
55-
: "http";
56-
let origin =
57-
nodeReq.headers.origin && "null" !== nodeReq.headers.origin
58-
? nodeReq.headers.origin
59-
: `${protocol}://${nodeReq.headers.host}`;
6017
// Use `req.originalUrl` so React Router is aware of the full path
6118
invariant(
6219
nodeReq.originalUrl,
6320
"Expected `nodeReq.originalUrl` to be defined"
6421
);
65-
let url = new URL(nodeReq.originalUrl, origin);
66-
67-
// Abort action/loaders once we can no longer write a response
68-
let controller: AbortController | null = new AbortController();
69-
let init: RequestInit = {
70-
method: nodeReq.method,
71-
headers: fromNodeHeaders(nodeReq),
72-
signal: controller.signal,
73-
};
74-
75-
// Abort action/loaders once we can no longer write a response iff we have
76-
// not yet sent a response (i.e., `close` without `finish`)
77-
// `finish` -> done rendering the response
78-
// `close` -> response can no longer be written to
79-
nodeRes.on("finish", () => (controller = null));
80-
nodeRes.on("close", () => controller?.abort());
81-
82-
if (nodeReq.method !== "GET" && nodeReq.method !== "HEAD") {
83-
init.body = createReadableStreamFromReadable(nodeReq);
84-
(init as { duplex: "half" }).duplex = "half";
85-
}
86-
87-
return new Request(url.href, init);
88-
}
89-
90-
// Adapted from solid-start's `handleNodeResponse`:
91-
// https://github.com/solidjs/solid-start/blob/7398163869b489cce503c167e284891cf51a6613/packages/start/node/fetch.js#L162-L185
92-
export async function toNodeRequest(res: Response, nodeRes: ServerResponse) {
93-
nodeRes.statusCode = res.status;
94-
95-
// HTTP/2 doesn't support status messages
96-
// https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.4
97-
if (!nodeRes.req || nodeRes.req.httpVersionMajor < 2) {
98-
nodeRes.statusMessage = res.statusText;
99-
}
100-
101-
let cookiesStrings = [];
102-
103-
for (let [name, value] of res.headers) {
104-
if (name === "set-cookie") {
105-
cookiesStrings.push(...splitCookiesString(value));
106-
} else nodeRes.setHeader(name, value);
107-
}
108-
109-
if (cookiesStrings.length) {
110-
nodeRes.setHeader("set-cookie", cookiesStrings);
111-
}
22+
nodeReq.url = nodeReq.originalUrl;
11223

113-
if (res.body) {
114-
// https://github.com/microsoft/TypeScript/issues/29867
115-
let responseBody = res.body as unknown as AsyncIterable<Uint8Array>;
116-
let readable = Readable.from(responseBody);
117-
readable.pipe(nodeRes);
118-
await once(readable, "end");
119-
} else {
120-
nodeRes.end();
121-
}
24+
return createRequest(nodeReq, nodeRes);
12225
}

packages/react-router-dev/vite/plugin.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
import * as path from "node:path";
1616
import * as url from "node:url";
1717
import * as babel from "@babel/core";
18+
import { sendResponse } from "@mjackson/node-fetch-server";
1819
import {
1920
unstable_setDevServerHooks as setDevServerHooks,
2021
createRequestHandler,
@@ -47,7 +48,7 @@ import invariant from "../invariant";
4748
import type { Cache } from "./cache";
4849
import { generate, parse } from "./babel";
4950
import type { NodeRequestHandler } from "./node-adapter";
50-
import { fromNodeRequest, toNodeRequest } from "./node-adapter";
51+
import { fromNodeRequest } from "./node-adapter";
5152
import {
5253
getCssStringFromViteDevModuleCode,
5354
getStylesForPathname,
@@ -1644,7 +1645,7 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => {
16441645
req,
16451646
await reactRouterDevLoadContext(req)
16461647
);
1647-
await toNodeRequest(res, nodeRes);
1648+
await sendResponse(nodeRes, res);
16481649
};
16491650
await nodeHandler(req, res);
16501651
} catch (error) {

pnpm-lock.yaml

Lines changed: 8 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)