diff --git a/packages/plugin-rsc/e2e/basic.test.ts b/packages/plugin-rsc/e2e/basic.test.ts index d3cdc551..1306ee31 100644 --- a/packages/plugin-rsc/e2e/basic.test.ts +++ b/packages/plugin-rsc/e2e/basic.test.ts @@ -855,31 +855,42 @@ function defineTest(f: Fixture) { 'rgb(255, 165, 0)', ) - // remove css import const editor = f.createEditor('src/routes/style-server/server.tsx') - editor.edit((s) => - s.replaceAll(`import './server.css'`, `/* import './server.css' */`), - ) + + // removing and adding new css works via hmr + { + await using _ = await expectNoReload(page) + + // remove css import + editor.edit((s) => + s.replaceAll(`import './server.css'`, `/* import './server.css' */`), + ) + await expect(page.locator('.test-style-server')).toHaveCSS( + 'color', + 'rgb(0, 0, 0)', + ) + + // add new css + editor.edit((s) => + s.replaceAll(`/* import './server.css' */`, `import './server2.css'`), + ) + await expect(page.locator('.test-style-server')).toHaveCSS( + 'color', + 'rgb(0, 255, 165)', + ) + } + + // TODO: React doesn't re-inert same css link. so manual reload is required. + editor.reset() await page.waitForTimeout(100) await expect(async () => { - // TODO: shouldn't require reload await page.reload() await expect(page.locator('.test-style-server')).toHaveCSS( 'color', - 'rgb(0, 0, 0)', + 'rgb(255, 165, 0)', { timeout: 10 }, ) }).toPass() - - // adding css works without reload - await waitForHydration(page) - await using _ = await expectNoReload(page) - - editor.reset() - await expect(page.locator('.test-style-server')).toHaveCSS( - 'color', - 'rgb(255, 165, 0)', - ) }) testNoJs('adding/removing css server @nojs', async ({ page }) => { diff --git a/packages/plugin-rsc/examples/basic/src/routes/style-server/server.tsx b/packages/plugin-rsc/examples/basic/src/routes/style-server/server.tsx index da4bafaf..a7132153 100644 --- a/packages/plugin-rsc/examples/basic/src/routes/style-server/server.tsx +++ b/packages/plugin-rsc/examples/basic/src/routes/style-server/server.tsx @@ -27,3 +27,7 @@ export function TestStyleServer() { ) } + +// add no-op `import.meta.hot` to trigger `prune` event. +// this is needed until we land https://github.com/vitejs/vite/pull/20768 +import.meta.hot diff --git a/packages/plugin-rsc/examples/basic/src/routes/style-server/server2.css b/packages/plugin-rsc/examples/basic/src/routes/style-server/server2.css new file mode 100644 index 00000000..46ded0c8 --- /dev/null +++ b/packages/plugin-rsc/examples/basic/src/routes/style-server/server2.css @@ -0,0 +1,3 @@ +.test-style-server { + color: rgb(0, 255, 165); +} diff --git a/packages/plugin-rsc/src/plugin.ts b/packages/plugin-rsc/src/plugin.ts index 1a3d75fa..08565c09 100644 --- a/packages/plugin-rsc/src/plugin.ts +++ b/packages/plugin-rsc/src/plugin.ts @@ -1026,6 +1026,17 @@ import.meta.hot.on("rsc:update", () => { document.querySelectorAll("vite-error-overlay").forEach((n) => n.close()) }); ` + // remove stylesheet links when css import is removed on rsc envrionment + code += `import.meta.hot.on("rsc:prune", ${(e: vite.PrunePayload) => { + const nodes = document.querySelectorAll( + "link[rel='stylesheet']", + ) + nodes.forEach((node) => { + if (e.paths.includes(node.dataset.rscCssHref!)) { + node.remove() + } + }) + }});` return code }, ), @@ -2031,6 +2042,22 @@ function vitePluginRscCss( }, { name: 'rsc:importer-resources', + configureServer(server) { + // delegate 'prune' event from rsc environment to browser + const hot = server.environments.rsc!.hot + const original = hot.send + hot.send = function (this, ...args: any[]) { + const e = args[0] as vite.PrunePayload + if (e && typeof e === 'object' && e.type === 'prune') { + server.environments.client.hot.send({ + type: 'custom', + event: 'rsc:prune', + data: e, + }) + } + return original.apply(this, args as any) + } + }, async transform(code, id) { if (!code.includes('import.meta.viteRsc.loadCss')) return @@ -2175,6 +2202,7 @@ function generateResourcesCode(depsCode: string, manager: RscPluginManager) { rel: 'stylesheet', precedence: 'vite-rsc/importer-resources', href: href, + 'data-rsc-css-href': href, }), ), RemoveDuplicateServerCss &&