diff --git a/packages/plugin-rsc/e2e/basic.test.ts b/packages/plugin-rsc/e2e/basic.test.ts index cc6cadd0..93979ded 100644 --- a/packages/plugin-rsc/e2e/basic.test.ts +++ b/packages/plugin-rsc/e2e/basic.test.ts @@ -1023,4 +1023,78 @@ function defineTest(f: Fixture) { 'test-browser-only: loading...', ) }) + + test('css queries @js', async ({ page }) => { + await page.goto(f.url()) + await waitForHydration(page) + await testCssQueries(page) + }) + + testNoJs('css queries @nojs', async ({ page }) => { + await page.goto(f.url()) + await testCssQueries(page) + }) + + async function testCssQueries(page: Page) { + // Test that CSS with special queries (?url, ?inline, ?raw) don't break the page + // and that normal CSS imports still work for both server and client components + + // Server component tests + await expect( + page.getByTestId('test-css-queries-server-normal'), + ).toBeVisible() + + // Client component tests + await expect( + page.getByTestId('test-css-queries-client-normal'), + ).toBeVisible() + + // Test that normal CSS imports are applied correctly to server component + await expect(page.locator('.test-css-query-server-normal')).toHaveCSS( + 'background-color', + 'rgb(255, 0, 0)', + ) + await expect(page.locator('.test-css-query-server-normal')).toHaveCSS( + 'color', + 'rgb(0, 255, 0)', + ) + + // Test that normal CSS imports are applied correctly to client component + await expect(page.locator('.test-css-query-client-normal')).toHaveCSS( + 'background-color', + 'rgb(0, 0, 255)', + ) + await expect(page.locator('.test-css-query-client-normal')).toHaveCSS( + 'color', + 'rgb(255, 255, 0)', + ) + + // Verify that URL query returns a string URL for server component (with filename pattern) + const serverUrlText = await page + .getByTestId('test-css-queries-server-url') + .textContent() + expect(serverUrlText).toMatch(/CSS URL \(server\): .*server-url.*\.css/) + + // Verify that URL query returns a string URL for client component (with filename pattern) + const clientUrlText = await page + .getByTestId('test-css-queries-client-url') + .textContent() + expect(clientUrlText).toMatch(/CSS URL \(client\): .*client-url.*\.css/) + + // Verify that inline and raw queries return strings for server component (should not be collected for SSR) + await expect( + page.getByTestId('test-css-queries-server-inline'), + ).toContainText('CSS Inline (server): string') + await expect(page.getByTestId('test-css-queries-server-raw')).toContainText( + 'CSS Raw (server): string', + ) + + // Verify that inline and raw queries return strings for client component (should not be collected for SSR) + await expect( + page.getByTestId('test-css-queries-client-inline'), + ).toContainText('CSS Inline (client): string') + await expect(page.getByTestId('test-css-queries-client-raw')).toContainText( + 'CSS Raw (client): string', + ) + } } diff --git a/packages/plugin-rsc/examples/basic/src/routes/css-queries/client-inline.css b/packages/plugin-rsc/examples/basic/src/routes/css-queries/client-inline.css new file mode 100644 index 00000000..d536fdcb --- /dev/null +++ b/packages/plugin-rsc/examples/basic/src/routes/css-queries/client-inline.css @@ -0,0 +1,4 @@ +.test-css-query-client-inline { + padding: 8px; + margin: 4px; +} diff --git a/packages/plugin-rsc/examples/basic/src/routes/css-queries/client-raw.css b/packages/plugin-rsc/examples/basic/src/routes/css-queries/client-raw.css new file mode 100644 index 00000000..e7081b56 --- /dev/null +++ b/packages/plugin-rsc/examples/basic/src/routes/css-queries/client-raw.css @@ -0,0 +1,3 @@ +.test-css-query-client-raw { + font-weight: bold; +} diff --git a/packages/plugin-rsc/examples/basic/src/routes/css-queries/client-url.css b/packages/plugin-rsc/examples/basic/src/routes/css-queries/client-url.css new file mode 100644 index 00000000..3c75763d --- /dev/null +++ b/packages/plugin-rsc/examples/basic/src/routes/css-queries/client-url.css @@ -0,0 +1,3 @@ +.test-css-query-client-url { + border: 2px solid rgb(0, 0, 255); +} diff --git a/packages/plugin-rsc/examples/basic/src/routes/css-queries/client.css b/packages/plugin-rsc/examples/basic/src/routes/css-queries/client.css new file mode 100644 index 00000000..ea2ffa8d --- /dev/null +++ b/packages/plugin-rsc/examples/basic/src/routes/css-queries/client.css @@ -0,0 +1,4 @@ +.test-css-query-client-normal { + background-color: rgb(0, 0, 255); + color: rgb(255, 255, 0); +} diff --git a/packages/plugin-rsc/examples/basic/src/routes/css-queries/client.tsx b/packages/plugin-rsc/examples/basic/src/routes/css-queries/client.tsx new file mode 100644 index 00000000..2a30fed3 --- /dev/null +++ b/packages/plugin-rsc/examples/basic/src/routes/css-queries/client.tsx @@ -0,0 +1,34 @@ +'use client' + +// Test CSS imports with special queries (?url, ?inline, ?raw) in client components +// These should not be collected for server-side rendering +import cssUrl from './client-url.css?url' +import cssInline from './client-inline.css?inline' +import cssRaw from './client-raw.css?raw' +import './client.css' // Normal import should still work + +export default function CssQueriesClientTest() { + return ( +
+
+ CSS URL (client): {cssUrl} +
+
+ CSS Inline (client):{' '} + {typeof cssInline === 'string' ? 'string' : 'other'} +
+
+ CSS Raw (client): {typeof cssRaw === 'string' ? 'string' : 'other'} +
+
+ Normal CSS import works (client) +
+
+ ) +} diff --git a/packages/plugin-rsc/examples/basic/src/routes/css-queries/server-inline.css b/packages/plugin-rsc/examples/basic/src/routes/css-queries/server-inline.css new file mode 100644 index 00000000..1148cefc --- /dev/null +++ b/packages/plugin-rsc/examples/basic/src/routes/css-queries/server-inline.css @@ -0,0 +1,4 @@ +.test-css-query-server-inline { + padding: 4px; + margin: 2px; +} diff --git a/packages/plugin-rsc/examples/basic/src/routes/css-queries/server-raw.css b/packages/plugin-rsc/examples/basic/src/routes/css-queries/server-raw.css new file mode 100644 index 00000000..8b7a3d38 --- /dev/null +++ b/packages/plugin-rsc/examples/basic/src/routes/css-queries/server-raw.css @@ -0,0 +1,3 @@ +.test-css-query-server-raw { + text-decoration: underline; +} diff --git a/packages/plugin-rsc/examples/basic/src/routes/css-queries/server-url.css b/packages/plugin-rsc/examples/basic/src/routes/css-queries/server-url.css new file mode 100644 index 00000000..d872b248 --- /dev/null +++ b/packages/plugin-rsc/examples/basic/src/routes/css-queries/server-url.css @@ -0,0 +1,3 @@ +.test-css-query-server-url { + border: 2px solid rgb(255, 0, 0); +} diff --git a/packages/plugin-rsc/examples/basic/src/routes/css-queries/server.css b/packages/plugin-rsc/examples/basic/src/routes/css-queries/server.css new file mode 100644 index 00000000..e73f20d5 --- /dev/null +++ b/packages/plugin-rsc/examples/basic/src/routes/css-queries/server.css @@ -0,0 +1,4 @@ +.test-css-query-server-normal { + background-color: rgb(255, 0, 0); + color: rgb(0, 255, 0); +} diff --git a/packages/plugin-rsc/examples/basic/src/routes/css-queries/server.tsx b/packages/plugin-rsc/examples/basic/src/routes/css-queries/server.tsx new file mode 100644 index 00000000..465fc6ed --- /dev/null +++ b/packages/plugin-rsc/examples/basic/src/routes/css-queries/server.tsx @@ -0,0 +1,39 @@ +// Test CSS imports with special queries (?url, ?inline, ?raw) in server component +// These should not be collected for server-side rendering +import cssUrl from './server-url.css?url' +import cssInline from './server-inline.css?inline' +import cssRaw from './server-raw.css?raw' +import './server.css' // Normal import should still work +import CssQueriesClientTest from './client' + +export default function CssQueriesTest() { + return ( +
+ {/* Server component test */} +
+
+ CSS URL (server): {cssUrl} +
+
+ CSS Inline (server):{' '} + {typeof cssInline === 'string' ? 'string' : 'other'} +
+
+ CSS Raw (server): {typeof cssRaw === 'string' ? 'string' : 'other'} +
+
+ Normal CSS import works (server) +
+
+ + {/* Client component test */} + +
+ ) +} diff --git a/packages/plugin-rsc/examples/basic/src/routes/root.tsx b/packages/plugin-rsc/examples/basic/src/routes/root.tsx index 5a3421ed..b3918838 100644 --- a/packages/plugin-rsc/examples/basic/src/routes/root.tsx +++ b/packages/plugin-rsc/examples/basic/src/routes/root.tsx @@ -29,6 +29,7 @@ import { TestTemporaryReference } from './temporary-reference/client' import { TestUseCache } from './use-cache/server' import { TestHydrationMismatch } from './hydration-mismatch/server' import { TestBrowserOnly } from './browser-only/client' +import CssQueriesTest from './css-queries/server' export function Root(props: { url: URL }) { return ( @@ -70,6 +71,7 @@ export function Root(props: { url: URL }) { + ) diff --git a/packages/plugin-rsc/src/plugin.ts b/packages/plugin-rsc/src/plugin.ts index 8f7f7f0f..9e85ddfb 100644 --- a/packages/plugin-rsc/src/plugin.ts +++ b/packages/plugin-rsc/src/plugin.ts @@ -1459,6 +1459,10 @@ export async function findSourceMapURL( export function vitePluginRscCss( rscCssOptions?: Pick, ): Plugin[] { + function hasSpecialCssQuery(id: string): boolean { + return /[?&](url|inline|raw)(\b|=|&|$)/.test(id) + } + function collectCss(environment: DevEnvironment, entryId: string) { const visited = new Set() const cssIds = new Set() @@ -1475,7 +1479,7 @@ export function vitePluginRscCss( } for (const next of mod?.importedModules ?? []) { if (next.id) { - if (isCSSRequest(next.id)) { + if (isCSSRequest(next.id) && !hasSpecialCssQuery(next.id)) { cssIds.add(next.id) } else { recurse(next.id)