Skip to content

fix: keep CSS when referenced both statically and dynamically#6891

Merged
schiller-manuel merged 4 commits intomainfrom
fix-css
Mar 11, 2026
Merged

fix: keep CSS when referenced both statically and dynamically#6891
schiller-manuel merged 4 commits intomainfrom
fix-css

Conversation

@schiller-manuel
Copy link
Contributor

@schiller-manuel schiller-manuel commented Mar 11, 2026

Summary by CodeRabbit

  • New Features

    • Two new pages demonstrating CSS module behavior (static and lazy) plus a shared styled widget and navigation links.
  • Tests

    • Added production E2E tests validating CSS across routes; dev-only guard for dev runs.
    • Test scripts updated to run both dev and prod E2E suites.
  • Chores

    • Centralized manifest construction into a dedicated builder and added unit tests for it.

@changeset-bot
Copy link

changeset-bot bot commented Mar 11, 2026

🦋 Changeset detected

Latest commit: 19e3ab9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
@tanstack/start-plugin-core Patch
@tanstack/react-start Patch
@tanstack/solid-start Patch
@tanstack/vue-start Patch
@tanstack/start-static-server-functions Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 11, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e451e62f-beab-4404-8d2a-c84dafbfde47

📥 Commits

Reviewing files that changed from the base of the PR and between 59aa0a6 and 19e3ab9.

📒 Files selected for processing (1)
  • e2e/react-start/css-modules/tests/css.dev.spec.ts

📝 Walkthrough

Walkthrough

Adds CSS-modules SSR reproduction pages and tests (static and lazy widget scenarios), new UI components and CSS modules, updates E2E scripts, and refactors the start-manifest-plugin by extracting manifest construction into a new manifestBuilder that scans client chunks, resolves assets (including dynamic CSS), and deduplicates route preloads.

Changes

Cohort / File(s) Summary
E2E Test Scripts
e2e/react-start/css-modules/package.json
Updated scripts: test:e2e:prod now runs MODE=prod playwright test --project=chromium; test:e2e now runs dev then prod.
CSS Module Components
e2e/react-start/css-modules/src/components/shared-widget.tsx, e2e/react-start/css-modules/src/components/shared-widget-lazy.tsx
Added presentational SharedWidget and a lazy wrapper SharedWidgetLazy.
CSS Module Styling
e2e/react-start/css-modules/src/styles/shared-widget.module.css
New CSS module defining .widget, .title, and .content.
Routes & Nav Links
e2e/react-start/css-modules/src/routes/lazy-css-static.tsx, e2e/react-start/css-modules/src/routes/lazy-css-lazy.tsx, e2e/react-start/css-modules/src/routes/__root.tsx
Added /lazy-css-static and /lazy-css-lazy routes and corresponding nav links with data-testids for E2E flows.
Route Tree
e2e/react-start/css-modules/src/routeTree.gen.ts
Generated route tree updated to include new lazy/static CSS routes with types, exports, and root children.
E2E Tests
e2e/react-start/css-modules/tests/css.dev.spec.ts, e2e/react-start/css-modules/tests/css.prod.spec.ts
Dev test now skips when MODE=prod; added prod-only Playwright tests verifying CSS-module styling persistence across navigation and lazy-load scenarios.
Manifest Builder
packages/start-plugin-core/src/start-manifest-plugin/manifestBuilder.ts
New module implementing manifest construction: scanning client chunks, mapping route chunks, collecting dynamic CSS (with hashed assets), building asset resolvers, merging per-route assets/preloads, and pruning/deduplicating preloads.
Plugin Refactor
packages/start-plugin-core/src/start-manifest-plugin/plugin.ts
Replaced inlined manifest assembly with buildStartManifest call; removed prior CSS-collection helpers and inlined logic.
Builder Tests
packages/start-plugin-core/tests/start-manifest-plugin/manifestBuilder.test.ts
Extensive unit tests for manifestBuilder covering chunk scanning, dynamic CSS collection, asset dedupe, manifest assembly, and nested preload deduplication.
Changeset
.changeset/open-pants-vanish.md
Added changeset noting fix to preserve CSS referenced both statically and dynamically.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  rect rgba(200,200,255,0.5)
    participant Dev as Developer/Build
  end
  rect rgba(200,255,200,0.5)
    participant Plugin as Start Plugin
  end
  rect rgba(255,200,200,0.5)
    participant Builder as manifestBuilder
    participant Rollup as Rollup Bundle
    participant RouteTree as RouteTreeRoutes
  end

  Dev->>Plugin: invoke plugin.load (clientBundle, routeTreeRoutes)
  Plugin->>Builder: buildStartManifest({ clientBundle, routeTreeRoutes, basePath })
  Builder->>Rollup: scanClientChunks(clientBundle)
  Builder->>RouteTree: read routeTreeRoutes (filePath mappings)
  Builder->>Rollup: collectDynamicImportCss(route chunks)
  Builder->>Builder: createManifestAssetResolvers(dynamicCss)
  Builder->>Builder: buildRouteManifestRoutes(...merged assets/preloads...)
  Builder->>Plugin: return startManifest (routes + clientEntry)
  Plugin->>Dev: return manifest for serve/build
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 I hopped through routes both lazy and clear,
I gathered CSS bits both far and near,
Chunks sorted, preloads pruned with delight,
Tests run in prod to prove styles hold tight,
A tiny rabbit cheers—styles stay bright!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: fixing CSS preservation in resources referenced both statically and dynamically, which is the core intent of the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-css

Comment @coderabbitai help to get the list of available commands and usage tips.

@nx-cloud
Copy link

nx-cloud bot commented Mar 11, 2026

View your CI Pipeline Execution ↗ for commit 19e3ab9

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 9m 13s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 2s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-11 19:28:33 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 11, 2026

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@6891

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/@tanstack/eslint-plugin-router@6891

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@6891

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/@tanstack/nitro-v2-vite-plugin@6891

@tanstack/react-router

npm i https://pkg.pr.new/@tanstack/react-router@6891

@tanstack/react-router-devtools

npm i https://pkg.pr.new/@tanstack/react-router-devtools@6891

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@6891

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@6891

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@6891

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@6891

@tanstack/router-cli

npm i https://pkg.pr.new/@tanstack/router-cli@6891

@tanstack/router-core

npm i https://pkg.pr.new/@tanstack/router-core@6891

@tanstack/router-devtools

npm i https://pkg.pr.new/@tanstack/router-devtools@6891

@tanstack/router-devtools-core

npm i https://pkg.pr.new/@tanstack/router-devtools-core@6891

@tanstack/router-generator

npm i https://pkg.pr.new/@tanstack/router-generator@6891

@tanstack/router-plugin

npm i https://pkg.pr.new/@tanstack/router-plugin@6891

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@6891

@tanstack/router-utils

npm i https://pkg.pr.new/@tanstack/router-utils@6891

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/@tanstack/router-vite-plugin@6891

@tanstack/solid-router

npm i https://pkg.pr.new/@tanstack/solid-router@6891

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/@tanstack/solid-router-devtools@6891

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@6891

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@6891

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@6891

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@6891

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@6891

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@6891

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@6891

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@6891

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@6891

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@6891

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@6891

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@6891

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@6891

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@6891

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@6891

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@6891

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@6891

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@6891

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@6891

commit: 9c8ac7b

@github-actions
Copy link
Contributor

github-actions bot commented Mar 11, 2026

Bundle Size Benchmarks

  • Commit: ab46b5ec4229
  • Measured at: 2026-03-11T19:20:23.897Z
  • Baseline source: history:044fb24d2656
  • Dashboard: bundle-size history
Scenario Current (gzip) Delta vs baseline Raw Brotli Trend
react-router.minimal 87.09 KiB 0 B (0.00%) 274.11 KiB 75.68 KiB ▁▁▆▆▇▇█████
react-router.full 90.08 KiB 0 B (0.00%) 284.36 KiB 78.32 KiB ▁▁▅▅▆▆█████
solid-router.minimal 36.42 KiB 0 B (0.00%) 109.29 KiB 32.74 KiB ▁▁▅▆▇▇█████
solid-router.full 40.75 KiB 0 B (0.00%) 122.27 KiB 36.58 KiB ▁▁▄▅▇▇█████
vue-router.minimal 52.29 KiB 0 B (0.00%) 149.34 KiB 47.01 KiB ▁▁▅▅▇▇█████
vue-router.full 57.08 KiB 0 B (0.00%) 164.85 KiB 51.27 KiB ▁▁▅▅▇▇█████
react-start.minimal 99.66 KiB 0 B (0.00%) 313.26 KiB 86.22 KiB ▁▁▅▅▇▇█████
react-start.full 102.97 KiB 0 B (0.00%) 322.99 KiB 89.07 KiB ▁▁▅▅▆▆█████
solid-start.minimal 48.75 KiB 0 B (0.00%) 146.89 KiB 43.14 KiB ▁▁▅▅▇▇█████
solid-start.full 54.21 KiB 0 B (0.00%) 162.75 KiB 47.88 KiB ▁▁▅▅▇▇█████

Trend sparkline is historical gzip bytes ending with this PR measurement; lower is better.

@codspeed-hq
Copy link

codspeed-hq bot commented Mar 11, 2026

Merging this PR will not alter performance

✅ 6 untouched benchmarks


Comparing fix-css (19e3ab9) with main (ab46b5e)

Open in CodSpeed

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/start-plugin-core/src/start-manifest-plugin/manifestBuilder.ts (1)

540-548: Consider adding a guard for missing child routes.

The non-null assertion on routesById[childRouteId] could cause a confusing runtime error if the route tree has children referencing non-existent route IDs. A descriptive error would help debugging misconfigured route trees.

🛡️ Optional: Add defensive check with clear error message
   if (route.children) {
     for (const childRouteId of route.children) {
+      const childRoute = routesById[childRouteId]
+      if (!childRoute) {
+        throw new Error(`Child route "${childRouteId}" not found in routes`)
+      }
       dedupeNestedRoutePreloads(
-        routesById[childRouteId]!,
+        childRoute,
         routesById,
         seenPreloads,
       )
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/start-manifest-plugin/manifestBuilder.ts`
around lines 540 - 548, The loop in dedupeNestedRoutePreloads uses a non-null
assertion on routesById[childRouteId] which can throw an unclear runtime error
if a child ID is missing; add a defensive guard inside the for loop that checks
whether routesById[childRouteId] exists and, if not, throws or logs a
descriptive error (e.g. include the parent route id and missing childRouteId)
before calling dedupeNestedRoutePreloads, referencing the variables
route.children, childRouteId, routesById, and seenPreloads so the missing-route
condition is detected and reported clearly.
packages/start-plugin-core/tests/start-manifest-plugin/manifestBuilder.test.ts (1)

627-643: Test is misplaced in the wrong describe block.

This test verifies buildStartManifest throws when a non-root route lacks filePath, but it's nested under describe('dedupeNestedRoutePreloads', ...). Consider moving it to the describe('buildStartManifest', ...) block for better organization.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/start-plugin-core/tests/start-manifest-plugin/manifestBuilder.test.ts`
around lines 627 - 643, The test asserting buildStartManifest throws when a
non-root route lacks filePath is in the wrong describe block; move the test (the
case using buildStartManifest with routeTreeRoutes containing __root__ and
'/about' and expecting "expected filePath to be set for /about") out of the
describe('dedupeNestedRoutePreloads', ...) group and into the
describe('buildStartManifest', ...) block so it lives with related
buildStartManifest tests and clearly references the buildStartManifest,
routeTreeRoutes, __root__, and '/about' symbols.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/start-plugin-core/src/start-manifest-plugin/manifestBuilder.ts`:
- Around line 540-548: The loop in dedupeNestedRoutePreloads uses a non-null
assertion on routesById[childRouteId] which can throw an unclear runtime error
if a child ID is missing; add a defensive guard inside the for loop that checks
whether routesById[childRouteId] exists and, if not, throws or logs a
descriptive error (e.g. include the parent route id and missing childRouteId)
before calling dedupeNestedRoutePreloads, referencing the variables
route.children, childRouteId, routesById, and seenPreloads so the missing-route
condition is detected and reported clearly.

In
`@packages/start-plugin-core/tests/start-manifest-plugin/manifestBuilder.test.ts`:
- Around line 627-643: The test asserting buildStartManifest throws when a
non-root route lacks filePath is in the wrong describe block; move the test (the
case using buildStartManifest with routeTreeRoutes containing __root__ and
'/about' and expecting "expected filePath to be set for /about") out of the
describe('dedupeNestedRoutePreloads', ...) group and into the
describe('buildStartManifest', ...) block so it lives with related
buildStartManifest tests and clearly references the buildStartManifest,
routeTreeRoutes, __root__, and '/about' symbols.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 15ab33de-da8b-4aa4-8474-58248bdff412

📥 Commits

Reviewing files that changed from the base of the PR and between fed53b3 and 59aa0a6.

📒 Files selected for processing (3)
  • .changeset/open-pants-vanish.md
  • packages/start-plugin-core/src/start-manifest-plugin/manifestBuilder.ts
  • packages/start-plugin-core/tests/start-manifest-plugin/manifestBuilder.test.ts

@schiller-manuel schiller-manuel merged commit 9a4d924 into main Mar 11, 2026
14 checks passed
@schiller-manuel schiller-manuel deleted the fix-css branch March 11, 2026 19:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant