fix(css): ensure order of extracted CSS#16588
fix(css): ensure order of extracted CSS#16588patak-cat merged 2 commits intovitejs:mainfrom susnux:fix/deterministic-styles
Conversation
|
|
bluwy
left a comment
There was a problem hiding this comment.
Seems like the best fix for now. If the bundle order is non-deterministic, it would be a bug in Rollup, otherwise it should work consistently now.
For other reviewers, from what I understand chunkCSSMap (and the renderChunk hook that sets it) is non-deterministic because Rollup calls renderChunk for all chunks in parallel. If some hook before the CSS plugin does async work with various timing, the calls would be affected as well.
| chunk.dynamicImports.forEach((importName) => | ||
| collect(bundle[importName]), | ||
| ) |
There was a problem hiding this comment.
Isn't this change unrelated to ordering? Wouldn't this mean that we'll be adding CSS of lazy loaded chunks into the parent chunks? 🤔
There was a problem hiding this comment.
No this is only about cssCodeSplit: false. For splitted css the CSS assets are emitted in the renderChunk hook.
So this needs to include all styles.
There was a problem hiding this comment.
Hmm even for cssCodeSplit: false, I think it may make sense to remove this too. When iterating the bundle chunks, we don't want to eagerly crawl the dynamic imports and concatenate its CSS. Dynamic chunks styles should be ordered last so it has a higher priority.
Removing this should also be safe because we iterate chunks with isDynamicEntry: true, which will cover all these dynamic imported chunks.
There was a problem hiding this comment.
Tested it right now, removing makes it non deterministic again, because the order of dynamic entries / dynamic imports in the bundle is not always the same.
There was a problem hiding this comment.
Dynamic chunks styles should be ordered last so it has a higher priority.
So a working (tests) alternative proposal:
Do not include .isDynamicEntry in the main loop, but move the chunk.dynamicImports.forEach to the end.
Because the import order is deterministic, but the output order of dynamic entry chunks is not deterministic in the bundle.
Meaning it will look like this:
function collect(chunk: OutputChunk | OutputAsset) {
if (!chunk || chunk.type !== 'chunk' || collected.has(chunk)) return
collected.add(chunk)
// First collect all styles from the synchronous imports (lowest priority)
chunk.imports.forEach((importName) => collect(bundle[importName]))
// Then collect the styles of the current chunk (might overwrite some styles from previous imports)
css += chunkCSSMap.get(chunk.preliminaryFileName) ?? ''
// Finally collect the styles of dynamic imports (they have the highest priority and might overwrite other styles)
chunk.dynamicImports.forEach((importName) =>
collect(bundle[importName]),
)
}
// The bundle is guaranteed to be deterministic, if not then we have a bug in rollup.
// So we use it to ensure a deterministic order of styles
for (const chunk of Object.values(bundle)) {
if (chunk.type === 'chunk' && chunk.isEntry) {
collect(chunk)
}
}I tested this and the styles are always deterministic and the dynamically imported chunks will overwrite previous styles.
There was a problem hiding this comment.
I'm not sure if that's enough. If the bundle has multiple direct entries, wouldn't that mean on the iteration of the first direct entry, we would crawl its dynamicImports before iterating the second direct entry?
Maybe we should do two loops instead:
- Loop through direct entries and collect the CSS, and record dynamic entries to a Set.
- Loop through the Set (of dynamic entries), and collect the CSS.
That should still keep the dynamic entries deterministic?
There was a problem hiding this comment.
@bluwy you are right, tested this and it works (see commit I pushed).
|
/ecosystem-ci run |
|
📝 Ran ecosystem CI on
✅ analogjs, histoire, ladle, laravel, marko, nuxt, previewjs, quasar, qwik, rakkas, remix, unocss, vite-plugin-pwa, vite-plugin-react, vite-plugin-react-pages, vite-plugin-react-swc, vite-plugin-svelte, vite-plugin-vue, vite-setup-catalogue, vitepress |
|
Rebased to resolve the conflict. |
Without this the order of the extracted CSS rules is defined by the order `renderChunk` of the css plugin is called. So with this the order of CSS rules is always in order of the output chunks of the bundle. Signed-off-by: Ferdinand Thiessen <[email protected]>
…e the styles Signed-off-by: Ferdinand Thiessen <[email protected]>
|
/ecosystem-ci run |
|
📝 Ran ecosystem CI on
✅ analogjs, astro, histoire, ladle, laravel, marko, previewjs, quasar, qwik, rakkas, remix, sveltekit, unocss, vike, vite-plugin-pwa, vite-plugin-react, vite-plugin-react-swc, vite-plugin-svelte, vite-plugin-vue, vite-setup-catalogue, vitepress |
Description
Without this the order of the extracted CSS rules is defined by the order
renderChunkof the css plugin is called. So with this the order of CSS rules is always in alphabetical order of the output chunks.Previously on my project every time I ran the build the output
style.csswas different by rule order, which caused unnecessary conflicts.