Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document edge-cases/issues with replacing default Svelte scoping #36

Closed
madeleineostoja opened this issue Nov 23, 2021 · 4 comments
Closed

Comments

@madeleineostoja
Copy link

The README mentions that enabling useAsDefaultScoping can cause issues especially when applied to external elements. What are these issues? And more broadly, what potential edge-cases can come up with this preprocessor over Svelte's default scoping strategy? It seems to be a pretty drastic rewrite of core scoping logic, so it'd great to have some kind of breakdown of pros/cons/potential gotchas with this approach. Right now it seems a little too good to be true.

@micantoine
Copy link
Owner

The Svelte approach is to append every css selectors with a unique class to only affect the elements of the component.

The CSS Modules approach is to scope every class names with a unique id/name to only affect the elements of the component.

Approach Example

Here is the svelte component

<p>lorem ipsum tut etoyu</p>
<p class="red">lorem ipsum tut etoyu</p>

<style>
  p {
    font-size: 14px
  }
  .red {
   color: red;
  }
</style>

After compilation, the component will look like this

Svelte scoped styles

<p class="svelte-123qwe>lorem ipsum tut etoyu</p>
<p class="red svelte-123qwe">lorem ipsum tut etoyu</p>

<style>
  p.svelte-123qwe {
    font-size: 14px
  }
  .red.svelte-123qwe {
   color: red;
  }
</style>
  • Any css inside <style> is scoped to that component
  • Class names are left untouched

CSS Modules preprocessor

<p>lorem ipsum tut moue</p>
<p class="red-123qwe">lorem ipsum tut moue</p>

<style>
  p {
    font-size: 14px
  }
  .red-123qwe {
   color: red;
  }
</style>
  • Only css selectors containing a class is scoped to that component
  • Class names are being replaced by a unique id

As non class selectors rules are applying globally, it is preferable to base each selector with a class.

<p class="text">lorem ipsum tut moue</p>
<p class="text red">lorem ipsum tut moue</p>

<style module>
  .text {
    font-size: 14px
  }
  .red {
   color: red;
  }
</style>

Compiling to

<p class="text-456rty">lorem ipsum tut moue</p>
<p class="text-456rty red-123qwe">lorem ipsum tut moue</p>

<style>
  .text-456rty{
    font-size: 14px
  }
  .red-123qwe {
   color: red;
  }
</style>

Why using CSS Modules over Svelte scoping ?

  • On a full svelte application: it is just a question of taste as the default svelte scoping is largely enough. Component styles will never inherit from other styling.

  • On a hybrid project (like using svelte to enhance a web page): the default scoping may actually inherits from a class of the same name belonging the page styling. In that case using CSS Modules to create a unique ID and to avoid class inheritance might be advantageous.

useAsDefaultScoping potential issue with third party plugins

The preprocessor requires you to add the module attribute to <style> in order to apply CSS Modules to a component. When enabling useAsDefaultScoping the module attribute is not required anymore and CSS Modules will apply to all svelte components of your application, including plugins. If a third party component is relying on svelte scoping and non class selectors, its styling will apply globally and may cause unexpected results. Therefore, It is recommended to set the includePaths option to your source folder in order to target your components only.

// config
preprocess: [
  cssModules([
    useAsDefaultScoping: true,
    includePaths: ['./src'],
  ]),
],

Preprocessor modes

Native

Pros:

  • uses default CSS Modules approach
  • creates unique ID to avoid classname conflicts and unexpected inheritances

Cons:

  • does not scopes non class selectors.
  • forces to write selectors with classes to be scoped to the component.
  • needs to consider third party plugins with useAsDefaultScoping on.

Mixed

Pros:

  • Creates class names with unique ID to avoid conflicts and unexpected inheritances
  • Uses svelte scoping on non class selectors

Cons:

  • adds more weight to tag selectors than class selectors (because of the svelte scoping)
<ul>
 <li>Home</li>
 <li class="active">About</li>
</ul>

<style module>
 li {
   color: gray; 
 }
 /* this will never be applied */
 .active {
   color: blue;
 }
 /* forcing us to write that instead */
 li.active {
   color: blue;
 }
</style>

<!-- or rewriting the component -->

<ul>
 <li class="item">Home</li>
 <li class="item active">About</li>
</ul>

<style module>
 .item {
   color: gray; 
 }
 .active {
   color: blue;
 }
</style>

Scoped

Pros:

  • Creates class names with unique ID to avoid conflicts and unexpected inheritances
  • scopes every selectors at equal weight

Cons:
not sure really It's like using the default svelte scoping while making sure the names of the classes are unique. So probably that issue coming from svelte.

@madeleineostoja
Copy link
Author

Thank you for the very thorough write up! I think this should def be added to the readme or similar.

I’m particularly interested in the scoped strategy, so you get the same behaviour as svelte’s scoping while also being able to pass scoped classnames to child components. If doubling classnames length is the only issue I think that’s a very good trade off

@micantoine
Copy link
Owner

same behaviour as svelte’s scoping while also being able to pass scoped classnames to child components

Actually it won't work. This is something I should add up to the list of pros/cons. Due to the svelte scoping, the scoped mode will not allow the passing of a scoped classname to a child component and not inherit its styling. Only the native and mixed mode do.

<!-- Child Component Button.svelte -->

<button class={$$restProps.class}><slot /></button>
<style>
  button {
    background: red;
    color: white;
  }
</style>
<!-- Parent Component -->

<script>
  import Button from './Button.svelte';
</script>

<div class="wrapper">
  <h1>Welcome</h1>
  <Button class="btn" />
</div>

<style module="scoped">
  .wrapper {
    margin: 0 auto;
    padding: 16px;
    max-width: 400px;
  }
  .btn {
    margin-top: 30px;
  }
</style>
  • svelte will drop the .btn rule from <style> because no elements of the component is using it.
<!-- Parent Component -->

<script>
  import Button from './Button.svelte';
</script>

<div class="wrapper">
  <h1>Welcome</h1>
  <p class="space">Lorem ipsum tut ewou tu po</p>
  <Button class="space" />
</div>

<style module="scoped">
  .wrapper {
    margin: 0 auto;
    padding: 16px;
    max-width: 400px;
  }
  .space {
    margin-top: 30px;
  }
</style>
  • the .space rule will only apply to <p> because svelte appends the scoped classname to the elements of that component, not to child components. Button will not inherit the style of .space.

So to summarize

Svelte scoping Preprocessor Native Preprocessor Mixed Preprocessor Scoped
scopes classes O O O O
scopes non class selectors O X O O
creates unique class ID X O O O
has equal selector weight O O X O
can pass scoped classname to child component X O O X

@micantoine
Copy link
Owner

@madeleineostoja After adding some new features, I updated the Readme. Thank you for your inputs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants