Skip to content

Commit c211c27

Browse files
authored
Merge branch 'main' into copilot/fix-90930974-c143-4a2d-93c4-be9b0d9ada15
2 parents 9c36e99 + 3114e88 commit c211c27

File tree

4 files changed

+69
-5
lines changed

4 files changed

+69
-5
lines changed

packages/plugin-rsc/e2e/basic.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,22 @@ function defineTest(f: Fixture) {
473473
'color',
474474
'rgb(255, 165, 0)',
475475
)
476+
await expectNoDuplicateServerCss(page)
477+
})
478+
479+
async function expectNoDuplicateServerCss(page: Page) {
480+
// check only manually inserted stylesheet link exists
481+
// (toHaveAttribute passes only when locator matches single element)
482+
await expect(page.locator('link[rel="stylesheet"]')).toHaveAttribute(
483+
'href',
484+
'/test-style-server-manual.css',
485+
)
486+
}
487+
488+
test('no duplicate server css', async ({ page }) => {
489+
await page.goto(f.url())
490+
await waitForHydration(page)
491+
await expectNoDuplicateServerCss(page)
476492
})
477493

478494
test('adding/removing css client @js', async ({ page }) => {
@@ -557,6 +573,7 @@ function defineTest(f: Fixture) {
557573
'color',
558574
'rgb(255, 165, 0)',
559575
)
576+
await expectNoDuplicateServerCss(page)
560577
})
561578

562579
// TODO: need a way to add/remove links on server hmr. for now, it requires a manually reload.

packages/plugin-rsc/examples/basic/src/routes/style-server/server.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export function TestStyleServer() {
1010
</div>
1111
<link
1212
rel="stylesheet"
13-
href="/test.css"
13+
href="/test-style-server-manual.css"
1414
precedence="test-style-server-manual"
1515
/>
1616
<div className="test-style-server-manual">test-style-server-manual</div>

packages/plugin-rsc/src/plugin.ts

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,8 @@ window.__vite_plugin_react_preamble_installed__ = true;
884884
const resolvedEntry = await this.resolve(source)
885885
assert(resolvedEntry, `[vite-rsc] failed to resolve entry '${source}'`)
886886
code += `await import(${JSON.stringify(resolvedEntry.id)});`
887-
// TODO: this doesn't have to wait for "vite:beforeUpdate" and should do it right after browser css import.
887+
// server css is normally removed via `RemoveDuplicateServerCss` on useEffect.
888+
// this also makes sure they are removed on hmr in case initial rendering failed.
888889
code += /* js */ `
889890
const ssrCss = document.querySelectorAll("link[rel='stylesheet']");
890891
import.meta.hot.on("vite:beforeUpdate", () => {
@@ -1992,6 +1993,36 @@ export function vitePluginRscCss(
19921993
}
19931994
},
19941995
},
1996+
createVirtualPlugin(
1997+
'vite-rsc/remove-duplicate-server-css',
1998+
async function () {
1999+
// Remove duplicate css during dev due to server rendered <link> and client inline <style>
2000+
// https://github.com/remix-run/react-router/blob/166fd940e7d5df9ed005ca68e12de53b1d88324a/packages/react-router/lib/dom-export/hydrated-router.tsx#L245-L274
2001+
assert.equal(this.environment.mode, 'dev')
2002+
function removeFn() {
2003+
document
2004+
.querySelectorAll("link[rel='stylesheet']")
2005+
.forEach((node) => {
2006+
if (
2007+
node instanceof HTMLElement &&
2008+
node.dataset.precedence?.startsWith('vite-rsc/')
2009+
) {
2010+
node.remove()
2011+
}
2012+
})
2013+
}
2014+
return `\
2015+
"use client"
2016+
import React from "react";
2017+
export default function RemoveDuplicateServerCss() {
2018+
React.useEffect(() => {
2019+
(${removeFn.toString()})();
2020+
}, []);
2021+
return null;
2022+
}
2023+
`
2024+
},
2025+
),
19952026
]
19962027
}
19972028

@@ -2022,6 +2053,7 @@ function generateResourcesCode(depsCode: string) {
20222053
const ResourcesFn = (
20232054
React: typeof import('react'),
20242055
deps: ResolvedAssetDeps,
2056+
RemoveDuplicateServerCss?: React.FC,
20252057
) => {
20262058
return function Resources() {
20272059
return React.createElement(React.Fragment, null, [
@@ -2042,14 +2074,29 @@ function generateResourcesCode(depsCode: string) {
20422074
src: href,
20432075
}),
20442076
),
2077+
RemoveDuplicateServerCss &&
2078+
React.createElement(RemoveDuplicateServerCss, {
2079+
key: 'remove-duplicate-css',
2080+
}),
20452081
])
20462082
}
20472083
}
20482084

20492085
return `
2050-
import __vite_rsc_react__ from "react";
2051-
export const Resources = (${ResourcesFn.toString()})(__vite_rsc_react__, ${depsCode});
2052-
`
2086+
import __vite_rsc_react__ from "react";
2087+
2088+
${
2089+
config.command === 'serve'
2090+
? `import RemoveDuplicateServerCss from "virtual:vite-rsc/remove-duplicate-server-css";`
2091+
: `const RemoveDuplicateServerCss = undefined;`
2092+
}
2093+
2094+
export const Resources = (${ResourcesFn.toString()})(
2095+
__vite_rsc_react__,
2096+
${depsCode},
2097+
RemoveDuplicateServerCss,
2098+
);
2099+
`
20532100
}
20542101

20552102
export async function transformRscCssExport(options: {

0 commit comments

Comments
 (0)