Skip to content

Commit

Permalink
fix(remix-dev/vite): use global Request class in Vite dev server ha…
Browse files Browse the repository at this point in the history
…ndler (#8062)

Co-authored-by: Mark Dalgleish <mark.john.dalgleish@gmail.com>
  • Loading branch information
hi-ogawa and markdalgleish authored Nov 23, 2023
1 parent 6953c3f commit 3bf45f0
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 162 deletions.
5 changes: 5 additions & 0 deletions .changeset/witty-gifts-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@remix-run/dev": patch
---

Fix `request instanceof Request` checks when using Vite dev server
177 changes: 177 additions & 0 deletions integration/vite-dev-custom-entry-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { test, expect } from "@playwright/test";
import type { Readable } from "node:stream";
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
import resolveBin from "resolve-bin";
import getPort from "get-port";
import waitOn from "wait-on";

import { createFixtureProject, js } from "./helpers/create-fixture.js";
import { killtree } from "./helpers/killtree.js";

test.describe("Vite custom entry dev", () => {
let projectDir: string;
let devProc: ChildProcessWithoutNullStreams;
let devPort: number;

test.beforeAll(async () => {
devPort = await getPort();
projectDir = await createFixtureProject({
compiler: "vite",
files: {
"remix.config.js": js`
throw new Error("Remix should not access remix.config.js when using Vite");
export default {};
`,
"vite.config.ts": js`
import { defineConfig } from "vite";
import { unstable_vitePlugin as remix } from "@remix-run/dev";
export default defineConfig({
server: {
port: ${devPort},
strictPort: true,
},
plugins: [
remix(),
],
});
`,
"app/entry.server.tsx": js`
import { PassThrough } from "node:stream";
import type { EntryContext } from "@remix-run/node";
import { createReadableStreamFromReadable } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import { renderToPipeableStream } from "react-dom/server";
const ABORT_DELAY = 5_000;
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
return new Promise((resolve, reject) => {
let shellRendered = false;
const { pipe, abort } = renderToPipeableStream(
<RemixServer
context={remixContext}
url={request.url}
abortDelay={ABORT_DELAY}
/>,
{
onShellReady() {
shellRendered = true;
const body = new PassThrough();
const stream = createReadableStreamFromReadable(body);
responseHeaders.set("Content-Type", "text/html");
// Used to test that the request object is an instance of the global Request constructor
responseHeaders.set("x-test-request-instanceof-request", String(request instanceof Request));
resolve(
new Response(stream, {
headers: responseHeaders,
status: responseStatusCode,
})
);
pipe(body);
},
onShellError(error: unknown) {
reject(error);
},
onError(error: unknown) {
responseStatusCode = 500;
// Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll
// reject and get logged in handleDocumentRequest.
if (shellRendered) {
console.error(error);
}
},
}
);
setTimeout(abort, ABORT_DELAY);
});
}
`,
"app/root.tsx": js`
import { Links, Meta, Outlet, Scripts, LiveReload } from "@remix-run/react";
export default function Root() {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
<div id="content">
<h1>Root</h1>
<Outlet />
</div>
<Scripts />
<LiveReload />
</body>
</html>
);
}
`,
"app/routes/_index.tsx": js`
export default function IndexRoute() {
return <div>IndexRoute</div>
}
`,
},
});

let nodeBin = process.argv[0];
let viteBin = resolveBin.sync("vite");
devProc = spawn(nodeBin, [viteBin, "dev"], {
cwd: projectDir,
env: process.env,
stdio: "pipe",
});
let devStdout = bufferize(devProc.stdout);
let devStderr = bufferize(devProc.stderr);

await waitOn({
resources: [`http://localhost:${devPort}/`],
timeout: 10000,
}).catch((err) => {
let stdout = devStdout();
let stderr = devStderr();
throw new Error(
[
err.message,
"",
"exit code: " + devProc.exitCode,
"stdout: " + stdout ? `\n${stdout}\n` : "<empty>",
"stderr: " + stderr ? `\n${stderr}\n` : "<empty>",
].join("\n")
);
});
});

test.afterAll(async () => {
devProc.pid && (await killtree(devProc.pid));
});

// Ensure libraries/consumers can perform an instanceof check on the request
test("request instanceof Request", async ({ request }) => {
let res = await request.get(`http://localhost:${devPort}/`);
expect(res.headers()).toMatchObject({
"x-test-request-instanceof-request": "true",
});
});
});

let bufferize = (stream: Readable): (() => string) => {
let buffer = "";
stream.on("data", (data) => (buffer += data.toString()));
return () => buffer;
};
2 changes: 0 additions & 2 deletions packages/remix-dev/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
"minimatch": "^9.0.0",
"node-fetch": "^2.6.9",
"ora": "^5.4.1",
"parse-multipart-data": "^1.5.0",
"picocolors": "^1.0.0",
"picomatch": "^2.3.1",
"pidtree": "^0.6.0",
Expand All @@ -72,7 +71,6 @@
"set-cookie-parser": "^2.6.0",
"tar-fs": "^2.1.1",
"tsconfig-paths": "^4.0.0",
"undici": "^5.22.1",
"ws": "^7.4.5"
},
"devDependencies": {
Expand Down
Loading

0 comments on commit 3bf45f0

Please sign in to comment.