fix(browser-extensions): don't render directly in body

This commit is contained in:
Sam Denty 2024-10-04 09:31:25 +02:00 committed by Dominic Elm
parent b17d6a58f5
commit 50885b065d
5 changed files with 87 additions and 17 deletions

View File

@ -3,5 +3,5 @@ import { startTransition } from 'react';
import { hydrateRoot } from 'react-dom/client';
startTransition(() => {
hydrateRoot(document, <RemixBrowser />);
hydrateRoot(document.getElementById('root')!, <RemixBrowser />);
});

View File

@ -2,6 +2,9 @@ import type { AppLoadContext, EntryContext } from '@remix-run/cloudflare';
import { RemixServer } from '@remix-run/react';
import { isbot } from 'isbot';
import { renderToReadableStream } from 'react-dom/server';
import { renderHeadToString } from 'remix-island';
import { Head } from './root';
import { themeStore } from '~/lib/stores/theme';
export default async function handleRequest(
request: Request,
@ -10,7 +13,7 @@ export default async function handleRequest(
remixContext: EntryContext,
_loadContext: AppLoadContext,
) {
const body = await renderToReadableStream(<RemixServer context={remixContext} url={request.url} />, {
const readable = await renderToReadableStream(<RemixServer context={remixContext} url={request.url} />, {
signal: request.signal,
onError(error: unknown) {
console.error(error);
@ -18,8 +21,49 @@ export default async function handleRequest(
},
});
const body = new ReadableStream({
start(controller) {
const head = renderHeadToString({ request, remixContext, Head });
controller.enqueue(
new Uint8Array(
new TextEncoder().encode(
`<!DOCTYPE html><html lang="en" data-theme="${themeStore.value}"><head>${head}</head><body><div id="root" class="w-full h-full">`,
),
),
);
const reader = readable.getReader();
function read() {
reader
.read()
.then(({ done, value }) => {
if (done) {
controller.enqueue(new Uint8Array(new TextEncoder().encode(`</div></body></html>`)));
controller.close();
return;
}
controller.enqueue(value);
read();
})
.catch((error) => {
controller.error(error);
readable.cancel();
});
}
read();
},
cancel() {
readable.cancel();
},
});
if (isbot(request.headers.get('user-agent') || '')) {
await body.allReady;
await readable.allReady;
}
responseHeaders.set('Content-Type', 'text/html');

View File

@ -4,6 +4,8 @@ import { Links, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/reac
import tailwindReset from '@unocss/reset/tailwind-compat.css?url';
import { themeStore } from './lib/stores/theme';
import { stripIndents } from './utils/stripIndent';
import { createHead } from 'remix-island';
import { useEffect } from 'react';
import reactToastifyStyles from 'react-toastify/dist/ReactToastify.css?url';
import globalStyles from './styles/index.scss?url';
@ -50,24 +52,29 @@ const inlineThemeCode = stripIndents`
}
`;
export const Head = createHead(() => (
<>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
<script dangerouslySetInnerHTML={{ __html: inlineThemeCode }} />
</>
));
export function Layout({ children }: { children: React.ReactNode }) {
const theme = useStore(themeStore);
useEffect(() => {
document.querySelector('html')?.setAttribute('data-theme', theme);
}, [theme]);
return (
<html lang="en" data-theme={theme}>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
<script dangerouslySetInnerHTML={{ __html: inlineThemeCode }} />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
<>
{children}
<ScrollRestoration />
<Scripts />
</>
);
}

View File

@ -73,6 +73,7 @@
"remark-gfm": "^4.0.0",
"remix-utils": "^7.6.0",
"shiki": "^1.9.1",
"remix-island": "^0.2.0",
"unist-util-visit": "^5.0.0"
},
"devDependencies": {

View File

@ -155,6 +155,9 @@ importers:
remark-gfm:
specifier: ^4.0.0
version: 4.0.0
remix-island:
specifier: ^0.2.0
version: 0.2.0(@remix-run/react@2.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.2))(@remix-run/server-runtime@2.10.2(typescript@5.5.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
remix-utils:
specifier: ^7.6.0
version: 7.6.0(@remix-run/cloudflare@2.10.2(@cloudflare/workers-types@4.20240620.0)(typescript@5.5.2))(@remix-run/node@2.10.2(typescript@5.5.2))(@remix-run/react@2.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.2))(@remix-run/router@1.17.1)(react@18.3.1)(zod@3.23.8)
@ -4331,6 +4334,14 @@ packages:
remark-stringify@11.0.0:
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
remix-island@0.2.0:
resolution: {integrity: sha512-NujWtmulgupxNOMiWKAj8lg56eYsy09aV/2pML8rov8N8LmY1UnSml4XYad+KHLy/pgZ1D9UxAmjI6GBJydTUg==}
peerDependencies:
'@remix-run/react': '>= 1'
'@remix-run/server-runtime': '>= 1'
react: '>= 16.8'
react-dom: '>= 16.8'
remix-utils@7.6.0:
resolution: {integrity: sha512-BPhCUEy+nwrhDDDg2v3+LFSszV6tluMbeSkbffj2o4tqZxt5Kn69Y9sNpGxYLAj8gjqeYDuxjv55of+gYnnykA==}
engines: {node: '>=18.0.0'}
@ -10133,6 +10144,13 @@ snapshots:
mdast-util-to-markdown: 2.1.0
unified: 11.0.5
remix-island@0.2.0(@remix-run/react@2.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.2))(@remix-run/server-runtime@2.10.2(typescript@5.5.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@remix-run/react': 2.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.2)
'@remix-run/server-runtime': 2.10.2(typescript@5.5.2)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
remix-utils@7.6.0(@remix-run/cloudflare@2.10.2(@cloudflare/workers-types@4.20240620.0)(typescript@5.5.2))(@remix-run/node@2.10.2(typescript@5.5.2))(@remix-run/react@2.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.2))(@remix-run/router@1.17.1)(react@18.3.1)(zod@3.23.8):
dependencies:
type-fest: 4.21.0