Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
restructure manifest
  • Loading branch information
schiller-manuel committed May 23, 2026
commit 48628a7aa2035908bc8c733875a18ea92053cc30
1 change: 1 addition & 0 deletions e2e/react-start/dev-ssr-styles/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@tanstack/router-core": "workspace:*",
"@tanstack/router-e2e-utils": "workspace:*",
"@types/node": "^22.10.2",
"@types/react": "^19.0.8",
Expand Down
13 changes: 6 additions & 7 deletions e2e/react-start/dev-ssr-styles/tests/app.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { expect } from '@playwright/test'
import { DEV_STYLES_ATTR } from '@tanstack/router-core'
import { test } from '@tanstack/router-e2e-utils'
import { ssrStylesMode } from '../env'

Expand Down Expand Up @@ -29,9 +30,9 @@ test.describe(`dev.ssrStyles (mode=${ssrStylesMode})`, () => {
expect(response.ok()).toBeTruthy()
const html = await response.text()

// Should have a link tag with data-tanstack-router-dev-styles
expect(html).toContain('data-tanstack-router-dev-styles')
// Should have a dev styles link tag.
expect(html).toContain('/@tanstack-start/styles.css')
expect(html).toContain(DEV_STYLES_ATTR)
})

test('dev styles link uses vite base (/) as basepath prefix', async ({
Expand Down Expand Up @@ -75,9 +76,9 @@ test.describe(`dev.ssrStyles (mode=${ssrStylesMode})`, () => {
expect(response.ok()).toBeTruthy()
const html = await response.text()

// Should NOT have a link tag with data-tanstack-router-dev-styles
expect(html).not.toContain('data-tanstack-router-dev-styles')
// Should NOT have a dev styles link tag.
expect(html).not.toContain('/@tanstack-start/styles.css')
expect(html).not.toContain(DEV_STYLES_ATTR)
})

test('page still renders without dev styles', async ({ page }) => {
Expand All @@ -100,16 +101,14 @@ test.describe(`dev.ssrStyles (mode=${ssrStylesMode})`, () => {
expect(response.ok()).toBeTruthy()
const html = await response.text()

// Should have a link tag with data-tanstack-router-dev-styles
expect(html).toContain('data-tanstack-router-dev-styles')

// The dev styles URL should use /custom-styles/ as the basepath prefix
const match = html.match(
/href="([^"]*@tanstack-start\/styles\.css[^"]*)"/,
)
expect(match).toBeTruthy()
const href = match![1]
expect(href).toMatch(/^\/custom-styles\/@tanstack-start\/styles\.css/)
expect(html).toContain(DEV_STYLES_ATTR)
})

test.describe('with JavaScript disabled', () => {
Expand Down
8 changes: 4 additions & 4 deletions e2e/react-start/start-manifest/tests/start-manifest.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ async function loadBuiltStartManifest() {
const manifestModule = await import(moduleUrl)

return manifestModule.tsrStartManifest() as {
routes: Record<string, { assets?: Array<unknown> }>
routes: Record<string, { css?: Array<string | { href: string }> }>
}
}

Expand Down Expand Up @@ -318,9 +318,9 @@ test('built start manifest preserves shared layout asset identity across sibling

const manifest = await loadBuiltStartManifest()

const sharedAAsset = manifest.routes['/shared-a']?.assets?.[0]
const sharedBAsset = manifest.routes['/shared-b']?.assets?.[0]
const sharedCAsset = manifest.routes['/shared-c']?.assets?.[0]
const sharedAAsset = manifest.routes['/shared-a']?.css?.[0]
const sharedBAsset = manifest.routes['/shared-b']?.css?.[0]
const sharedCAsset = manifest.routes['/shared-c']?.css?.[0]

expect(sharedAAsset).toBeTruthy()
expect(sharedAAsset).toBe(sharedBAsset)
Expand Down
8 changes: 4 additions & 4 deletions e2e/solid-start/start-manifest/tests/start-manifest.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ async function loadBuiltStartManifest() {
const manifestModule = await import(moduleUrl)

return manifestModule.tsrStartManifest() as {
routes: Record<string, { assets?: Array<unknown> }>
routes: Record<string, { css?: Array<string | { href: string }> }>
}
}

Expand Down Expand Up @@ -312,9 +312,9 @@ test('built start manifest preserves shared layout asset identity across sibling

const manifest = await loadBuiltStartManifest()

const sharedAAsset = manifest.routes['/shared-a']?.assets?.[0]
const sharedBAsset = manifest.routes['/shared-b']?.assets?.[0]
const sharedCAsset = manifest.routes['/shared-c']?.assets?.[0]
const sharedAAsset = manifest.routes['/shared-a']?.css?.[0]
const sharedBAsset = manifest.routes['/shared-b']?.css?.[0]
const sharedCAsset = manifest.routes['/shared-c']?.css?.[0]

expect(sharedAAsset).toBeTruthy()
expect(sharedAAsset).toBe(sharedBAsset)
Expand Down
24 changes: 18 additions & 6 deletions e2e/vue-start/start-manifest/tests/start-manifest.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ async function loadBuiltStartManifest() {
const manifestModule = await import(moduleUrl)

return manifestModule.tsrStartManifest() as {
routes: Record<string, { assets?: Array<unknown> }>
routes: Record<string, { css?: Array<string | { href: string }> }>
}
}

Expand Down Expand Up @@ -119,15 +119,21 @@ async function expectDirectEntry({
await expect
.poll(async () => {
const hrefs = await getHeadStylesheetHrefs(page)
return hasMatchingStylesheetHref(hrefs, expectedStylesheetPattern)
return countMatchingStylesheetHrefs(hrefs, expectedStylesheetPattern)
})
.toBe(true)
.toBe(1)
await expect
.poll(async () => {
const hrefs = await getHeadStylesheetHrefs(page)
return countMatchingStylesheetHrefs(hrefs, unexpectedStylesheetPattern)
})
.toBe(0)
await expect
.poll(async () => {
const hrefs = await getHeadStylesheetHrefs(page)
return hrefs.length
})
.toBe(2)

await expect
.poll(() => getColor('root-shell-marker', page))
Expand Down Expand Up @@ -301,6 +307,12 @@ test('home route only renders the root stylesheet and no route-specific CSS', as
return countMatchingStylesheetHrefs(hrefs, 'r2-')
})
.toBe(0)
await expect
.poll(async () => {
const hrefs = await getHeadStylesheetHrefs(page)
return hrefs.length
})
.toBe(1)

await expect
.poll(() => getColor('root-shell-marker', page))
Expand All @@ -316,9 +328,9 @@ test('built start manifest preserves shared layout asset identity across sibling

const manifest = await loadBuiltStartManifest()

const sharedAAsset = manifest.routes['/shared-a']?.assets?.[0]
const sharedBAsset = manifest.routes['/shared-b']?.assets?.[0]
const sharedCAsset = manifest.routes['/shared-c']?.assets?.[0]
const sharedAAsset = manifest.routes['/shared-a']?.css?.[0]
const sharedBAsset = manifest.routes['/shared-b']?.css?.[0]
const sharedCAsset = manifest.routes['/shared-c']?.css?.[0]

expect(sharedAAsset).toBeTruthy()
expect(sharedAAsset).toBe(sharedBAsset)
Expand Down
7 changes: 4 additions & 3 deletions packages/react-router/src/HeadContent.dev.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
'use client'

import * as React from 'react'
import { DEV_STYLES_ATTR } from '@tanstack/router-core'
import { Asset } from './Asset'
import { useRouter } from './useRouter'
import { useHydrated } from './ClientOnly'
import { useTags } from './headContentUtils'
import type { HeadContentProps } from './HeadContent'

const DEV_STYLES_ATTR = 'data-tanstack-router-dev-styles'

/**
* Render route-managed head tags (title, meta, links, styles, head scripts).
* Place inside the document head of your app shell.
Expand Down Expand Up @@ -36,7 +35,9 @@ export function HeadContent(props: HeadContentProps) {

// Filter out dev styles after hydration
const filteredTags = hydrated
? tags.filter((tag) => !tag.attrs?.[DEV_STYLES_ATTR])
? tags.filter(
(tag) => tag.tag !== 'link' || tag.attrs?.[DEV_STYLES_ATTR] !== true,
)
: tags

return (
Expand Down
38 changes: 16 additions & 22 deletions packages/react-router/src/Scripts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ type ScriptRenderAsset = RouterManagedTag & {
preventScriptHoist?: boolean
}

function isExternalManifestScript(asset: RouterManagedTag) {
return asset.tag === 'script' && typeof asset.attrs?.src === 'string'
}

/**
* Render body script tags collected from route matches and SSR manifests.
* Should be placed near the end of the document body.
Expand All @@ -29,26 +25,24 @@ export const Scripts = () => {
return []
}

matches
.map((match) => router.looseRoutesById[match.routeId]!)
.forEach((route) => {
const routeManifest = manifest.routes[route.id]
for (const match of matches) {
const scripts = manifest.routes[match.routeId]?.scripts

routeManifest?.assets
?.filter((d) => d.tag === 'script')
.forEach((asset) => {
const scriptAsset = {
tag: 'script',
attrs: { ...asset.attrs, nonce },
children: asset.children,
...(isExternalManifestScript(asset)
? { preventScriptHoist: true }
: {}),
} satisfies ScriptRenderAsset
if (!scripts) {
continue
}

assetScripts.push(scriptAsset)
})
})
for (const asset of scripts) {
assetScripts.push({
tag: 'script',
attrs: { ...asset.attrs, nonce },
children: asset.children,
...(typeof asset.attrs?.src === 'string'
? { preventScriptHoist: true }
: {}),
})
}
}

return assetScripts
}
Expand Down
Loading