svelte icon indicating copy to clipboard operation
svelte copied to clipboard

CSS isolation bug when components styles is equals and have not used css rule

Open NikolayMakhonin opened this issue 5 years ago • 8 comments

I think it is optimization side effect. Likely it can be reproduced only if you have at least one not used css rule. See this repl

See these components:

Component1.svelte

<div class="red"> Red </div>

<style>
	.red {
		background: red;
	}
	.yellow {
		background: yellow;
	}
</style>

Component2.svelte

<div class="red"> Red </div>
<div class="yellow"> Yellow </div>

<style>
	.red {
		background: red;
	}
	.yellow {
		background: yellow;
	}
</style>

1. For these components svelte generates different CSS with same css class suffixes

Component1.css

.red.svelte-1q75qj7{
    background:red
}

Component2.css

.red.svelte-1q75qj7 {
    background:red
}
.red.svelte-1q75qj7{
    background:red
}
.yellow.svelte-1q75qj7{
    background:yellow
}

2. Depending on which component loads first, only one of these styles will be loaded. You can see in the repl that Yellow div is not yellow. Switch the tabIndex default value to 2, and this div will be yellow.

App.svelte

<script>
	import Component1 from './Component1.svelte'
	import Component2 from './Component2.svelte'
	
	let tabIndex = 1
</script>

<button on:click={() => tabIndex = 1}>Tab 1</button>
<button on:click={() => tabIndex = 2}>Tab 2</button>

{#if tabIndex === 1}
	<Component1 />
{:else}
	<Component2 />
{/if}

3. Even if both styles will be loaded, their loading order will affect the style of the component. Thus, the styles of some components will affect others.

NikolayMakhonin avatar Jan 24 '20 10:01 NikolayMakhonin

Interesting. I guess the first way I'm thinking about to handle this is to compute the hash based on the CSS with the unused selectors already removed. (Right now it's a hash of the literal string inside the <style> tag.) If that's going to be too much fiddly work or require too many changes, we could just have it be a hash of the styles concatenated with a list of the removed selectors, since it doesn't matter what sort of string it's the hash of.

Conduitry avatar Jan 24 '20 13:01 Conduitry

To preserve the comment from the closed PR, @tanhauhau suggested basing the CSS hash class on the entire component's string, not just the styles. This would certainly work, and is how it worked before #1091. Some of the reasoning for that change was in the now-deleted Gitter channel, but I think it had something to do with writing a component that you rollup-plugin-replace bits of the HTML of to handle localization, and to let this not affect the generated CSS.

Conduitry avatar Feb 23 '20 13:02 Conduitry

I'm also running into this issue. My use case is that I'm dynamically generating multiple components based on a shared template (which contains <script> and <style>), but each component has different content.

My current workaround is to inject the unique name of the component into a comment in the CSS.

Though it looks like if the CSS is emitted as a separate file, then the styles work regardless in which order the components are loaded.

fkling avatar Dec 18 '20 12:12 fkling

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jun 26 '21 22:06 stale[bot]

@Conduitry The bug is still reproduced in v3.52.0. This is a critical bug, svelte must guarantee the style isolation. Maybe it's time to fix it? We can simply add svelte file path to the hash function of the svelte-{hash} class.

https://svelte.dev/repl/6474b1394fa94fb1af2ef3445c5ef96f?version=3.52.0

NikolayMakhonin avatar Oct 30 '22 06:10 NikolayMakhonin

Temporary solution:

rollup.config.js

function normalizePath(filepath) {
  return filepath.replace(/\\/g, '/')
}

const preprocessBase = sveltePreprocess({
  postcss   : true,
  typescript: true,
})

const preprocess = {
  ...preprocessBase,
  async style({content, filename, ...others}) {
    const result = await preprocessBase.style({
      content,
      filename,
      ...others,
    })

    // fix the style isolation bug: https://github.com/sveltejs/svelte/issues/4313
    result.code = `/* ${normalizePath(filename)} */\r\n${result.code}`

    return result
  },
}

export default {
  ...
  plugins: [
    ...
    svelte({
      preprocess,
      ...
    }),
    ...
  ],
  ...
}

NikolayMakhonin avatar Oct 30 '22 06:10 NikolayMakhonin

Can somebody make a reproduction? I tried both REPL and local SK project and both always load the corresponding CSS, so, though the CSS hashes still collide, I failed to make it cause real troubles.

7nik avatar May 26 '25 09:05 7nik

Is this still an issue? I couldn't reproduce it.

Test both legacy and runes.

Now svelte in dev mode it produce a style tag per component and the unused classes are commented out with an (unused) label on them. Hash do collide but is not a problem since they are generated based on CSS content.

Image

In prod mode duplicated CSS class names (including the hash) are purged but the .yellow class mentioned by the issue opener in his reproduction is retained since at least 1 component uses it.

Image

raythurnvoid avatar May 30 '25 15:05 raythurnvoid