Repository: sveltejs/svelte (JavaScript)
Date: 2022-07-02 16:21:03+00:00
Task type: Feature

Issue:
Scoped styles for <svelte:element>
### Describe the problem

Currenlty scoped styling for any dynamic tags generated with <svelte:element> give the generic warning "Unused CSS selector", not really sure if that's a bug or it should be added as a feature!

### Describe the proposed solution

add support for scoped styles with <svelte:element>

### Alternatives considered

currently the only solution to fix that problem is using global styles

### Importance

would make my life easier


Issue discussion:
Could you provide us REPL?
I tried to reproduce it on REPL but I couldn't.
https://svelte.dev/repl/7e4c14c9a5ce430488e8bdc331a27976?version=3.47.0
The issue is, that can't use the generic tag selectors like this:
https://svelte.dev/repl/23d982fc6f4f4f06a6aa227860fa2d84?version=3.47.0

Your workaround with using classes works for me right now tho :D
Ah I got it. Thank you!

I think this workaround also useful.
https://svelte.dev/repl/3832a541ea24479581c97bfd0a934b7b?version=3.47.0
I guess when we try to see if the selectors match any element, we can make `svelte:element` to match all element selector ?

```svelte
<div>
  <svelte:element class="a" this={...}>
     <span />
  </svelte:element>
</div>

<style>
   /* used selectors */
   h1 { }
   p span { }
   ul.a { }
   div > b > span { }
   div > i { }

   /* unused selectors */
   h2 div { }
   h3.b { }
</style>
```
I think the compiler disable CSS removal process if using dynamic class name.
Should we do a similar process for `<svelte:element>`?
(If `<svelte:element>` is there, we don't remove css which specified tag name like `h1 { color: blue }`)

![image](https://user-images.githubusercontent.com/19153718/162924870-624f8a96-e658-483f-aae2-dd5e94199e14.png)

@tanhauhau 

oops, we are posting at the exact same time lol.
Yes, I think we can implement such a process.

Can I try?
A thought,
Making special considerations for things like svelte:element might get out of hand as more features are added.

How about introducing a way for users to tell the compiler to not purge certain selectors? Essentially what people do with `:global()` but it'd still keep scoping. This would solve the problem at hand right? While also unlocking more possibilities in userland. [An example possibility](https://svelte.dev/repl/71245511cf144d189f51ba65aeb8cb9e?version=3.47.0)

There's already an issue proposing something similar, will link it and carry over the discussion there.
> A thought, Making special considerations for things like svelte:element might get out of hand as more features are added.
> 
> How about introducing a way for users to tell the compiler to not purge certain selectors? Essentially what people do with `:global()` but it'd still keep scoping. This would solve the problem at hand right? While also unlocking more possibilities in userland. [An example possibility](https://svelte.dev/repl/71245511cf144d189f51ba65aeb8cb9e?version=3.47.0)
> 
> There's already an issue proposing something similar, will link it and carry over the discussion there.

I love this idea, maybe making a special keyword like :global() something like :keep() or whatever will fix all the issues of purging what the compiler sees unused css
> There's already an issue proposing something similar, will link it and carry over the discussion there.

Could you please tell me where is that?

And personally, it sounds like it is transferring the responsibilities of the Svelte compiler to userland regarding purging CSS.
It seems to me that as long as Svelte has the ability to purge CSS, it has the responsibility to handle all cases.
I'd also love to opt out of purging (while keeping scoping).
Found it https://github.com/sveltejs/svelte/issues/5804

> And personally, it sounds like it is transferring the responsibilities of the Svelte compiler to userland regarding purging CSS.

It is kinda, (*technically it'd be the responsibility of not purging CSS*), IMO it's fine in this case, we can't expect Svelte to bend for every possible scenario that people can conjure.

Forgive me if I misunderstood the current proposed solution to this issue, my interpretation was that if the component has a dynamic class name somewhere then Svelte will ignore unused CSS in that component? Which I think opens up more possibilities for pitfalls, and an explicit opt in to keep styles would be a lot better here.
> Can I try?

@baseballyama yup go for it 🚀
There is actual problem here regarding CSS purging, so I think we need to discuss about it.
And I think I should wait to handle this until then.

https://github.com/sveltejs/svelte/issues/5804#issuecomment-1099913005

Solution patch:
diff --git a/src/compiler/compile/css/Selector.ts b/src/compiler/compile/css/Selector.ts
--- a/src/compiler/compile/css/Selector.ts
+++ b/src/compiler/compile/css/Selector.ts
@@ -299,7 +299,7 @@ function block_might_apply_to_node(block: Block, node: Element): BlockAppliesToN
 				return BlockAppliesToNode.NotPossible;
 			}
 		} else if (selector.type === 'TypeSelector') {
-			if (node.name.toLowerCase() !== name.toLowerCase() && name !== '*') return BlockAppliesToNode.NotPossible;
+			if (node.name.toLowerCase() !== name.toLowerCase() && name !== '*' && !node.is_dynamic_element) return BlockAppliesToNode.NotPossible;
 		} else {
 			return BlockAppliesToNode.UnknownSelectorType;
 		}


Test patch:
diff --git a/test/css/samples/dynamic-element-tag/_config.js b/test/css/samples/dynamic-element-tag/_config.js
new file mode 100644
--- /dev/null
+++ b/test/css/samples/dynamic-element-tag/_config.js
@@ -0,0 +1,3 @@
+export default {
+	warnings: []
+};
diff --git a/test/css/samples/dynamic-element-tag/expected.css b/test/css/samples/dynamic-element-tag/expected.css
new file mode 100644
--- /dev/null
+++ b/test/css/samples/dynamic-element-tag/expected.css
@@ -0,0 +1 @@
+div.svelte-xyz.svelte-xyz.svelte-xyz{color:red}h2.svelte-xyz>p.svelte-xyz.svelte-xyz{color:red}h2.svelte-xyz span.svelte-xyz.svelte-xyz{color:red}h2.svelte-xyz>span.svelte-xyz>b.svelte-xyz{color:red}h2.svelte-xyz span b.svelte-xyz.svelte-xyz{color:red}h2.svelte-xyz b.svelte-xyz.svelte-xyz{color:red}
\ No newline at end of file
diff --git a/test/css/samples/dynamic-element-tag/expected.html b/test/css/samples/dynamic-element-tag/expected.html
new file mode 100644
--- /dev/null
+++ b/test/css/samples/dynamic-element-tag/expected.html
@@ -0,0 +1,4 @@
+<div class="svelte-xyz"></div>
+<h2 class="svelte-xyz">
+	<div class="svelte-xyz"><b class="svelte-xyz">text</b></div>
+</h2>
diff --git a/test/css/samples/dynamic-element-tag/input.svelte b/test/css/samples/dynamic-element-tag/input.svelte
new file mode 100644
--- /dev/null
+++ b/test/css/samples/dynamic-element-tag/input.svelte
@@ -0,0 +1,32 @@
+<script>
+	export let element = 'div';
+</script>
+
+<svelte:element this={element} />
+
+<h2>
+	<svelte:element this={element}>
+		<b>text</b>
+	</svelte:element>
+</h2>
+
+<style>
+	div {
+		color: red;
+	}
+	h2 > p {
+		color: red;
+	}
+	h2 span {
+		color: red;
+	}
+	h2 > span > b {
+		color: red;
+	}
+	h2 span b {
+		color: red;
+	}
+	h2 b {
+		color: red;
+	}
+</style>