Switch to renderToPipeableStream API

Closes #1152

Did something weird to remove errors on /plans
This commit is contained in:
Kalle 2022-11-23 21:33:46 +02:00
parent 6061c9672a
commit 78d55fc896
5 changed files with 130 additions and 20 deletions

View File

@ -30,6 +30,7 @@ module.exports = {
"@typescript-eslint/no-unsafe-argument": 0,
"@typescript-eslint/no-non-null-assertion": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/unbound-method": 0,
"react/prop-types": 0,
"@typescript-eslint/no-restricted-imports": [
"error",

View File

@ -1,32 +1,125 @@
import { PassThrough } from "stream";
import type { EntryContext } from "@remix-run/node";
import { Response } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import { I18nextProvider } from "react-i18next";
import { renderToString } from "react-dom/server";
import isbot from "isbot";
import { renderToPipeableStream } from "react-dom/server";
import cron from "node-cron";
import { updatePatreonData } from "./modules/patreon";
import { i18Instance } from "./modules/i18n";
import { I18nextProvider } from "react-i18next";
export default async function handleRequest(
const ABORT_DELAY = 5000;
const handleRequest = (
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
const i18n = await i18Instance(request, remixContext);
) =>
isbot(request.headers.get("user-agent"))
? handleBotRequest(
request,
responseStatusCode,
responseHeaders,
remixContext
)
: handleBrowserRequest(
request,
responseStatusCode,
responseHeaders,
remixContext
);
export default handleRequest;
const markup = renderToString(
<I18nextProvider i18n={i18n}>
<RemixServer context={remixContext} url={request.url} />
</I18nextProvider>
);
const handleBotRequest = (
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) =>
new Promise((resolve, reject) => {
let didError = false;
responseHeaders.set("Content-Type", "text/html");
void i18Instance(request, remixContext).then((i18n) => {
const { pipe, abort } = renderToPipeableStream(
<I18nextProvider i18n={i18n}>
<RemixServer context={remixContext} url={request.url} />
</I18nextProvider>,
{
onAllReady: () => {
const body = new PassThrough();
return new Response("<!DOCTYPE html>" + markup, {
status: responseStatusCode,
headers: responseHeaders,
responseHeaders.set("Content-Type", "text/html");
resolve(
new Response(body, {
headers: responseHeaders,
status: didError ? 500 : responseStatusCode,
})
);
pipe(body);
},
onShellError: (error: unknown) => {
reject(error);
},
onError: (error: unknown) => {
didError = true;
console.error(error);
},
}
);
setTimeout(abort, ABORT_DELAY);
});
});
const handleBrowserRequest = (
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) =>
new Promise((resolve, reject) => {
let didError = false;
void i18Instance(request, remixContext).then((i18n) => {
const { pipe, abort } = renderToPipeableStream(
<I18nextProvider i18n={i18n}>
<RemixServer context={remixContext} url={request.url} />
</I18nextProvider>,
{
onShellReady: () => {
const body = new PassThrough();
responseHeaders.set("Content-Type", "text/html");
resolve(
new Response(body, {
headers: responseHeaders,
status: didError ? 500 : responseStatusCode,
})
);
pipe(body);
},
onShellError: (error: unknown) => {
reject(error);
},
onError: (error: unknown) => {
didError = true;
console.error(error);
},
}
);
setTimeout(abort, ABORT_DELAY);
});
});
}
// example from https://github.com/BenMcH/remix-rss/blob/main/app/entry.server.tsx
declare global {

View File

@ -2,6 +2,7 @@ import { lazy, Suspense } from "react";
import type { LinksFunction } from "@remix-run/node";
import styles from "~/styles/plans.css";
import type { SendouRouteHandle } from "~/utils/remix";
import { useIsMounted } from "~/hooks/useIsMounted";
export const handle: SendouRouteHandle = {
i18n: ["weapons"],
@ -14,9 +15,9 @@ export const links: LinksFunction = () => {
const Planner = lazy(() => import("~/components/Planner"));
export default function MapPlannerPage() {
return (
<Suspense fallback={<div className="plans__placeholder" />}>
<Planner />
</Suspense>
);
const isMounted = useIsMounted();
if (!isMounted) return <div className="plans__placeholder" />;
return <Planner />;
}

14
package-lock.json generated
View File

@ -23,6 +23,7 @@
"i18next-browser-languagedetector": "^6.1.5",
"i18next-fs-backend": "^1.1.5",
"i18next-http-backend": "^1.4.4",
"isbot": "^3.6.5",
"just-capitalize": "^3.1.1",
"just-clone": "^6.1.1",
"just-random-integer": "^4.1.1",
@ -10050,6 +10051,14 @@
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"node_modules/isbot": {
"version": "3.6.5",
"resolved": "https://registry.npmjs.org/isbot/-/isbot-3.6.5.tgz",
"integrity": "sha512-BchONELXt6yMad++BwGpa0oQxo/uD0keL7N15cYVf0A1oMIoNQ79OqeYdPMFWDrNhCqCbRuw9Y9F3QBjvAxZ5g==",
"engines": {
"node": ">=12"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@ -24062,6 +24071,11 @@
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"isbot": {
"version": "3.6.5",
"resolved": "https://registry.npmjs.org/isbot/-/isbot-3.6.5.tgz",
"integrity": "sha512-BchONELXt6yMad++BwGpa0oQxo/uD0keL7N15cYVf0A1oMIoNQ79OqeYdPMFWDrNhCqCbRuw9Y9F3QBjvAxZ5g=="
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",

View File

@ -48,6 +48,7 @@
"i18next-browser-languagedetector": "^6.1.5",
"i18next-fs-backend": "^1.1.5",
"i18next-http-backend": "^1.4.4",
"isbot": "^3.6.5",
"just-capitalize": "^3.1.1",
"just-clone": "^6.1.1",
"just-random-integer": "^4.1.1",