Sam Denty commited on
Commit
50885b0
·
1 Parent(s): b17d6a5

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

Browse files
Files changed (5) hide show
  1. app/entry.client.tsx +1 -1
  2. app/entry.server.tsx +46 -2
  3. app/root.tsx +21 -14
  4. package.json +1 -0
  5. pnpm-lock.yaml +18 -0
app/entry.client.tsx CHANGED
@@ -3,5 +3,5 @@ import { startTransition } from 'react';
3
  import { hydrateRoot } from 'react-dom/client';
4
 
5
  startTransition(() => {
6
- hydrateRoot(document, <RemixBrowser />);
7
  });
 
3
  import { hydrateRoot } from 'react-dom/client';
4
 
5
  startTransition(() => {
6
+ hydrateRoot(document.getElementById('root')!, <RemixBrowser />);
7
  });
app/entry.server.tsx CHANGED
@@ -2,6 +2,9 @@ import type { AppLoadContext, EntryContext } from '@remix-run/cloudflare';
2
  import { RemixServer } from '@remix-run/react';
3
  import { isbot } from 'isbot';
4
  import { renderToReadableStream } from 'react-dom/server';
 
 
 
5
 
6
  export default async function handleRequest(
7
  request: Request,
@@ -10,7 +13,7 @@ export default async function handleRequest(
10
  remixContext: EntryContext,
11
  _loadContext: AppLoadContext,
12
  ) {
13
- const body = await renderToReadableStream(<RemixServer context={remixContext} url={request.url} />, {
14
  signal: request.signal,
15
  onError(error: unknown) {
16
  console.error(error);
@@ -18,8 +21,49 @@ export default async function handleRequest(
18
  },
19
  });
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  if (isbot(request.headers.get('user-agent') || '')) {
22
- await body.allReady;
23
  }
24
 
25
  responseHeaders.set('Content-Type', 'text/html');
 
2
  import { RemixServer } from '@remix-run/react';
3
  import { isbot } from 'isbot';
4
  import { renderToReadableStream } from 'react-dom/server';
5
+ import { renderHeadToString } from 'remix-island';
6
+ import { Head } from './root';
7
+ import { themeStore } from '~/lib/stores/theme';
8
 
9
  export default async function handleRequest(
10
  request: Request,
 
13
  remixContext: EntryContext,
14
  _loadContext: AppLoadContext,
15
  ) {
16
+ const readable = await renderToReadableStream(<RemixServer context={remixContext} url={request.url} />, {
17
  signal: request.signal,
18
  onError(error: unknown) {
19
  console.error(error);
 
21
  },
22
  });
23
 
24
+ const body = new ReadableStream({
25
+ start(controller) {
26
+ const head = renderHeadToString({ request, remixContext, Head });
27
+
28
+ controller.enqueue(
29
+ new Uint8Array(
30
+ new TextEncoder().encode(
31
+ `<!DOCTYPE html><html lang="en" data-theme="${themeStore.value}"><head>${head}</head><body><div id="root" class="w-full h-full">`,
32
+ ),
33
+ ),
34
+ );
35
+
36
+ const reader = readable.getReader();
37
+
38
+ function read() {
39
+ reader
40
+ .read()
41
+ .then(({ done, value }) => {
42
+ if (done) {
43
+ controller.enqueue(new Uint8Array(new TextEncoder().encode(`</div></body></html>`)));
44
+ controller.close();
45
+
46
+ return;
47
+ }
48
+
49
+ controller.enqueue(value);
50
+ read();
51
+ })
52
+ .catch((error) => {
53
+ controller.error(error);
54
+ readable.cancel();
55
+ });
56
+ }
57
+ read();
58
+ },
59
+
60
+ cancel() {
61
+ readable.cancel();
62
+ },
63
+ });
64
+
65
  if (isbot(request.headers.get('user-agent') || '')) {
66
+ await readable.allReady;
67
  }
68
 
69
  responseHeaders.set('Content-Type', 'text/html');
app/root.tsx CHANGED
@@ -4,6 +4,8 @@ import { Links, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/reac
4
  import tailwindReset from '@unocss/reset/tailwind-compat.css?url';
5
  import { themeStore } from './lib/stores/theme';
6
  import { stripIndents } from './utils/stripIndent';
 
 
7
 
8
  import reactToastifyStyles from 'react-toastify/dist/ReactToastify.css?url';
9
  import globalStyles from './styles/index.scss?url';
@@ -50,24 +52,29 @@ const inlineThemeCode = stripIndents`
50
  }
51
  `;
52
 
 
 
 
 
 
 
 
 
 
 
53
  export function Layout({ children }: { children: React.ReactNode }) {
54
  const theme = useStore(themeStore);
55
 
 
 
 
 
56
  return (
57
- <html lang="en" data-theme={theme}>
58
- <head>
59
- <meta charSet="utf-8" />
60
- <meta name="viewport" content="width=device-width, initial-scale=1" />
61
- <Meta />
62
- <Links />
63
- <script dangerouslySetInnerHTML={{ __html: inlineThemeCode }} />
64
- </head>
65
- <body>
66
- {children}
67
- <ScrollRestoration />
68
- <Scripts />
69
- </body>
70
- </html>
71
  );
72
  }
73
 
 
4
  import tailwindReset from '@unocss/reset/tailwind-compat.css?url';
5
  import { themeStore } from './lib/stores/theme';
6
  import { stripIndents } from './utils/stripIndent';
7
+ import { createHead } from 'remix-island';
8
+ import { useEffect } from 'react';
9
 
10
  import reactToastifyStyles from 'react-toastify/dist/ReactToastify.css?url';
11
  import globalStyles from './styles/index.scss?url';
 
52
  }
53
  `;
54
 
55
+ export const Head = createHead(() => (
56
+ <>
57
+ <meta charSet="utf-8" />
58
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
59
+ <Meta />
60
+ <Links />
61
+ <script dangerouslySetInnerHTML={{ __html: inlineThemeCode }} />
62
+ </>
63
+ ));
64
+
65
  export function Layout({ children }: { children: React.ReactNode }) {
66
  const theme = useStore(themeStore);
67
 
68
+ useEffect(() => {
69
+ document.querySelector('html')?.setAttribute('data-theme', theme);
70
+ }, [theme]);
71
+
72
  return (
73
+ <>
74
+ {children}
75
+ <ScrollRestoration />
76
+ <Scripts />
77
+ </>
 
 
 
 
 
 
 
 
 
78
  );
79
  }
80
 
package.json CHANGED
@@ -73,6 +73,7 @@
73
  "remark-gfm": "^4.0.0",
74
  "remix-utils": "^7.6.0",
75
  "shiki": "^1.9.1",
 
76
  "unist-util-visit": "^5.0.0"
77
  },
78
  "devDependencies": {
 
73
  "remark-gfm": "^4.0.0",
74
  "remix-utils": "^7.6.0",
75
  "shiki": "^1.9.1",
76
+ "remix-island": "^0.2.0",
77
  "unist-util-visit": "^5.0.0"
78
  },
79
  "devDependencies": {
pnpm-lock.yaml CHANGED
@@ -155,6 +155,9 @@ importers:
155
  remark-gfm:
156
  specifier: ^4.0.0
157
  version: 4.0.0
 
 
 
158
  remix-utils:
159
  specifier: ^7.6.0
160
@@ -4331,6 +4334,14 @@ packages:
4331
4332
  resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
4333
 
 
 
 
 
 
 
 
 
4334
4335
  resolution: {integrity: sha512-BPhCUEy+nwrhDDDg2v3+LFSszV6tluMbeSkbffj2o4tqZxt5Kn69Y9sNpGxYLAj8gjqeYDuxjv55of+gYnnykA==}
4336
  engines: {node: '>=18.0.0'}
@@ -10133,6 +10144,13 @@ snapshots:
10133
  mdast-util-to-markdown: 2.1.0
10134
  unified: 11.0.5
10135
 
 
 
 
 
 
 
 
10136
10137
  dependencies:
10138
  type-fest: 4.21.0
 
155
  remark-gfm:
156
  specifier: ^4.0.0
157
  version: 4.0.0
158
+ remix-island:
159
+ specifier: ^0.2.0
160
161
  remix-utils:
162
  specifier: ^7.6.0
163
 
4334
4335
  resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
4336
 
4337
4338
+ resolution: {integrity: sha512-NujWtmulgupxNOMiWKAj8lg56eYsy09aV/2pML8rov8N8LmY1UnSml4XYad+KHLy/pgZ1D9UxAmjI6GBJydTUg==}
4339
+ peerDependencies:
4340
+ '@remix-run/react': '>= 1'
4341
+ '@remix-run/server-runtime': '>= 1'
4342
+ react: '>= 16.8'
4343
+ react-dom: '>= 16.8'
4344
+
4345
4346
  resolution: {integrity: sha512-BPhCUEy+nwrhDDDg2v3+LFSszV6tluMbeSkbffj2o4tqZxt5Kn69Y9sNpGxYLAj8gjqeYDuxjv55of+gYnnykA==}
4347
  engines: {node: '>=18.0.0'}
 
10144
  mdast-util-to-markdown: 2.1.0
10145
  unified: 11.0.5
10146
 
10147
10148
+ dependencies:
10149
10150
+ '@remix-run/server-runtime': 2.10.2([email protected])
10151
+ react: 18.3.1
10152
+ react-dom: 18.3.1([email protected])
10153
+
10154
10155
  dependencies:
10156
  type-fest: 4.21.0