Hello,
first of all, thanks for this great piece of software, appreciate working with it!
I wonder if it is possible to disable _unused css_ checks for specific parts, similar to _eslint:disable:nounsed_
Adding some classes only programmatically throws a warning when bundling, as it expects all classes to exist in the template. Is there a way around it?
Best, Markus
The recommendation is not to add and remove classes programmatically, for a couple of reasons:
Instead, it's more common to express dynamic classes in the template:
{#each things as thing}
<div class="{thing === selected ? 'selected' : ''}>
{thing.name}
</div>
{/each}
<style>
.selected {
font-weight: bold;
}
</style>
However if you really need to manipulate classes programmatically you can preserve the CSS with the :global(...) modifier. This will result in non-encapsulated CSS:
<div ref:things>
{#each things as thing}
<div data-id={thing.id}>
{thing.name}
</div>
{/each}
</div>
<style>
:global(.selected) {
font-weight: bold;
}
</style>
<script>
export default {
onupdate({ changed, current, previous }) {
if (changed.selected) {
if (previous.selected) {
const div = this.refs.things.querySelector(`[data-id="${previous.selected.id}"]`);
div.classList.remove('selected');
}
if (current.selected) {
const div = this.refs.things.querySelector(`[data-id="${current.selected.id}"]`);
div.classList.add('selected');
}
}
}
};
</script>
To preserve some encapsulation you can put the global selector inside a local one — it will still affect any child components, but it won't affect the rest of the page:
<style>
ref:things :global(.selected) {
font-weight: bold;
}
</style>
Thanks for this great writeup, I definitely will put this to my bookmarks!
I also found a super clean solution for my issue, which was just for fading in an element, so I could do it as simple as:
{#if backdropActive}
<div transition:fade="{duration: fadeDuration}" aria-hidden="false"></div>
{/if}
<script type="text/javascript">
import { fade } from 'svelte-transitions';
export default {
transitions: { fade }
}
</script>
Still we have now a different issue, same warning, but different usecase:
We're using some external scss framework that provides variables, functions, mixins, etc which are required currently to be in each <style>
<style type="text/scss">
@import 'node_modules/fundamental-ui/scss/icons';
@import 'node_modules/fundamental-ui/scss/core';
@import 'node_modules/fundamental-ui/scss/components/button';
// sometimes even more imports
Of course there are lots of css declarations unused with this type of usage.
Is there a better way, have I generally missed something?
Another valid use case for this is non-Svelte components. We've got an ag-grid implementation with renderers that apply classes for example.
And another use case is when rendering a recursive components (e.g. trees) with <svelte:self /> - I wanted to style nested levels (e.g. ul ul {}) and got the "Unused CSS" warning and the css gets removed.
I'd probably be somewhat against having ul ul {} be able to style ul => <svelte:self> => ul.
What happens when you have <Foo> => ul => <Bar> => ... => <Foo> => ul? The <Foo> component has no way of knowing whether <Bar> may have another instance of <Foo> inside it. It shouldn't keep the ul ul selector just because somewhere down in the tree eventually there might be another instance of <Foo> containing a ul.
I think the current rule of 'selectors refer to _this instance_ of _this component_ (unless they are wrapped in :global(...))' is much easier to reason about than 'selectors refer to _this instance_ of _this component_ or immediately nested ones via <svelte:self> but not any deeper ones'.
@Conduitry yep, I used :global to make it work.
Closing this - I think the current behavior is what we want.
So I use Sass mixins and would love an eslint-style ignore option. Here's an example mixin:
@mixin shape {
background-image: linear-gradient(135deg, $pink-3, $violet-4 100%);
&[shape="circle"],
&.circle {
clip-path: circle(50% at 50% 50%);
-webkit-clip-path: circle(50% at 50% 50%);
}
&[shape="diamond"],
&.diamond {
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
-webkit-clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
}
&[shape="triangle"],
&.triangle {
clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
-webkit-clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
}
&[shape="triangle-alt"],
&.triangle-alt {
clip-path: polygon(50% 100%, 0 0, 100% 0);
-webkit-clip-path: polygon(50% 100%, 0 0, 100% 0);
}
img {
width: 100%; height: 100%;
object-fit: cover;
}
}
When including this into my avatar class, obviously only one of these rules will be used. However, due to the nature of my app, there usage of the included styles will vary from page to page.
EDIT: It'd be neat if I could place a rule inside the mixin file itself but that's probably the purview of the preprocessor...
Here's how I "solved" the issue for the particular mixin mentioned above:
// in rollup.config.js
...,
svelte({
preprocess,
dev: isDevelopment,
hydratable: true,
emitCss: true,
onwarn: (warning, handler) => {
const { code, frame } = warning;
if (code === "anchor-is-valid" || code === "a11y-autofocus")
return;
if (code === "css-unused-selector" && frame.includes("shape"))
return;
handler(warning);
}
}),
...
I'm still getting the error on rules like header-navigation-section.active because .active is conditional but I'll figure that out some other time.
I'd probably be somewhat against having
ul ul {}be able to styleul=><svelte:self>=>ul.What happens when you have
<Foo>=>ul=><Bar>=> ... =><Foo>=>ul? The<Foo>component has no way of knowing whether<Bar>may have another instance of<Foo>inside it. It shouldn't keep theul ulselector just because somewhere down in the tree eventually there might be another instance of<Foo>containing aul.I think the current rule of 'selectors refer to _this instance_ of _this component_ (unless they are wrapped in
:global(...))' is much easier to reason about than 'selectors refer to _this instance_ of _this component_ or immediately nested ones via<svelte:self>but not any deeper ones'.
I ran into the issue @tborychowski mentioned.
I don't see any negative impacts in allowing descendant rules in <svelte:self> cases. In my opinion resorting to :global() or a workaround as below is inferior.
{#if false}
<ul><ul><span>dummy to allow descendant rule</span></ul></ul>
{/if}
I ran into a similar problem where I had <section> in HTML and section + section in CSS.
Most helpful comment
The recommendation is not to add and remove classes programmatically, for a couple of reasons:
Instead, it's more common to express dynamic classes in the template:
However if you really need to manipulate classes programmatically you can preserve the CSS with the
:global(...)modifier. This will result in non-encapsulated CSS:To preserve some encapsulation you can put the global selector inside a local one — it will still affect any child components, but it won't affect the rest of the page: