diff --git a/.changeset/eight-months-camp.md b/.changeset/eight-months-camp.md new file mode 100644 index 000000000..3767cae75 --- /dev/null +++ b/.changeset/eight-months-camp.md @@ -0,0 +1,5 @@ +--- +"@solidjs/start": patch +--- + +Fixed CSS from shared chunks not being collected via the chunk name. diff --git a/apps/fixtures/css/src/components/sharedChunk/lazy1.tsx b/apps/fixtures/css/src/components/sharedChunk/lazy1.tsx new file mode 100644 index 000000000..484d65e92 --- /dev/null +++ b/apps/fixtures/css/src/components/sharedChunk/lazy1.tsx @@ -0,0 +1,5 @@ +import "../../styles/sharedChunk.css"; + +export default () => { + return <>; +}; diff --git a/apps/fixtures/css/src/components/sharedChunk/lazy2.tsx b/apps/fixtures/css/src/components/sharedChunk/lazy2.tsx new file mode 100644 index 000000000..42083cc07 --- /dev/null +++ b/apps/fixtures/css/src/components/sharedChunk/lazy2.tsx @@ -0,0 +1,3 @@ +export default () => { + return <>; +}; diff --git a/apps/fixtures/css/src/components/test.tsx b/apps/fixtures/css/src/components/test.tsx index 43b7de1b9..a4facf368 100644 --- a/apps/fixtures/css/src/components/test.tsx +++ b/apps/fixtures/css/src/components/test.tsx @@ -101,6 +101,19 @@ export const CommonTests = (props: { routeModuleClass?: string }) => ( } /> + + Tests if CSS from shared chunks is server rendered properly. Rollup occasionally combines + modules into such shared chunks. + + } + /> ); diff --git a/apps/fixtures/css/src/routes/index.tsx b/apps/fixtures/css/src/routes/index.tsx index 08933785e..70cd855e8 100644 --- a/apps/fixtures/css/src/routes/index.tsx +++ b/apps/fixtures/css/src/routes/index.tsx @@ -14,6 +14,11 @@ const LazyLinkTmp = lazy(() => import("../components/lazyLinkTmp")); const entries = import.meta.glob("../components/lazyG*.tsx"); const LazyGlob = lazy(Object.values(entries)[0] as any); +const SharedChunk = lazy(() => import("../components/sharedChunk/lazy1")); +// Do not remove this. +// Rollup only creates a shared chunk if there are atleast two modules. +lazy(() => import("../components/sharedChunk/lazy2")); + const getData = query(async () => { "use server"; await new Promise(res => setTimeout(res, 1000)); @@ -35,6 +40,7 @@ export default function Home() { + diff --git a/apps/fixtures/css/src/styles/sharedChunk.css b/apps/fixtures/css/src/styles/sharedChunk.css new file mode 100644 index 000000000..bdc50263a --- /dev/null +++ b/apps/fixtures/css/src/styles/sharedChunk.css @@ -0,0 +1,3 @@ +.sharedChunk { + background-color: var(--color-success); +} diff --git a/apps/fixtures/css/vite.config.ts b/apps/fixtures/css/vite.config.ts index 636b0a385..b573727a8 100644 --- a/apps/fixtures/css/vite.config.ts +++ b/apps/fixtures/css/vite.config.ts @@ -5,4 +5,22 @@ import { solidStart } from "../../../packages/start/src/config"; export default defineConfig({ plugins: [solidStart(), nitroV2Plugin(), tailwindcss()], + build: { + rollupOptions: { + output: { + /** + * Creates a shared chunk with two components. Needed for the "SharedChunk" test! + * The vite manifest behaves differently for such shared chunks. + * More info: packages/start/src/config/lazy.ts + * + * TODO: When switching to Rolldown, migrate this to advancedChunks + * https://vite.dev/guide/rolldown.html#manualchunks-to-advancedchunks + */ + manualChunks(id) { + if (!id.includes("src/components/sharedChunk")) return; + return "shared"; + }, + }, + }, + }, }); diff --git a/packages/start/src/config/lazy.ts b/packages/start/src/config/lazy.ts index f94129383..55d4acef1 100644 --- a/packages/start/src/config/lazy.ts +++ b/packages/start/src/config/lazy.ts @@ -48,13 +48,43 @@ const fileEndingRegex = /(ts|js)x(\?.*)?$/; const lazy = (): PluginOption => { const cwd = process.cwd().replaceAll(osSep, sep); + + /** + * Maps module ids to their client-specific shared chunk names. + * Modules in shared chunks need to find their assets via the chunk name, instead of their module id. + * + * Vite includes assets of such modules in the manifest via the chunk name: + * https://github.com/vitejs/vite/blob/4be37a8389c67873880f826b01fe40137e1c29a7/packages/vite/src/node/plugins/manifest.ts#L179 + * https://github.com/vitejs/vite/blob/4be37a8389c67873880f826b01fe40137e1c29a7/packages/vite/src/node/plugins/manifest.ts#L319 + * + * Rollup occassionally creates shared chunks automatically, + * but they can also be manually created by the user via: + * https://rollupjs.org/configuration-options/#output-manualchunks + * + * More infos on Rollup's logic: + * https://github.com/rollup/rollup/issues/3772#issuecomment-689955168 + */ + const sharedChunkNames: Record = {}; + return { name: "solid-lazy-css", enforce: "pre", - applyToEnvironment(env) { - return env.name === VITE_ENVIRONMENTS.server; + generateBundle(_, bundle) { + if (this.environment.name !== VITE_ENVIRONMENTS.client) return; + + for (const chunk of Object.values(bundle)) { + if (chunk.type !== "chunk" || !chunk.isDynamicEntry || chunk.facadeModuleId) continue; + + // Has to follow Vites implementation: + // https://github.com/vitejs/vite/blob/4be37a8389c67873880f826b01fe40137e1c29a7/packages/vite/src/node/plugins/manifest.ts#L179 + const chunkName = `_${basename(chunk.fileName)}`; + for (const id of chunk.moduleIds) { + sharedChunkNames[id] = chunkName; + } + } }, async transform(src, id) { + if (this.environment.name !== VITE_ENVIRONMENTS.server) return; if (!id.match(fileEndingRegex)) return; // The transformed files either import "lazy" or css files @@ -66,7 +96,8 @@ const lazy = (): PluginOption => { const hasDefaultExport = src.indexOf("export default") !== -1; if (hasDefaultExport) { const localId = relative(cwd, id); - plugins.push(idTransform(localId)); + const chunkName = sharedChunkNames[id]; + plugins.push(idTransform(chunkName ?? localId)); } const hasLazy = src.indexOf("lazy(") !== -1;