|
1 | 1 | // @refresh skip |
2 | | -// |
3 | | -// NOTE: This file intentionally avoids JSX to work around a crash in |
4 | | -// babel-plugin-jsx-dom-expressions@0.41.0-next.9 when spread attributes are |
5 | | -// used on native HTML elements during SSR transformation. The previous |
6 | | -// implementation used JSX (<style {...props.attrs}> etc.) which triggered the |
7 | | -// bug. Instead we build the HTML strings directly — these assets are only |
8 | | -// ever rendered during SSR so reactive JSX is unnecessary. |
9 | | - |
| 2 | +import { NoHydration } from "@solidjs/web"; |
10 | 3 | import type { JSX } from "@solidjs/web"; |
11 | 4 |
|
12 | | -/** Escape HTML special characters for safe attribute / text insertion. */ |
13 | | -function esc(s: string): string { |
14 | | - return s.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">"); |
15 | | -} |
16 | | - |
17 | | -function renderAttrs(obj: Record<string, any>): string { |
18 | | - let out = ""; |
19 | | - for (const [k, v] of Object.entries(obj)) { |
20 | | - if (v == null || v === false || v === undefined) continue; |
21 | | - if (v === true) { |
22 | | - out += ` ${esc(k)}`; |
23 | | - } else { |
24 | | - out += ` ${esc(k)}="${esc(String(v))}"`; |
25 | | - } |
26 | | - } |
27 | | - return out; |
28 | | -} |
| 5 | +const assetMap = { |
| 6 | + style: (props: { attrs: JSX.StyleHTMLAttributes<HTMLStyleElement>; children?: JSX.Element }) => ( |
| 7 | + <style {...props.attrs}>{props.children}</style> |
| 8 | + ), |
| 9 | + link: (props: { attrs: JSX.LinkHTMLAttributes<HTMLLinkElement> }) => <link {...props.attrs} />, |
| 10 | + script: (props: { |
| 11 | + attrs: JSX.ScriptHTMLAttributes<HTMLScriptElement>; |
| 12 | + key: string | undefined; |
| 13 | + }) => { |
| 14 | + return props.attrs.src ? ( |
| 15 | + <script {...props.attrs} id={props.key}> |
| 16 | + {" "} |
| 17 | + </script> |
| 18 | + ) : null; |
| 19 | + }, |
| 20 | + noscript: (props: { attrs: JSX.HTMLAttributes<HTMLElement>; children: JSX.Element }) => ( |
| 21 | + <noscript {...props.attrs}>{props.children}</noscript> |
| 22 | + ), |
| 23 | +}; |
29 | 24 |
|
30 | 25 | export function renderAsset(asset: Asset, nonce?: string) { |
31 | 26 | let { |
32 | 27 | tag, |
33 | | - attrs: { key, ...rest } = { key: undefined }, |
| 28 | + attrs: { key, ...attrs } = { key: undefined }, |
34 | 29 | children, |
35 | 30 | } = asset as any; |
36 | | - |
37 | | - const allAttrs = { ...rest, nonce } as Record<string, any>; |
38 | | - |
39 | | - let html: string; |
40 | | - switch (tag as string) { |
41 | | - case "style": |
42 | | - html = `<style${renderAttrs(allAttrs)}>${children ?? ""}</style>`; |
43 | | - break; |
44 | | - case "link": |
45 | | - html = `<link${renderAttrs(allAttrs)} />`; |
46 | | - break; |
47 | | - case "script": |
48 | | - if (!allAttrs.src) return { t: "" }; |
49 | | - html = `<script${renderAttrs(allAttrs)}${key ? ` id="${esc(key)}"` : ""}> </script>`; |
50 | | - break; |
51 | | - case "noscript": |
52 | | - html = `<noscript${renderAttrs(allAttrs)}>${children ?? ""}</noscript>`; |
53 | | - break; |
54 | | - default: |
55 | | - html = ""; |
56 | | - } |
57 | | - |
58 | | - // Return as an SSR node object ({ t: string }) so that @solidjs/web's |
59 | | - // escape() passes it through unmodified and resolveSSRNode() uses it directly. |
60 | | - return { t: html }; |
| 31 | + // Asset thunks run at shell-injection time with no reactive owner; NoHydration |
| 32 | + // provides one (with hydration-key generation disabled) so ssrElement doesn't |
| 33 | + // throw. Head asset tags never hydrate, so they shouldn't carry _hk anyway. |
| 34 | + return ( |
| 35 | + <NoHydration>{(assetMap as any)[tag]({ attrs: { ...attrs, nonce }, key, children })}</NoHydration> |
| 36 | + ); |
61 | 37 | } |
62 | 38 |
|
63 | 39 | export type Asset = |
|
0 commit comments