Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions .changeset/rsbuild-windows-route-manifest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/start-plugin-core': patch
---

Fix the Rsbuild Start manifest dropping every route's stylesheets and preloads on Windows by normalizing rspack module paths to the POSIX form the generated route tree uses.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ type RspackCompilation = Rspack.Compilation
type RspackCompilationChunk = Rspack.Chunk
type RspackModule = Rspack.Module

const backslashRegex = /\\/g

/**
* Convert an OS native path to the POSIX form used by the generated route tree.
*/
function toPosixPath(filePath: string): string {
return filePath.replace(backslashRegex, '/')
}

/**
* Extract route file paths from rspack module identifiers.
*
Expand Down Expand Up @@ -42,7 +51,14 @@ function getRouteFilePathsFromModules(
if (!new URLSearchParams(query).has(tsrSplit)) continue

const nameForCondition = mod.nameForCondition()
const routeFilePath = nameForCondition ?? resourcePart.slice(0, queryIndex)

// rspack reports module paths using the OS separator, while the generated route
// tree records every route `filePath` with POSIX separators. Normalize before the
// path becomes a manifest key, otherwise no route matches its chunk on Windows and
// every route loses its stylesheets and preloads.
const routeFilePath = toPosixPath(
nameForCondition ?? resourcePart.slice(0, queryIndex),
)

if (seen?.has(routeFilePath)) continue

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { describe, expect, test } from 'vitest'
import { normalizeRspackClientBuild } from '../../src/rsbuild/normalized-client-build'
import { buildStartManifest } from '../../src/start-manifest-plugin/manifestBuilder'
import type { Rspack } from '@rsbuild/core'

function makeModule(options: {
identifier: string
nameForCondition?: string | null
}): Rspack.Module {
return {
identifier: () => options.identifier,
nameForCondition: () => options.nameForCondition ?? null,
} as unknown as Rspack.Module
}

function makeChunk(options: {
name: string
files: Array<string>
auxiliaryFiles?: Array<string>
}): Rspack.Chunk {
const chunk = {
name: options.name,
files: new Set(options.files),
auxiliaryFiles: new Set(options.auxiliaryFiles ?? []),
} as unknown as Rspack.Chunk

const group = {
chunks: [chunk],
childrenIterable: new Set(),
}

;(chunk as any).groupsIterable = new Set([group])

return chunk
}

function makeCompilation(
entries: Array<{ chunk: Rspack.Chunk; modules: Array<Rspack.Module> }>,
): Rspack.Compilation {
const entryChunk = entries.find((entry) => entry.chunk.name === 'index')!
const modulesByChunk = new Map(
entries.map((entry) => [entry.chunk, entry.modules]),
)

return {
entrypoints: new Map([['index', { chunks: [entryChunk.chunk] }]]),
chunks: new Set(entries.map((entry) => entry.chunk)),
chunkGraph: {
getChunkModules: (chunk: Rspack.Chunk) => modulesByChunk.get(chunk) ?? [],
},
getAssets: () => [],
} as unknown as Rspack.Compilation
}

function makeWindowsCompilation() {
return makeCompilation([
{
chunk: makeChunk({ name: 'index', files: ['index.js'] }),
modules: [makeModule({ identifier: 'C:\\app\\src\\client.tsx' })],
},
{
chunk: makeChunk({
name: 'posts',
files: ['posts.js'],
auxiliaryFiles: ['posts.css'],
}),
modules: [
makeModule({
identifier:
'builtin:swc-loader??ruleSet[0]!C:\\app\\src\\routes\\posts.tsx?tsr-split=component',
nameForCondition: 'C:\\app\\src\\routes\\posts.tsx',
}),
],
},
])
}

describe('normalizeRspackClientBuild', () => {
test('keys route chunks by a POSIX path when rspack reports OS native paths', () => {
const build = normalizeRspackClientBuild(makeWindowsCompilation())

expect([...build.chunkFileNamesByRouteFilePath.keys()]).toEqual([
'C:/app/src/routes/posts.tsx',
])
expect(build.chunksByFileName.get('posts.js')?.routeFilePaths).toEqual([
'C:/app/src/routes/posts.tsx',
])
})

test('gives a route its stylesheet when rspack reports OS native paths', () => {
const manifest = buildStartManifest({
clientBuild: normalizeRspackClientBuild(makeWindowsCompilation()),
routeTreeRoutes: {
__root__: {
filePath: 'C:/app/src/routes/__root.tsx',
children: ['/posts'],
},
'/posts': { filePath: 'C:/app/src/routes/posts.tsx', children: [] },
},
basePath: '/',
})

expect(manifest.routes['/posts']?.css).toEqual(['/posts.css'])
expect(manifest.routes['/posts']?.preloads).toEqual(['/posts.js'])
})
})