diff --git a/packages/solid-router/package.json b/packages/solid-router/package.json
index e8cd2c44576..3e41ee95c85 100644
--- a/packages/solid-router/package.json
+++ b/packages/solid-router/package.json
@@ -100,13 +100,14 @@
"dependencies": {
"@solid-devtools/logger": "^0.9.4",
"@solid-primitives/refs": "^1.0.8",
- "@solidjs/meta": "^0.29.4",
"@tanstack/history": "workspace:*",
"@tanstack/router-core": "workspace:*",
"@tanstack/solid-store": "^0.8.0",
+ "@unhead/solid-js": "3.0.0-beta.5",
"isbot": "^5.1.22",
"tiny-invariant": "^1.3.3",
- "tiny-warning": "^1.0.3"
+ "tiny-warning": "^1.0.3",
+ "unhead": "3.0.0-beta.5"
},
"devDependencies": {
"@solidjs/testing-library": "^0.8.10",
diff --git a/packages/solid-router/src/Asset.tsx b/packages/solid-router/src/Asset.tsx
index 8e1e79bff2e..7c9a0c3fc13 100644
--- a/packages/solid-router/src/Asset.tsx
+++ b/packages/solid-router/src/Asset.tsx
@@ -1,140 +1,56 @@
-import { Link, Meta, Style, Title } from '@solidjs/meta'
-import { onCleanup, onMount } from 'solid-js'
-import { useRouter } from './useRouter'
+import { useHead } from '@unhead/solid-js'
import type { RouterManagedTag } from '@tanstack/router-core'
import type { JSX } from 'solid-js'
+import type { UseHeadInput, ValidTagPositions } from 'unhead/types'
+
+export function Asset(
+ props: RouterManagedTag & {
+ tagPosition?: ValidTagPositions
+ },
+): JSX.Element | null {
+ useHead(toHeadInput(props))
+ return null
+}
-export function Asset({
+function toHeadInput({
tag,
attrs,
children,
-}: RouterManagedTag): JSX.Element | null {
- switch (tag) {
- case 'title':
- return
{children}
- case 'meta':
- return
- case 'link':
- return
- case 'style':
- return
- case 'script':
- return
- default:
- return null
+ tagPosition,
+}: RouterManagedTag & {
+ tagPosition?: ValidTagPositions
+}): UseHeadInput {
+ const withPosition = (input: Record) => {
+ if (tagPosition) {
+ input.tagPosition = tagPosition
+ }
+ return input
}
-}
-
-interface ScriptAttrs {
- [key: string]: string | boolean | undefined
- src?: string
-}
-
-function Script({
- attrs,
- children,
-}: {
- attrs?: ScriptAttrs
- children?: string
-}): JSX.Element | null {
- const router = useRouter()
-
- onMount(() => {
- if (attrs?.src) {
- const normSrc = (() => {
- try {
- const base = document.baseURI || window.location.href
- return new URL(attrs.src, base).href
- } catch {
- return attrs.src
- }
- })()
- const existingScript = Array.from(
- document.querySelectorAll('script[src]'),
- ).find((el) => (el as HTMLScriptElement).src === normSrc)
-
- if (existingScript) {
- return
- }
-
- const script = document.createElement('script')
-
- for (const [key, value] of Object.entries(attrs)) {
- if (value !== undefined && value !== false) {
- script.setAttribute(
- key,
- typeof value === 'boolean' ? '' : String(value),
- )
- }
- }
-
- document.head.appendChild(script)
- onCleanup(() => {
- if (script.parentNode) {
- script.parentNode.removeChild(script)
- }
- })
+ switch (tag) {
+ case 'title':
+ return { title: children }
+ case 'meta': {
+ return { meta: [withPosition({ ...(attrs ?? {}) })] }
}
-
- if (typeof children === 'string') {
- const typeAttr =
- typeof attrs?.type === 'string' ? attrs.type : 'text/javascript'
- const nonceAttr =
- typeof attrs?.nonce === 'string' ? attrs.nonce : undefined
- const existingScript = Array.from(
- document.querySelectorAll('script:not([src])'),
- ).find((el) => {
- if (!(el instanceof HTMLScriptElement)) return false
- const sType = el.getAttribute('type') ?? 'text/javascript'
- const sNonce = el.getAttribute('nonce') ?? undefined
- return (
- el.textContent === children &&
- sType === typeAttr &&
- sNonce === nonceAttr
- )
- })
-
- if (existingScript) {
- return
+ case 'link': {
+ return { link: [withPosition({ ...(attrs ?? {}) })] }
+ }
+ case 'style': {
+ const style = withPosition({ ...(attrs ?? {}) })
+ if (typeof children === 'string') {
+ style.textContent = children
}
-
- const script = document.createElement('script')
- script.textContent = children
-
- if (attrs) {
- for (const [key, value] of Object.entries(attrs)) {
- if (value !== undefined && value !== false) {
- script.setAttribute(
- key,
- typeof value === 'boolean' ? '' : String(value),
- )
- }
- }
+ return { style: [style] }
+ }
+ case 'script': {
+ const script = withPosition({ ...(attrs ?? {}) })
+ if (typeof children === 'string') {
+ script.textContent = children
}
-
- document.head.appendChild(script)
-
- onCleanup(() => {
- if (script.parentNode) {
- script.parentNode.removeChild(script)
- }
- })
+ return { script: [script] }
}
- })
-
- if (!router.isServer) {
- // render an empty script on the client just to avoid hydration errors
- return null
- }
-
- if (attrs?.src && typeof attrs.src === 'string') {
- return
- }
-
- if (typeof children === 'string') {
- return
+ default:
+ return {}
}
-
- return null
}
diff --git a/packages/solid-router/src/HeadContent.tsx b/packages/solid-router/src/HeadContent.tsx
index 103be65c465..c57bd37b309 100644
--- a/packages/solid-router/src/HeadContent.tsx
+++ b/packages/solid-router/src/HeadContent.tsx
@@ -1,5 +1,4 @@
import * as Solid from 'solid-js'
-import { MetaProvider } from '@solidjs/meta'
import { For } from 'solid-js'
import { Asset } from './Asset'
import { useRouter } from './useRouter'
@@ -185,16 +184,12 @@ export const useTags = () => {
* @description The `HeadContent` component is used to render meta tags, links, and scripts for the current route.
* When using full document hydration (hydrating from ``), this component should be rendered in the ``
* to ensure it's part of the reactive tree and updates correctly during client-side navigation.
- * The component uses portals internally to render content into the `` element.
+ * The component registers tags with Unhead so the provider can render them into the `` element.
*/
export function HeadContent() {
const tags = useTags()
- return (
-
- {(tag) => }
-
- )
+ return {(tag) => }
}
function uniqBy(arr: Array, fn: (item: T) => string) {
diff --git a/packages/solid-router/src/RouterProvider.tsx b/packages/solid-router/src/RouterProvider.tsx
index 1092d9b43c9..cd901ba359b 100644
--- a/packages/solid-router/src/RouterProvider.tsx
+++ b/packages/solid-router/src/RouterProvider.tsx
@@ -1,3 +1,6 @@
+import { UnheadContext, createHead } from '@unhead/solid-js/client'
+import { useContext } from 'solid-js'
+import { isServer } from 'solid-js/web'
import { getRouterContext } from './routerContext'
import { SafeFragment } from './SafeFragment'
import { Matches } from './Matches'
@@ -8,6 +11,21 @@ import type {
} from '@tanstack/router-core'
import type * as Solid from 'solid-js'
+let clientHead: ReturnType | undefined
+
+function HeadProvider(props: { children: () => Solid.JSX.Element }) {
+ const existing = useContext(UnheadContext)
+ if (existing || isServer) {
+ return props.children()
+ }
+ clientHead ||= createHead()
+ return (
+
+ {props.children()}
+
+ )
+}
+
export function RouterContextProvider<
TRouter extends AnyRouter = RegisteredRouter,
TDehydrated extends Record = Record,
@@ -34,9 +52,13 @@ export function RouterContextProvider<
return (
-
- {children()}
-
+
+ {() => (
+
+ {children()}
+
+ )}
+
)
}
diff --git a/packages/solid-router/src/Scripts.tsx b/packages/solid-router/src/Scripts.tsx
index d900faf55de..2de0da399c2 100644
--- a/packages/solid-router/src/Scripts.tsx
+++ b/packages/solid-router/src/Scripts.tsx
@@ -69,7 +69,7 @@ export const Scripts = () => {
return (
<>
{allScripts.map((asset, i) => (
-
+
))}
>
)
diff --git a/packages/solid-router/src/ssr/RouterClient.tsx b/packages/solid-router/src/ssr/RouterClient.tsx
index d49255312db..60f7754e172 100644
--- a/packages/solid-router/src/ssr/RouterClient.tsx
+++ b/packages/solid-router/src/ssr/RouterClient.tsx
@@ -1,4 +1,9 @@
import { hydrate } from '@tanstack/router-core/ssr/client'
+import {
+ UnheadContext,
+ createHead as createClientHead,
+} from '@unhead/solid-js/client'
+import { createStreamableHead as createStreamableClientHead } from '@unhead/solid-js/stream/client'
import { Await } from '../awaited'
import { HeadContent } from '../HeadContent'
import { RouterProvider } from '../RouterProvider'
@@ -6,10 +11,21 @@ import type { AnyRouter } from '@tanstack/router-core'
import type { JSXElement } from 'solid-js'
let hydrationPromise: Promise>> | undefined
+let headInstance: ReturnType | undefined
const Dummy = (props: { children?: JSXElement }) => <>{props.children}>
+const getHeadInstance = () => {
+ if (!headInstance) {
+ headInstance =
+ (createStreamableClientHead()) ?? createClientHead()
+ }
+ return headInstance
+}
+
export function RouterClient(props: { router: AnyRouter }) {
+ const head = getHeadInstance()
+
if (!hydrationPromise) {
if (!props.router.state.matches.length) {
hydrationPromise = hydrate(props.router)
@@ -18,26 +34,28 @@ export function RouterClient(props: { router: AnyRouter }) {
}
}
return (
- (
-
+
+ (
- (
-
+
+ (
-
- {props.children}
+
+
+ {props.children}
+
+
-
-
- )}
- />
+ )}
+ />
+
-
- )}
- />
+ )}
+ />
+
)
}
diff --git a/packages/solid-router/src/ssr/RouterServer.tsx b/packages/solid-router/src/ssr/RouterServer.tsx
index 99b4e684bbb..3acb545f99f 100644
--- a/packages/solid-router/src/ssr/RouterServer.tsx
+++ b/packages/solid-router/src/ssr/RouterServer.tsx
@@ -3,58 +3,51 @@ import {
HydrationScript,
NoHydration,
ssr,
- useAssets,
} from 'solid-js/web'
-import { MetaProvider } from '@solidjs/meta'
-import { Asset } from '../Asset'
-import { useTags } from '../HeadContent'
+import { HeadStream } from '@unhead/solid-js/stream/server'
+import { HeadContent } from '../HeadContent'
import { RouterProvider } from '../RouterProvider'
import { Scripts } from '../Scripts'
+import type { JSXElement } from 'solid-js'
import type { AnyRouter } from '@tanstack/router-core'
-export function ServerHeadContent() {
- const tags = useTags()
- useAssets(() => {
- return (
-
- {tags().map((tag) => (
-
- ))}
-
- )
- })
- return null
-}
-
const docType = ssr('')
export function RouterServer(props: {
router: TRouter
}) {
+ const headStream = HeadStream() as unknown as JSXElement
return (
{docType as any}
+ {headStream}
-
- (
-
-
-
- {props.children}
-
-
-
- )}
- />
-
+
)
}
+
+export function RouterServerBody(props: {
+ router: TRouter
+}) {
+ return (
+
+ (
+
+
+ {props.children}
+
+
+ )}
+ />
+
+ )
+}
diff --git a/packages/solid-router/src/ssr/defaultStreamHandler.tsx b/packages/solid-router/src/ssr/defaultStreamHandler.tsx
index 16b158f8b01..debdb27735c 100644
--- a/packages/solid-router/src/ssr/defaultStreamHandler.tsx
+++ b/packages/solid-router/src/ssr/defaultStreamHandler.tsx
@@ -1,5 +1,5 @@
import { defineHandlerCallback } from '@tanstack/router-core/ssr/server'
-import { RouterServer } from './RouterServer'
+import { RouterServerBody } from './RouterServer'
import { renderRouterToStream } from './renderRouterToStream'
export const defaultStreamHandler = defineHandlerCallback(
@@ -8,6 +8,6 @@ export const defaultStreamHandler = defineHandlerCallback(
request,
router,
responseHeaders,
- children: () => ,
+ children: () => ,
}),
)
diff --git a/packages/solid-router/src/ssr/renderRouterToStream.tsx b/packages/solid-router/src/ssr/renderRouterToStream.tsx
index 5c3e4030603..c7ff4d1c373 100644
--- a/packages/solid-router/src/ssr/renderRouterToStream.tsx
+++ b/packages/solid-router/src/ssr/renderRouterToStream.tsx
@@ -1,25 +1,38 @@
-import * as Solid from 'solid-js/web'
+import { TransformStream } from 'node:stream/web'
+import {
+ HydrationScript,
+ NoHydration,
+ renderToStream,
+ renderToString,
+ ssr,
+} from 'solid-js/web'
+import {
+ HeadStream,
+ UnheadContext,
+ createStreamableHead,
+} from '@unhead/solid-js/stream/server'
import { isbot } from 'isbot'
import { transformReadableStreamWithRouter } from '@tanstack/router-core/ssr/server'
import { makeSsrSerovalPlugin } from '@tanstack/router-core'
import type { JSXElement } from 'solid-js'
-import type { ReadableStream } from 'node:stream/web'
+import type { ReadableStream as NodeReadableStream } from 'node:stream/web'
import type { AnyRouter } from '@tanstack/router-core'
+import type { SSRHeadPayload } from 'unhead/types'
export const renderRouterToStream = async ({
request,
router,
responseHeaders,
children,
+ document,
}: {
request: Request
router: AnyRouter
responseHeaders: Headers
children: () => JSXElement
+ document?: boolean
}) => {
- const { writable, readable } = new TransformStream()
-
- const docType = Solid.ssr('')
+ const docType = ssr('')
const serializationAdapters =
(router.options as any)?.serializationAdapters ||
@@ -29,30 +42,319 @@ export const renderRouterToStream = async ({
return plugin
})
- const stream = Solid.renderToStream(
+ const { head, onCompleteShell } = createStreamableHead()
+ let shellState: SSRHeadPayload | undefined
+ let resolveShellState: ((state: SSRHeadPayload) => void) | undefined
+ const shellStateReady = new Promise((resolve) => {
+ resolveShellState = resolve
+ })
+ const originalRender = head.render.bind(head)
+ head.render = (...args) => {
+ const state = originalRender(...args) as SSRHeadPayload
+ shellState ??= state
+ return state
+ }
+ const onCompleteShellWithState = () => {
+ onCompleteShell()
+ if (!shellState) {
+ shellState = head.render() as SSRHeadPayload
+ }
+ resolveShellState?.(shellState)
+ }
+
+ const template = renderToString(
+ () => (
+
+ {docType as any}
+
+
+
+
+
+ ),
+ {
+ nonce: router.options.ssr?.nonce,
+ } as any,
+ )
+ const hydrationScript = renderToString(() => , {
+ nonce: router.options.ssr?.nonce,
+ } as any)
+ const stream = renderToStream(
() => (
- <>
- {docType}
- {children()}
- >
+
+ {document ? (
+ <>
+ {docType as any}
+ {children()}
+ >
+ ) : (
+ <>
+ {HeadStream() as unknown as JSXElement}
+ {children()}
+ >
+ )}
+
),
{
nonce: router.options.ssr?.nonce,
plugins: serovalPlugins,
+ onCompleteShell: onCompleteShellWithState,
} as any,
)
+ const { readable, writable } = new TransformStream()
+ stream.pipeTo(writable)
+
if (isbot(request.headers.get('User-Agent'))) {
await stream
}
- stream.pipeTo(writable)
+
+ const resolvedShellState = await shellStateReady
+ const wrappedStream = document
+ ? injectHeadIntoDocumentStream(
+ readable as unknown as ReadableStream,
+ resolvedShellState,
+ getStreamKey(head),
+ )
+ : wrapBodyStream(
+ readable as unknown as ReadableStream,
+ template,
+ {
+ ...resolvedShellState,
+ headTags: `${hydrationScript}${resolvedShellState.headTags}`,
+ },
+ getStreamKey(head),
+ )
const responseStream = transformReadableStreamWithRouter(
router,
- readable as unknown as ReadableStream,
+ wrappedStream as unknown as NodeReadableStream,
)
return new Response(responseStream as any, {
status: router.state.statusCode,
headers: responseHeaders,
})
}
+
+const DEFAULT_STREAM_KEY = '__unhead__'
+
+function getStreamKey(head: { resolvedOptions?: { experimentalStreamKey?: string } }) {
+ return head.resolvedOptions?.experimentalStreamKey ?? DEFAULT_STREAM_KEY
+}
+
+function createBootstrapScript(streamKey: string) {
+ return ``
+}
+
+function injectHeadIntoDocumentStream(
+ stream: ReadableStream,
+ shellState: SSRHeadPayload,
+ streamKey: string,
+) {
+ const decoder = new TextDecoder()
+ const encoder = new TextEncoder()
+ const headTags = `${createBootstrapScript(streamKey)}${shellState.headTags}`
+ const bodyTagsOpen = shellState.bodyTagsOpen
+ const bodyTags = shellState.bodyTags
+ const htmlAttrs = shellState.htmlAttrs
+ const bodyAttrs = shellState.bodyAttrs
+ const closeTag = ''
+
+ return new ReadableStream({
+ async start(controller) {
+ const reader = stream.getReader()
+ let buffer = ''
+ let shellFlushed = false
+ let bodyTagsInjected = false
+
+ while (true) {
+ const { done, value } = await reader.read()
+ if (done) break
+ buffer += decoder.decode(value, { stream: true })
+
+ if (!shellFlushed) {
+ const bodyOpenEnd = findBodyOpenEnd(buffer)
+ if (bodyOpenEnd === -1) {
+ continue
+ }
+ const shell = buffer.slice(0, bodyOpenEnd)
+ const rest = buffer.slice(bodyOpenEnd)
+ const injectedShell = injectShell(shell, {
+ headTags,
+ bodyTagsOpen,
+ htmlAttrs,
+ bodyAttrs,
+ })
+ controller.enqueue(encoder.encode(injectedShell))
+ buffer = rest
+ shellFlushed = true
+ }
+
+ if (shellFlushed) {
+ if (!bodyTagsInjected && bodyTags) {
+ const index = buffer.toLowerCase().indexOf(closeTag)
+ if (index !== -1) {
+ const beforeClose = buffer.slice(0, index)
+ const afterClose = buffer.slice(index)
+ controller.enqueue(encoder.encode(beforeClose + bodyTags))
+ buffer = afterClose
+ bodyTagsInjected = true
+ } else if (buffer.length > closeTag.length) {
+ const safeIndex = buffer.length - closeTag.length
+ controller.enqueue(encoder.encode(buffer.slice(0, safeIndex)))
+ buffer = buffer.slice(safeIndex)
+ }
+ } else if (buffer.length) {
+ controller.enqueue(encoder.encode(buffer))
+ buffer = ''
+ }
+ }
+ }
+
+ buffer += decoder.decode()
+ if (!shellFlushed) {
+ const injected = injectShell(buffer, {
+ headTags,
+ bodyTagsOpen,
+ htmlAttrs,
+ bodyAttrs,
+ })
+ controller.enqueue(encoder.encode(injected))
+ } else if (!bodyTagsInjected && bodyTags) {
+ const index = buffer.toLowerCase().indexOf(closeTag)
+ if (index !== -1) {
+ controller.enqueue(
+ encoder.encode(
+ buffer.slice(0, index) + bodyTags + buffer.slice(index),
+ ),
+ )
+ } else {
+ controller.enqueue(encoder.encode(buffer + bodyTags))
+ }
+ } else if (buffer.length) {
+ controller.enqueue(encoder.encode(buffer))
+ }
+
+ controller.close()
+ },
+ })
+}
+
+function wrapBodyStream(
+ stream: ReadableStream,
+ template: string,
+ shellState: SSRHeadPayload,
+ streamKey: string,
+) {
+ const encoder = new TextEncoder()
+ const { shell, end } = splitTemplate(template)
+ const headTags = `${createBootstrapScript(streamKey)}${shellState.headTags}`
+ const injectedShell = injectShell(shell, {
+ headTags,
+ bodyTagsOpen: shellState.bodyTagsOpen,
+ htmlAttrs: shellState.htmlAttrs,
+ bodyAttrs: shellState.bodyAttrs,
+ })
+
+ return new ReadableStream({
+ async start(controller) {
+ controller.enqueue(encoder.encode(injectedShell))
+ const reader = stream.getReader()
+
+ while (true) {
+ const { done, value } = await reader.read()
+ if (done) break
+ controller.enqueue(value)
+ }
+
+ controller.enqueue(
+ encoder.encode(`${shellState.bodyTags ?? ''}${end}`),
+ )
+ controller.close()
+ },
+ })
+}
+
+function splitTemplate(template: string) {
+ const bodyOpenEnd = findBodyOpenEnd(template)
+ const lower = template.toLowerCase()
+ const bodyCloseStart = lower.indexOf('')
+ if (bodyOpenEnd === -1 || bodyCloseStart === -1) {
+ return { shell: template, end: '' }
+ }
+ return {
+ shell: template.slice(0, bodyOpenEnd),
+ end: template.slice(bodyCloseStart),
+ }
+}
+
+function findBodyOpenEnd(html: string) {
+ const lower = html.toLowerCase()
+ const start = lower.indexOf('', start)
+ if (end === -1) return -1
+ return end + 1
+}
+
+function injectShell(
+ html: string,
+ {
+ headTags,
+ bodyTagsOpen,
+ htmlAttrs,
+ bodyAttrs,
+ }: {
+ headTags: string
+ bodyTagsOpen: string
+ htmlAttrs: string
+ bodyAttrs: string
+ },
+) {
+ let result = html
+ if (headTags) {
+ result = insertBeforeClosingTag(result, 'head', headTags)
+ }
+ if (htmlAttrs) {
+ result = appendAttrsToTag(result, 'html', htmlAttrs)
+ }
+ if (bodyAttrs) {
+ result = appendAttrsToTag(result, 'body', bodyAttrs)
+ }
+ if (bodyTagsOpen) {
+ result = insertAfterOpeningTag(result, 'body', bodyTagsOpen)
+ }
+ return result
+}
+
+function appendAttrsToTag(html: string, tag: string, attrs: string) {
+ if (!attrs) return html
+ const lower = html.toLowerCase()
+ const openTag = `<${tag}`
+ const start = lower.indexOf(openTag)
+ if (start === -1) return html
+ const end = html.indexOf('>', start)
+ if (end === -1) return html
+ const normalizedAttrs = attrs.startsWith(' ') ? attrs : ` ${attrs}`
+ return html.slice(0, end) + normalizedAttrs + html.slice(end)
+}
+
+function insertAfterOpeningTag(html: string, tag: string, content: string) {
+ if (!content) return html
+ const lower = html.toLowerCase()
+ const openTag = `<${tag}`
+ const start = lower.indexOf(openTag)
+ if (start === -1) return html
+ const end = html.indexOf('>', start)
+ if (end === -1) return html
+ return html.slice(0, end + 1) + content + html.slice(end + 1)
+}
+
+function insertBeforeClosingTag(html: string, tag: string, content: string) {
+ if (!content) return html
+ const lower = html.toLowerCase()
+ const closeTag = `${tag}`
+ const index = lower.indexOf(closeTag)
+ if (index === -1) return html
+ return html.slice(0, index) + content + html.slice(index)
+}
diff --git a/packages/solid-router/src/ssr/renderRouterToString.tsx b/packages/solid-router/src/ssr/renderRouterToString.tsx
index 9d217699377..a173050c848 100644
--- a/packages/solid-router/src/ssr/renderRouterToString.tsx
+++ b/packages/solid-router/src/ssr/renderRouterToString.tsx
@@ -1,4 +1,9 @@
import * as Solid from 'solid-js/web'
+import {
+ UnheadContext,
+ createHead,
+ transformHtmlTemplate,
+} from '@unhead/solid-js/server'
import { makeSsrSerovalPlugin } from '@tanstack/router-core'
import type { AnyRouter } from '@tanstack/router-core'
import type { JSXElement } from 'solid-js'
@@ -21,10 +26,19 @@ export const renderRouterToString = async ({
return plugin
})
- let html = Solid.renderToString(children, {
- nonce: router.options.ssr?.nonce,
- plugins: serovalPlugins,
- } as any)
+ const head = createHead()
+ let html = Solid.renderToString(
+ () => (
+
+ {children()}
+
+ ),
+ {
+ nonce: router.options.ssr?.nonce,
+ plugins: serovalPlugins,
+ } as any,
+ )
+ html = await transformHtmlTemplate(head, html)
router.serverSsr!.setRenderFinished()
const injectedHtml = router.serverSsr!.takeBufferedHtml()
diff --git a/packages/solid-router/src/ssr/server.ts b/packages/solid-router/src/ssr/server.ts
index b4e84970c47..8f36c849b0f 100644
--- a/packages/solid-router/src/ssr/server.ts
+++ b/packages/solid-router/src/ssr/server.ts
@@ -1,4 +1,4 @@
-export { RouterServer } from './RouterServer'
+export { RouterServer, RouterServerBody } from './RouterServer'
export { defaultRenderHandler } from './defaultRenderHandler'
export { defaultStreamHandler } from './defaultStreamHandler'
export { renderRouterToStream } from './renderRouterToStream'
diff --git a/packages/solid-router/tests/Scripts.test.tsx b/packages/solid-router/tests/Scripts.test.tsx
index 6ffa0bd645d..0284e554cc9 100644
--- a/packages/solid-router/tests/Scripts.test.tsx
+++ b/packages/solid-router/tests/Scripts.test.tsx
@@ -97,11 +97,15 @@ describe('ssr scripts', () => {
{ src: 'script3.js' },
])
- const { container } = render(() => )
+ render(() => )
- expect(container.innerHTML).toEqual(
- '',
- )
+ await new Promise((resolve) => setTimeout(resolve, 0))
+
+ const scripts = Array.from(
+ document.body.querySelectorAll('script[src]'),
+ ).map((el) => (el as HTMLScriptElement).getAttribute('src'))
+
+ expect(scripts).toEqual(['script.js', 'script3.js'])
})
})
diff --git a/packages/solid-start-server/package.json b/packages/solid-start-server/package.json
index 12fcfe0bf94..db33b221392 100644
--- a/packages/solid-start-server/package.json
+++ b/packages/solid-start-server/package.json
@@ -58,12 +58,12 @@
"node": ">=22.12.0"
},
"dependencies": {
- "@solidjs/meta": "^0.29.4",
"@tanstack/history": "workspace:*",
"@tanstack/router-core": "workspace:*",
"@tanstack/solid-router": "workspace:*",
"@tanstack/start-client-core": "workspace:*",
- "@tanstack/start-server-core": "workspace:*"
+ "@tanstack/start-server-core": "workspace:*",
+ "@unhead/solid-js": "3.0.0-beta.5"
},
"devDependencies": {
"solid-js": "^1.9.10",
diff --git a/packages/solid-start-server/src/defaultStreamHandler.tsx b/packages/solid-start-server/src/defaultStreamHandler.tsx
index 4ee257a1863..858208fadca 100644
--- a/packages/solid-start-server/src/defaultStreamHandler.tsx
+++ b/packages/solid-start-server/src/defaultStreamHandler.tsx
@@ -9,5 +9,6 @@ export const defaultStreamHandler = defineHandlerCallback(
router,
responseHeaders,
children: () => ,
+ document: true,
}),
)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3ab9c9061b6..86fa7b39a30 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -11703,9 +11703,6 @@ importers:
'@solid-primitives/refs':
specifier: ^1.0.8
version: 1.1.0(solid-js@1.9.10)
- '@solidjs/meta':
- specifier: ^0.29.4
- version: 0.29.4(solid-js@1.9.10)
'@tanstack/history':
specifier: workspace:*
version: link:../history
@@ -11715,6 +11712,9 @@ importers:
'@tanstack/solid-store':
specifier: ^0.8.0
version: 0.8.0(solid-js@1.9.10)
+ '@unhead/solid-js':
+ specifier: 3.0.0-beta.5
+ version: 3.0.0-beta.5(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(solid-js@1.9.10)(terser@5.37.0)(tsx@4.20.3)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))(yaml@2.8.1)
isbot:
specifier: ^5.1.22
version: 5.1.28
@@ -11724,6 +11724,9 @@ importers:
tiny-warning:
specifier: ^1.0.3
version: 1.0.3
+ unhead:
+ specifier: 3.0.0-beta.5
+ version: 3.0.0-beta.5(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)
devDependencies:
'@solidjs/testing-library':
specifier: ^0.8.10
@@ -11861,9 +11864,6 @@ importers:
packages/solid-start-server:
dependencies:
- '@solidjs/meta':
- specifier: ^0.29.4
- version: 0.29.4(solid-js@1.9.10)
'@tanstack/history':
specifier: workspace:*
version: link:../history
@@ -11879,6 +11879,9 @@ importers:
'@tanstack/start-server-core':
specifier: workspace:*
version: link:../start-server-core
+ '@unhead/solid-js':
+ specifier: 3.0.0-beta.5
+ version: 3.0.0-beta.5(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(solid-js@1.9.10)(terser@5.37.0)(tsx@4.20.3)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))(yaml@2.8.1)
devDependencies:
solid-js:
specifier: 1.9.10
@@ -14832,6 +14835,128 @@ packages:
cpu: [x64]
os: [win32]
+ '@oxc-parser/binding-android-arm-eabi@0.106.0':
+ resolution: {integrity: sha512-uoo8Bbc0/UrsQHlpdelqz8+jQ5hQqJs6MKjeiGqSU0E5Dkben2PuxXjg2jmabT+TzclysNEyE7eKHGTA7uVVqQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [android]
+
+ '@oxc-parser/binding-android-arm64@0.106.0':
+ resolution: {integrity: sha512-7+hnrpce0uX96Hu8seWMJXqDnBTtSikibn1xa1yCa/musU1XZOLznhdWKA1usaPnwLBXP+7+h6nrdvKZ4HoT5Q==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [android]
+
+ '@oxc-parser/binding-darwin-arm64@0.106.0':
+ resolution: {integrity: sha512-J7d6j8PwicRXTL4I00eWhqupuq0Pei9EafTzoB7ccluNo5fXNspkIH1NtGpgxPsLyUkZy5Nb5J3Y80TpdX6yQA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@oxc-parser/binding-darwin-x64@0.106.0':
+ resolution: {integrity: sha512-5LhQlSACZPeyxbcE8WNMW1s88ExWGRnk0LQbQ3Co3gYkmgw12x2q6RnPT0N9BC6490VnWsynFafwCMPSrMnjfg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@oxc-parser/binding-freebsd-x64@0.106.0':
+ resolution: {integrity: sha512-IInBOOMzB54rV/s8K5Feu6krWNHMR/V52prXy+9B0GhjOSQ2Q7EAd8y1gXWgjKB0NMDychCLgdaInanUn45eyQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@oxc-parser/binding-linux-arm-gnueabihf@0.106.0':
+ resolution: {integrity: sha512-p0IQvugmAsA2288b30FP5ncbcp6juBQrsZNZD6SDiWRY3X3g5OH5puVtihE5KMNkeHmmd3S8MEHFCv0G1tYGPA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-arm-musleabihf@0.106.0':
+ resolution: {integrity: sha512-VgJPJVygSyFEfFtv6hscx9AbnewsxDUCxWmgrB/GHktoMlDQSDBh9aG1lENiiJnB2FLR8WG15446X3Mw2I4Zog==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-arm64-gnu@0.106.0':
+ resolution: {integrity: sha512-Gqs6q/pwlpgzx5qE2RtlTnY7hJuS1a5PYBT3unpSAMUE0LrbV7kQ8thmQo1ngI1tnCImWpuuXjZ2YbI0iKquXw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-arm64-musl@0.106.0':
+ resolution: {integrity: sha512-Bvtp8SK4MyahReapEPodracfBV9ed7+5WCHyjhSWoljrapJIU4OOLSsRyZ9zV2KhkjuD66DZq/qQv6pC73zzWQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-ppc64-gnu@0.106.0':
+ resolution: {integrity: sha512-DIXyavnpbBo+F/4G04LZ4xuuGXDY4m9qHB/HWtVj9z+Frb/r+SPAuptqAZFtJ9avcwbAOe3LO+K8BWHmK6+lnw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-riscv64-gnu@0.106.0':
+ resolution: {integrity: sha512-VdqTcLTET72nPcJkSz3xrpcxab7q2/z04d6y+Th1mUTyXs2b/9VC3BcDmaFAfmhz8GX/5FVuzUTQzda1mTsh/g==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-riscv64-musl@0.106.0':
+ resolution: {integrity: sha512-FgHBGg9DHQ0dePOWQ9rNN+DHueJa1XWHc9u0VJCVY+XXAx3iT2ASj21xZ1wA+Rh92CyuuZ7RpQ6Y+O57fieNlg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-s390x-gnu@0.106.0':
+ resolution: {integrity: sha512-fEIx2bUggt+s1eTaRVzhy5VgdrO1B8tUKxOPpGwwdF9VSP0KnLPaAv/gA4trJPxuIjjJRRVoK42v9R4O1jkbLg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [s390x]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-x64-gnu@0.106.0':
+ resolution: {integrity: sha512-DbDQkdK8ZuS/jnRx8UbESQ5ypCJpD7VpERB/RWZfSdA2+B4TbonDwNWbTU+q2VJTbh5Xq1X65eQyz4/MIfiFSQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-x64-musl@0.106.0':
+ resolution: {integrity: sha512-D0PbaLv1MyNFDmjY4UqLQFlC+0GPCvrzI/8VlAvG7ztAZx0KdFYT3pPGsHjKshUJW9+e42JK29abLd0bZ4I95w==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@oxc-parser/binding-openharmony-arm64@0.106.0':
+ resolution: {integrity: sha512-uXSzts/ghlqmWm1cQTctyxdAnvha5dzVW5JkEB30J4M47yj2FcCtzUGdZO/sgXxggD/QM7EANlB66cOyk/NsoA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@oxc-parser/binding-wasm32-wasi@0.106.0':
+ resolution: {integrity: sha512-oU8wkw9U1vhkICQIJLX8uy1lCPJqXf7aAidaqT2wJOce4a9XmGr2YNseEKbmVV/1TQaSHpHZNsDXglYicb4qKQ==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+
+ '@oxc-parser/binding-win32-arm64-msvc@0.106.0':
+ resolution: {integrity: sha512-zYRSn6MNlL8qcUIPRQWDu1JdgVqZa5iR4Drld8FBue3fHQGL0XrNQEd8qoWmuNo7FI0WiBRRuVgtkPaNoSsYmg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@oxc-parser/binding-win32-ia32-msvc@0.106.0':
+ resolution: {integrity: sha512-FRHVO84i5WgQDk0XI4oRt2qDhRUXyot2EGBSogp34LoE5hsondyuZ244+Fod9czgscmgSb6Aon8PaEhHQ0lJYg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@oxc-parser/binding-win32-x64-msvc@0.106.0':
+ resolution: {integrity: sha512-ydMjY15RdfRZZa7RrP+jjeudbDFDqKo5CGDTxvYBJ4jpROvVo0ThqN85vvNfVJ55gEUSjodCqvmA30qNTBZd/A==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@oxc-project/types@0.106.0':
+ resolution: {integrity: sha512-QdsH3rZq480VnOHSHgPYOhjL8O8LBdcnSjM408BpPCCUc0JYYZPG9Gafl9i3OcGk/7137o+gweb4cCv3WAUykg==}
+
'@oxc-transform/binding-android-arm-eabi@0.106.0':
resolution: {integrity: sha512-3MdeadurvkOHsDDheqIawCIxj40DYUnPRf0BatrB/ppbRPCpkgzCXTdchpAJjaAEsa3MavHMNmtqlrYg9yjYQg==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -16625,11 +16750,6 @@ packages:
peerDependencies:
solid-js: 1.9.10
- '@solidjs/meta@0.29.4':
- resolution: {integrity: sha512-zdIWBGpR9zGx1p1bzIPqF5Gs+Ks/BH8R6fWhmUa/dcK1L2rUC8BAcZJzNRYBQv74kScf1TSOs0EY//Vd/I0V8g==}
- peerDependencies:
- solid-js: 1.9.10
-
'@solidjs/testing-library@0.8.10':
resolution: {integrity: sha512-qdeuIerwyq7oQTIrrKvV0aL9aFeuwTd86VYD3afdq5HYEwoox1OBTJy4y8A3TFZr8oAR0nujYgCzY/8wgHGfeQ==}
engines: {node: '>= 14'}
@@ -17438,6 +17558,15 @@ packages:
resolution: {integrity: sha512-576+u0QD+Jp3tZzvfRfxon0EA2lzcDt3lhUbsC6Lgzy9x2VR4E+JUiNyGHi5T8vk0TV+fpJ5GLG1JsJuWCaKhw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@unhead/solid-js@3.0.0-beta.5':
+ resolution: {integrity: sha512-dSXNDdf0Gz1xmpnUKMLQHq9oiXox8z+jdh8IivX9Op3JBoK8Z4NaOnLYP3rmCFWnudVN97u7GGRSep+KULIJSg==}
+ peerDependencies:
+ solid-js: 1.9.10
+ vite: ^7.1.7
+ peerDependenciesMeta:
+ vite:
+ optional: true
+
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==}
cpu: [arm]
@@ -20081,6 +20210,9 @@ packages:
hookable@5.5.3:
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
+ hookable@6.0.1:
+ resolution: {integrity: sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw==}
+
hosted-git-info@7.0.2:
resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==}
engines: {node: ^16.14.0 || >=18.0.0}
@@ -21528,6 +21660,10 @@ packages:
resolution: {integrity: sha512-WuhR/Vz0ISIU1W7YRZqRT1ILDfCJ6ik83ma90QnblSj/BBJhyC16YnC0BI/9sG71Fezcrl4NqN10oLNH2Z+Trw==}
engines: {node: ^20.19.0 || >=22.12.0}
+ oxc-parser@0.106.0:
+ resolution: {integrity: sha512-KSqA8PNgqi+wadUoGJXWyTr0mLuMzEABXQK5hKlj+cEWID+Rhw8xiqLappTDaCUpOqnKCpyO9N5RlzlFxR+TBw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+
oxc-transform@0.106.0:
resolution: {integrity: sha512-qaEQDTcyIMO9YtmrxK8EYIOtgUMx6CKDNguqHEbnKBHAYCwlrA8RawAh1Gvo8zNdDbclOtRwT3t5WqV3bB3GRA==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -23144,6 +23280,9 @@ packages:
unenv@2.0.0-rc.24:
resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==}
+ unhead@3.0.0-beta.5:
+ resolution: {integrity: sha512-mYnhRlK09bk0o3zNJUmXi0K6EuzHTPSzrGR7jUkG3VThJGgtBC/o85tkcUPYg4F3vtSIMn7Qe4DR2NPi2sPXIg==}
+
unicode-emoji-modifier-base@1.0.0:
resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==}
engines: {node: '>=4'}
@@ -26689,6 +26828,70 @@ snapshots:
'@oxc-minify/binding-win32-x64-msvc@0.106.0':
optional: true
+ '@oxc-parser/binding-android-arm-eabi@0.106.0':
+ optional: true
+
+ '@oxc-parser/binding-android-arm64@0.106.0':
+ optional: true
+
+ '@oxc-parser/binding-darwin-arm64@0.106.0':
+ optional: true
+
+ '@oxc-parser/binding-darwin-x64@0.106.0':
+ optional: true
+
+ '@oxc-parser/binding-freebsd-x64@0.106.0':
+ optional: true
+
+ '@oxc-parser/binding-linux-arm-gnueabihf@0.106.0':
+ optional: true
+
+ '@oxc-parser/binding-linux-arm-musleabihf@0.106.0':
+ optional: true
+
+ '@oxc-parser/binding-linux-arm64-gnu@0.106.0':
+ optional: true
+
+ '@oxc-parser/binding-linux-arm64-musl@0.106.0':
+ optional: true
+
+ '@oxc-parser/binding-linux-ppc64-gnu@0.106.0':
+ optional: true
+
+ '@oxc-parser/binding-linux-riscv64-gnu@0.106.0':
+ optional: true
+
+ '@oxc-parser/binding-linux-riscv64-musl@0.106.0':
+ optional: true
+
+ '@oxc-parser/binding-linux-s390x-gnu@0.106.0':
+ optional: true
+
+ '@oxc-parser/binding-linux-x64-gnu@0.106.0':
+ optional: true
+
+ '@oxc-parser/binding-linux-x64-musl@0.106.0':
+ optional: true
+
+ '@oxc-parser/binding-openharmony-arm64@0.106.0':
+ optional: true
+
+ '@oxc-parser/binding-wasm32-wasi@0.106.0':
+ dependencies:
+ '@napi-rs/wasm-runtime': 1.1.0
+ optional: true
+
+ '@oxc-parser/binding-win32-arm64-msvc@0.106.0':
+ optional: true
+
+ '@oxc-parser/binding-win32-ia32-msvc@0.106.0':
+ optional: true
+
+ '@oxc-parser/binding-win32-x64-msvc@0.106.0':
+ optional: true
+
+ '@oxc-project/types@0.106.0': {}
+
'@oxc-transform/binding-android-arm-eabi@0.106.0':
optional: true
@@ -28538,10 +28741,6 @@ snapshots:
dependencies:
solid-js: 1.9.10
- '@solidjs/meta@0.29.4(solid-js@1.9.10)':
- dependencies:
- solid-js: 1.9.10
-
'@solidjs/testing-library@0.8.10(solid-js@1.9.10)':
dependencies:
'@testing-library/dom': 10.4.1
@@ -29590,6 +29789,28 @@ snapshots:
'@typescript-eslint/types': 8.44.1
eslint-visitor-keys: 4.2.1
+ '@unhead/solid-js@3.0.0-beta.5(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(solid-js@1.9.10)(terser@5.37.0)(tsx@4.20.3)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))(yaml@2.8.1)':
+ dependencies:
+ magic-string: 0.30.21
+ mlly: 1.8.0
+ oxc-parser: 0.106.0
+ solid-js: 1.9.10
+ unhead: 3.0.0-beta.5(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)
+ optionalDependencies:
+ vite: 7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)
+ transitivePeerDependencies:
+ - '@types/node'
+ - jiti
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - terser
+ - tsx
+ - yaml
+
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
optional: true
@@ -32724,6 +32945,8 @@ snapshots:
hookable@5.5.3: {}
+ hookable@6.0.1: {}
+
hosted-git-info@7.0.2:
dependencies:
lru-cache: 10.4.3
@@ -34389,6 +34612,31 @@ snapshots:
'@oxc-minify/binding-win32-ia32-msvc': 0.106.0
'@oxc-minify/binding-win32-x64-msvc': 0.106.0
+ oxc-parser@0.106.0:
+ dependencies:
+ '@oxc-project/types': 0.106.0
+ optionalDependencies:
+ '@oxc-parser/binding-android-arm-eabi': 0.106.0
+ '@oxc-parser/binding-android-arm64': 0.106.0
+ '@oxc-parser/binding-darwin-arm64': 0.106.0
+ '@oxc-parser/binding-darwin-x64': 0.106.0
+ '@oxc-parser/binding-freebsd-x64': 0.106.0
+ '@oxc-parser/binding-linux-arm-gnueabihf': 0.106.0
+ '@oxc-parser/binding-linux-arm-musleabihf': 0.106.0
+ '@oxc-parser/binding-linux-arm64-gnu': 0.106.0
+ '@oxc-parser/binding-linux-arm64-musl': 0.106.0
+ '@oxc-parser/binding-linux-ppc64-gnu': 0.106.0
+ '@oxc-parser/binding-linux-riscv64-gnu': 0.106.0
+ '@oxc-parser/binding-linux-riscv64-musl': 0.106.0
+ '@oxc-parser/binding-linux-s390x-gnu': 0.106.0
+ '@oxc-parser/binding-linux-x64-gnu': 0.106.0
+ '@oxc-parser/binding-linux-x64-musl': 0.106.0
+ '@oxc-parser/binding-openharmony-arm64': 0.106.0
+ '@oxc-parser/binding-wasm32-wasi': 0.106.0
+ '@oxc-parser/binding-win32-arm64-msvc': 0.106.0
+ '@oxc-parser/binding-win32-ia32-msvc': 0.106.0
+ '@oxc-parser/binding-win32-x64-msvc': 0.106.0
+
oxc-transform@0.106.0:
optionalDependencies:
'@oxc-transform/binding-android-arm-eabi': 0.106.0
@@ -36158,6 +36406,24 @@ snapshots:
dependencies:
pathe: 2.0.3
+ unhead@3.0.0-beta.5(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1):
+ dependencies:
+ hookable: 6.0.1
+ magic-string: 0.30.21
+ vite: 7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)
+ transitivePeerDependencies:
+ - '@types/node'
+ - jiti
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - terser
+ - tsx
+ - yaml
+
unicode-emoji-modifier-base@1.0.0: {}
unicorn-magic@0.1.0: {}