You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit c74bd3e
Browse filesBrowse the repository at this point in the historyBrowse files
fix(pages): link CSS that island and Markdown pages reach through components (#481)
Fixes#452
## Problem
```
pages/layout.tsx ──renders──► src/Shell.tsx ──imports──► src/global.css
```
`vp dev` applied `global.css`. A production build did not:
- The islands client entry was only built when a page or layout file had
a direct CSS import, `<style>` or island import (a text scan). With CSS
only reachable through a component, no client build ran, so pages got no
`<link rel="stylesheet">`.
- Even when the entry was built, it only imported the **direct** CSS
imports of pages and layouts.
## Fix
```
server build client build
──────────── ────────────
buildEnd: walk static importedIds islands client entry
layouts first, then pages ──recorded──► import "<css id>"; ...
(real resolution: aliases, ▼
packages, Vue/Svelte styles) islands-client-*.css → <link> on island/md pages
```
- **When the entry is built:** for every app with island or Markdown
pages (`hasIslandPages`). It replaces the `needsIslandClientBundle` text
scan, which only the adapters used.
- **What CSS goes in:**
- The server build has already resolved every import of the island pages
and layouts, so the server build's module graph lists the stylesheets
they reach.
- Static imports only, in import order, like the dev CSS collector.
`?inline` / `?url` / `?raw` are skipped.
- The walk also enters island components, as dev does, so a layout that
renders one without hydrating keeps its CSS.
- Without a server build (client-only builds), the old direct-import
scan is still used.
- **Framework details:**
- CSS modules (`*.module.css`, Vue `<style module>`) are kept. Vite
marks them side-effect free, so the bare import would drop them.
- Vue scoped styles: `cssScopeTo` is cleared for the recorded ids,
because the component itself is not in the client bundle.
- Svelte: the entry imports vite-plugin-svelte's own
`?svelte&type=style` id, compiled through the existing island-style
loader, so the same rule is not emitted twice in mixed apps.
- **No extra JS:**
- Pages that need no JS still get only the `<link>`, no `<script>`.
- With no islands and no page needing JS, the entry holds only the CSS
imports, so no hydration runtime is bundled.
- React and Solid now add the entry to the client build only, as Vue and
Svelte already did, so the worker no longer bundles it.
## Behaviour changes worth knowing
- Static-output island apps without any direct CSS import are now
prerendered. Before, the missing client build meant no HTML.
- A Vue island page or layout with `<style scoped>` used to fail the
build (`PARSE_ERROR` on `...&scoped=true`). It now builds and links the
CSS.
- `playground/markdown-blog`: same CSS rules, but `@void/md/theme.css`
(imported in the script) now comes before the layout's `<style>`, which
is Vue's normal order.
## Known limit (unchanged, not in this PR)
In apps with both island and regular pages, `collectRouteCss`
(`routing-assets.ts`) puts a chunk's own CSS before the CSS of chunks it
imports, so the islands stylesheet can come before shared layout CSS.
That order already applied to direct page CSS on `main`. Changing it
would change the link order on every page.
## Tests
New production-build tests for each adapter
(`packages/{react,vue,svelte,solid}/test/integration/island-css-build.test.ts`,
shared `test/island-css-build.ts`). Each one builds a static app where a
layout renders a component that imports CSS, with an island page and a
Markdown page, and checks:
- the page links a stylesheet, and each rule (plain CSS, CSS module,
Vue/Svelte `<style>`, Vue scoped with matching `data-v-*`, Svelte
matching `svelte-*` class) appears exactly once;
- pages that need no JS have no `<script>`;
- React: an app with no CSS has no link, script or `hydrateRoot`, and
`dist/ssr` has no islands client.
Plus unit tests for `collectIslandStyles` and `islandStyleRoots` (order,
no repeats, `\0` ids, `?inline`/`?url`).
| Check | Result |
|---|---|
| New build tests on `main` sources | 6 fail (no client build) |
| New build tests with the fix | pass |
| Undo each part (walk, entry decision, each adapter hook, CSS-module
keep, Vue scope, Svelte id) | a test fails for each |
| Adapter + md package tests, browser suites with local Chrome | pass |
| `vp fmt --check`, `vp lint`, typecheck void/react/vue/svelte/solid |
clean |
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
0 commit comments