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

RFC: Scoped Style Attribute #901

Closed
lukeed opened this issue Oct 24, 2017 · 16 comments
Closed

RFC: Scoped Style Attribute #901

lukeed opened this issue Oct 24, 2017 · 16 comments

Comments

@lukeed
Copy link
Member

lukeed commented Oct 24, 2017

If this is the wrong place for this -- sorry! StackOverflow didn't seem like the right place to me.

Currently, the inclusion of any CSS within a style tag is considered scoped. The only way out of this is to wrap your selector(s) in :global(). This, however, gets to be fairly verbose when you want to wrap all selectors on the page.

<style>
:global(.foo) {
  text-align: center;
}
:global(.foo span) {
  font-size: 80%;
}
:global(.foo em) {
  color: pink;
}
.bar {
  text-align: right;
}
</style>
<!-- unscoped: ".foo *" -->
<!-- scoped: ".bar" -->

Arguably, the appeal of SFCs is encapsulating all component-relatives into the single file. Additionally, a core motivation of Svelte (imo) is to assist in the creation of vanilla apps (including HTML, CSS, JS) in a nicer, faster way. (We all know this, duh!)

I bring this up because, right now, devs are essentially penalized for using the SFC format --- the obvious format for Svelte --- with an additional 17 bytes of CSS per selector, and the only ways to avoid the penalty are cumbersome: keep all CSS in a styles/ dir or :global all the things.

Although rendering is fast AF, there's "penalty" for rendering the scoped attribute & additional bytes penalty for saving the scope assignment in bundle.

Proposition

I'd like to use Vue as inspiration & move the current behaviors under the scoped attribute for the style tag. Nothing would change, including the ability to "exit" the scope with the help of :global.

<style scoped>
:global(.foo) {
  text-align: center;
}
:global(.foo span) {
  font-size: 80%;
}
:global(.foo em) {
  color: pink;
}
.bar {
  text-align: right;
}
<!-- same as current -->
<style>
.foo {
  text-align: center;
}
.foo span {
  font-size: 80%;
}
.foo em {
  color: pink;
}
.bar {
  text-align: right;
}
<!-- all unscoped -->

Then, without scoped, Svelte would treat normal style contents as partial CSS files. By default, no "penalties" via bytes (& render time, lol) are suffered. Instead, Svelte concatenates the stylesheet, as is, via component assembly.

This also opens the door for CSS preprocessors down the road via the lang attribute... another hat tip to Vue.

I plan on opening another RFC for lang at a later time. 😄

@Rich-Harris
Copy link
Member

if this is the wrong place for this -- sorry!

nope, right place!

I definitely hear you, though it feels like disabling scoping by default is the wrong solution. Or rather, disabling scoping would be the right solution if we wanted to be able to disable scoping, but not for the sake of a side-effect of doing so — because we don't want to! (At least, I don't think we do?)

If <style scoped> was something people opted in to, a lot of people wouldn't use it, and as a consequence they'd end up reverting to over-engineered namespacing conventions (which defeats the object) or they'd experience annoying-to-debug conflicts. Or perhaps they would read about what scoped entails, and opt in reluctantly because they believe it would have deleterious performance impacts. Eliminating that kind of 'configuration anxiety' from web development (and the associated learning overhead) is one of Svelte's goals.

Which prompts two further questions:

  1. How bad are the performance impacts?
  2. Is there an alternative to making scoped optional?

I believe the answer to 1 is 'not very'. Bytes-wise, although the selectors themselves don't compress well, you only have one unique selector per component so I'd expect the post-gzip impact to be acceptable. Perf-wise, there's definitely a theoretical hit, but what kind of numbers are we talking about? (Genuine question!)

As for 2, the answer is, well... maybe? For example if we analysed your entire component tree, we could generate efficient Styletron-style selectors and replace classes accordingly (gets slightly tricky with dynamic classes, but still possible). That has one major downside which is that your original classes would vanish (follow me on Twitter for occasional rants about why that's bad...), but if it's an opt-in for perf-heads like you then I'd be okay with it 😀

These are just my thoughts though, I would be interested to know what others think.

@ekhaled
Copy link
Contributor

ekhaled commented Oct 24, 2017

just an idea...

how about going the opposite way, i.e:
<style unscoped>

so we keep current behaviour and add unscoped styles if needed, by declaring them explicitly?

@lukeed
Copy link
Member Author

lukeed commented Oct 24, 2017

Cool, thanks!

If <style scoped> was something people opted in to, a lot of people wouldn't use it, and as a consequence they'd end up reverting to over-engineered namespacing conventions

I tend to disagree. I think it's a matter of documentation. The ability to scoped was one of the first things I learned about Vue, actually.

  1. I can do some work on comparing rendering impacts, though I strongly suspect it's mostly theoretical, as you say. I think there's more loss in older browsers' ability to match CSS with an attribute inclusion. (I mean, like, IE8/9 old.)

    The bytes do come to be a bit of an issue, imo. I have a a demo app, which is a single component with cascade:false, and the scoped namespace is added to the stylesheet 18 times. That's an extra 306 bytes of CSS! (plus an extra 17 for holding the JS value)

    Now, 306 isn't that much, initially --- although I do have libraries that are smaller than that.

    It's more of a problem with actual applications with, say, 10-20 unique components. I have one of these, but unfortunately cannot link to it. Although, I will say that the byte-increase definitely scales up predictably.

    So I end up having to (over?)engineer solutions to avoid the scope/namespace altogether.

  1. I've thought about this approach for a long time. I've mixed feeling about it, too. While you can definitely generate unique 3-4 character strings, it's hard to catch every use scenario, which is hugely prone to bugs, and the final result is a semantically unrecognizable app... aka, hard to debug.

My inclination is to still go with the scoped attribute, especially since it's declarative. And it can be "on by default" by just including it by default on all examples & CLI templates.

I also like @ekhaled's idea, but would like noscope instead as a homage to CS days 😆... but seriously.

The important part, to me, is just having a simple, straightforward way to turning it off, aside from just avoiding style tags altogether.

@lukeed
Copy link
Member Author

lukeed commented Oct 30, 2017

Another option is to just call the attribute global so that it aligns with current naming. Behind the scenes, it could just wrap each selector with the :global modifier.

@arxpoetica
Copy link
Member

This seems to be the same issue as #1080 (which I closed, since this conversation was already in progress).

@ansarizafar
Copy link

I totally agree with @lukeed

@burningTyger
Copy link
Contributor

The global style could also be used to allow @media, @page or @font-face in css. So instead of just a component with some visual attributes components could also be used to add style to a page.

@cssandstuff
Copy link

cssandstuff commented Jun 5, 2018

is there currently a way keep scoped styles for a css classname that is dynamic and is on the same element?
So I was expecting to be able to do this:

img:global(.dummy){
  width: 100%;
  height: 100%;
  opacity: 0;
}

resulting in
img.svelte-pz57d7.dummy{width:100%;height:100%;opacity:0}

at the moment you can only do it without the space

img :global(.dummy){
  width: 100%;
  height: 100%;
  opacity: 0;
}

resulting in
img.svelte-pz57d7 .dummy{width:100%;height:100%;opacity:0}

syntax like this might also be handy.

img &.dummy{
/* styles here*/
}

@lukeed
Copy link
Member Author

lukeed commented Jun 5, 2018

@cssandstuff This might be edging on "offtopic" territory, not sure 😉. But, I think you may be after :global(img.dummy)

@Rich-Harris
Copy link
Member

@cssandstuff not sure I understand what result you're looking for (i.e. what CSS should be generated)?

Some compound selectors will look wacky inside :global(...), depending on your syntax highlighter, so I generally recommend :global(img).dummy rather than :global(img.dummy). They both mean the same thing though so either will do

@thgh
Copy link
Contributor

thgh commented Jun 6, 2018

Grouping leaky styles together would make things cleaner.

<style>
@document {   /* No highlighting support for @global */
  .btn>.icon {}
  .btn>.icon:hover {}
}
</style>

@arxpoetica
Copy link
Member

For anyone following along at home, there’s this RFC which is just in the beginning stages and has not PR yet: https://github.com/arxpoetica/rfcs/blob/css-in-html/text/0000-css-in-html.md

@nolanlawson
Copy link
Contributor

Perf-wise, there's definitely a theoretical hit, but what kind of numbers are we talking about? (Genuine question!)

I'm curious about this myself. In Pinafore, I wrote a script to make all CSS global because I wanted to get rid of the extra attribute selectors, but now that attribute selectors are gone (#1118) it's worth revisiting.

I built Pinafore with and without the globalizing, and the total size of all CSS (35 files) increased from 41372 bytes to 48672 bytes (41.3kb -> 48.6kb, +17.6%). So not an enormous increase.

Probably more interesting would be to measure the impact on style calculation costs, but this is going to vary heavily from app to app (e.g. how much layout thrashing you may be doing, how complex your DOM is) and from browser to browser.

@aheissenberger
Copy link

I used @maxmilton 's code and created this preprocessor which will make any css code global including support for external files and postcss plugins:
https://www.npmjs.com/package/svelte-preprocess-css-global

@btakita
Copy link
Contributor

btakita commented May 14, 2019

I have a solution for sass/scss with the style[global] attribute in the @ctx-core/sass package.

https://github.com/ctx-core/ctx-core/blob/master/packages/sass/svelte.js#L34

@antony
Copy link
Member

antony commented Apr 9, 2020

Closing this in favour of the RFC that is open, and because support for global is available via preprocessors, and also, we've discussed a lot that scoping a style tag is not the approach we want. Focus should be shifted to the RFC

@antony antony closed this as completed Apr 9, 2020
@sveltejs sveltejs locked as resolved and limited conversation to collaborators Apr 9, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests