-
Notifications
You must be signed in to change notification settings - Fork 913
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
Is it possible to make "scoped" components style leak optional? #957
Comments
I'm not sure that I understand the problem. If you style a child component, it's root node will be affected (which you would want in this case , wouldn't you?) . If you don't, it doesn't. And I don't see how other nested elements within the child complement would be affected, unless the child was not scoped and used generic class names instead of a solid name space - which would be a problem of the child no matter if the parent uses scoped or not. Yes, css modules offer a higher level of isolation in this case, but I don't see how to easily improve in this direction for "scoped" with our fundamentally changing the whole logic that we have in place. But if you have a good idea, propose it, we are always happy to discuss new ideas. |
Thank you for your reply! Sorry for being not clear with description. If it's acceptable I can display a simple use case. A new developer starts a project by creating an empty dir and doing some simple command-line magic:
After playing around a bit they want to create a website with menu to the left and content to the right. Accoring to VueJS/ReactJS/AngularJS architecture this will be 3 components: one for left-right flexbox split layout, one for menu and one for content. Developer starts with layout, replacing Hello.vue with this:
After that, developer creates a LeftMenu.vue component:
Developer expect "root" to be scoped into componen and do not affect other components. Even comment in sample app hints so: "". The "root" sounds like a short and reasonable name for root div which are completely different for container and for menu. Unfortunately, with beforementioned vue-loader feature the "root" class from container will leak into "menu" componen, making it flexbox which will be totally unexpected and hard to debug. Introducing a configuration option can disable this style leak and make development much more straightforward. May I offer a patch / merge request / pull request so you can see that this is a really small change? |
You won't be able to prevent the CSS from cascading with a "non-leaking-style-scope" modifier. Example: <parent style="display: flex" non-leaking-style-scope>
<child>No styles</child>
</parent> Renders to: <div class="parent" style="display: flex">
<div class="child" style="display: flex">No styles.</div>
</div> Why does this do that?
At the cost of a performance hit, the Shadow DOM could work. Full integration would mean a robust plugin (until it's more widely implemented) that could handle the back and forth of passing dynamic modules to the shadow-dom type component. You would get the benefit of styles that don't leak in or out. You might want to include options to pass in styles or stylesheets you want to inherit - lest you get a blank canvas. Here is one way you can add it yourself: https://github.com/karol-f/vue-custom-element For those like me who would want to know why it doesn't work outside of the Shadow DOM: CSS is designed to cascade styles from parents to children on the computed DOM tree. In CSS, there are elements and rules which inherit the values of the parent by default. (Flexbox is one of those.) Check out this guide by MDN. Since browsers ship with default styles, we don't need to write all the rule defaults for every single rendered element. With Vue templates, all rendered html from components will end up in the DOM, and therefore be child elements of some parent element. The CSS code compiled from Vue template blocks So what is the point of Vue's
Is it possible to make a "non-leaking-styles" method or turn off the cascading rules that display inherited [parent|browser-default] styles? Your options:
Read up on the terms "specificity" and "inheritance" here: P.S. @grigoryvp For layouts, I highly recommend learning CSS Grid because it's children don't inherit the way flexbox does. Once it clicks, you won't want to go back. EDIT: Forgot about the Shadow DOM |
For my coworkers who wonder why I am ok with waiting for the native handlers, the virtual-dom used by vue and react is faster:
... and I happen to love what I can do with CSS. :) |
@grigoryvp While Vue core doesn't include adding Shadow DOM polyfills (it requires more code and reduced performance while browsers don't support it), you still have options:
|
Thank you again. As I explained before, maybe "leak" was a very bad word to describe the issue. As I illustrated in the screenshot, the problem is in the "data-v-b37e7c0a" HTML attribute that vue-loader adds in order for "root" class to be applied not only to the component where it's scoped, but also to the root element of a child component. This is a vue-loader feature described in the documentation: I propose to make an optional opt-out for this feature. I'm really sorry for that "leak" word and for spending your time. |
Well, I think I understand the point which @grigoryvp states. In other words, when you're using scoped styles in that way, in order to understand styling of component it's not enough to look inside component template, since some classes with exactly same names may be pulled to root node from parent component. This makes things more complex when developing many components separately by big teams |
Thank you @xanf and @grigoryvp for the clarification. I apologize for missing the obvious that you pointed out in the image. I missed that Vue had added the parent scoping data attributes onto the child component root element (so that the parent can control layout). If I understand correctly: What's expected:Adding the "scope" attribute simplifies development of component styling by automatically reducing CSS specificity required, meaning I can use whatever CSS specifier I want, and style conflicts between components will not happen. Problem:Style conficts might happen regardless of scoping, and I really can't use whatever CSS specifier I want without conflict. The documentation is misleading by comparing the scoping to encapsulation done with the Shadow DOM like Polymer. We have a current workaround:Understand the caveats, update the documentation (removing Shadow DOM similarity; including with how Cascading inheritance applies to rendered DOM). Current known caveats when using
|
Looks good to me! Just a note for @grigoryvp and other's interested in that topic: while it is possible to make vue-loader not to add specific data attribute if at this point child component will be inserted, that will prevent some typical and useful scenarios, when you have some styling applied via css classes in parent component, and some - in child on the same node. It's definitely a frequently used case |
Before we recommend a patch or solution to be implemented, I think a discussion should be had regarding methods to remove the data-scope from the child root element. I predict problems if we recommend and/or implement something that doesn't follow a pattern. Already, the additional option will continue to have the same 'this is not obvious functionality' problem when trying to determine why scoping was/wasn't followed. Points to discuss:
|
Just had a heated deabed with our front end team lead. It seems that simplest way to "opt-out" from this feature is to add an empty root div, which is very easy. And feature itself seems to be usefull since it allows to modify non-cascading styles for root element like "margin", "border-radius" etc. So I humbly propose following improvement into docks, we can replace "With scoped, the parent component's styles will not leak into child components. However, a child component's root node will be affected by both the parent's scoped CSS and the child's scoped CSS. This is by design so that the the parent can style the child root element for layout purposes." with something like: "With scoped, the parent component's styles will not leak into child components. However, a child component's root node can use classes from both parent's scoped CSS and it's own scoped CSS. This feature allows parent component to change child component's root node non-cascading styles like "margin" or "border-radius". To disable parent component's class names visibility for child component's root node just add a "div" root node without classes to the child component" If it's acceptable I will do an English proof read and submit a patch |
@grigoryvp That is a simple workaround, another is to avoid using the same classnames on the root node. I also think this topic should be revisited when Shadow DOM support is going to be added to Vue core if it makes sense. |
@grigoryvp @xanf @LinusBorg I think the docs needed a bit more to help fix the confusion, so I also took a crack at updating the documentation for Scoped CSS. Let me know what you think (forked version here): https://github.com/johnathankent/vue-loader/blob/53a9f3ab52d63aabe9304a61b89f99392fac620c/docs/en/features/scoped-css.md |
Thanks, @johnathankent But I can't see any info that this feature can be disabled by adding empty parent 'div' element. Did I miss something or such info is not important? Also, offtopic, you wrote that another workaround is to avoid using the same classname on different components. But how is it possible for medium to big sized projects with hundreds or thousands of components? Human focus can handle only 7 plus-minus 2 items at a time :( Or if different developers are creating components - how can they check that root element's classes are unique? |
@grigoryvp I feel your pain. Communicating front-end CSS and how it works is hard. The documentation as it currently stands kinda says 'leaks' and I think that has caused some added confusion, so that's why we need to help fix it. Cascading inheritance is not something that can be stopped. Styles cascade (not 'leak'), and we cannot disable cascading as a 'feature'. Cascading styles are browser functionality and not Vue.js features. Without Shadow DOM custom elements, disabling and leak fixes just ain't gonna happen. Vue components help "encapsulate" styles, but all that means is it's adding specificity so style cascades stay local to itself and all it's rendered children.
I thought that's what you meant in your earlier comment:
I use a pattern where external parent components or stylesheets handle layout using the root node in Vue Components, so I avoid setting styles on it. I think that's what the documentation meant to convey in the first place. And even with wrapper divs, cascading styles are not going to be removed or prevented. Used as a simple pattern might lessen conflicts, but it's still no guarantee that cascading inheritance will not conflict with styles from a Vue child component.
Yeah, I wasn't clear there. I was thinking of team project styleguides. In large teams or project cases as you point out, documenting or syncing classnames is not simple. In that case, a pattern is perfect use case. I added a couple pattern and tried to clarify a bit more. Let me know what you think: Edit: I added a couple changes and realized that all the "Keep in mind" section was technically styleguides. So I propose that we could move the styleguide to a separate page and reference it early - in order to keep it clean and simple. Updated link here: https://github.com/johnathankent/vue-loader/blob/111bc28c3eaa478ec1b0cdf5943dd7d2545b8ea9/docs/en/features/scoped-css.md |
More updates! I made clarified the headers a bit, and created a troubleshooting section to the styleguide. See: https://github.com/johnathankent/vue-loader/blob/f0c830e6839ab2e9ce04891421ffd1b846c3fde8/docs/en/features/scoped-css.md |
Lots of text, but it explains the point about child root nodes and how they are styled by parents. Thanks a lot for the hard work and sorry for all the troubles! |
@grigoryvp Ain't no trouble. Happy to help. I also apologize for not understanding the real problem at first, too. Anything else we could do to make the doc easier or cleaner before we submit it? |
We've almost got a solution! As soon as vuejs/vue#7385 is fixed (if it isn't already and I missed it) then we can easily use SkateJS to make Custom Elements written as Vue single-file-components that can be composed by nesting them, and have shadow roots to encapsulate inner parts. This would keep styles from cascading to child components! At the moment, vue-custom-element does not support nesting of the generated custom elements, while using SkateJS with a Vue Here's how you generate custom elements from Vue components using SkateJS (it works, except for the above mentioned styling issue): skatejs/skatejs#1289 (comment) Note the The content in the shadow root is generated by the Vue component! So you can use that Output looks like the following, and notice the inspector shows both elements nested: So, once we figure out how to get styles placed inside of shadow roots, then we'll be good to go!! 😉 |
Okay, admittedly, this doesn't wire up the attributes to the component props, yet... |
Alright you all, I published my solution. Styles work and won't leak into sub-roots: https://github.com/trusktr/vue-web-component There's some listed caveats, but planning to improve this. |
+1 for being able to turn this feature off. |
+1 for being able to turn this feature off |
Looks like the same problem then :( |
Tried to find out where exactly this happens, hoping it would be a simple enough task to send in a PR for it... and it seems like this happens in Vue's core https://github.com/vuejs/vue/blob/dev/src/core/vdom/patch.js#L337 Already feel like I'm way over my head. Am I right in that a PR for a |
Hey, I really need to use something like { leakyScopedCSS: false}
Is there an option to make a OS platform independent data-v-id in vue-loader? For reference, here is my karma config snippet.
|
the latest version fixed my issue above. I am aware of what fixed it.
Thanks vue.js team! |
@johnathankent: I'm investigating using Vue for a large project at my workplace and was extremely discouraged to uncover this bug by accident after following the simple examples on https://vuejs.org/v2/guide/single-file-components.html It would be great if you could get your updated docs merged, but probably more importantly, the examples on the introductory page linked above desperately need to be changed because they encourage writing SFCs in a way that makes unexpected CSS leakage extremely likely. |
I've built a simple Vue directive that explicitly stops propagation of Example usage: <template>
<div>
<!-- This h1 is styled -->
<h1>Welcome</h1>
<main v-remove-scope>
<!-- This h1 is not styled -->
<h1>Willkommen!</h1>
<!-- Neither are slot contents -->
<slot />
</main>
</div>
</template>
<style scoped>
h1 { color: red }
</style> It recursively updates UPDATE this doesn't seem to work well with multiple nested slots. I switched to using simple Vue.component("wrap", {
render(h) {
return h("div", this.$slots.default)
},
}) |
Any update on this? Today I run into the same issue. I had a class called I hoped that scoped css removes the need for verbose names like BEM etc. |
Yes, this is pretty annoying. And although in some cases you can use non-colliding naming as suggested, it is a too easy to run into the same issue multiple times. <div>
<!-- Parent button -->
<button type="button" class="btn btn-primary">
Next
</button>
<Toolbox>
<div slot="buttons">
<!-- Child button -->
<button type="button" class="btn btn-danger">
Delete
</button>
</div>
</Toolbox>
</div> In this case, styling |
Talk about a deal breaker. This methodology of uncontrolled cascading directly conflicts with the idea of modularization. When a project has more than fifty components with various levels of nesting, how does one expect to keep track of unique CSS class names per component? I may as well go back to 2002 and just create one gigantic CSS file where I can better manage and track unique class names. The point of modularization is reuse. Unfortunately, with this uncontrolled cascading, importing a component somewhere will most likely lead to unexpected conflicts. It is clear that the creators of this framework and maintainers like @johnathankent are holding on to the ideas of the 2000s (i.e. long and unmaintainable cascading sheets) while also trying to incorporate ideas of today. I am also disappointed that this thread has led to no real solutions and surprised that it is still open. I think it's time to go back to Angular and React, where controlled cascading and modularization actually work. |
+1 Piece of garbage, pointless feature |
+1 for being able to turn this feature off |
But temporary I found this solution: Test.vue: <template>
<div>
<div class="test">
<TestNested/>
</div>
</div>
</template>
<style lang="sass" scoped>
.test
width: 200px
height: 200px
background: red
</style> Nested.vue: <template>
<div>
<div class="test">
</div>
</div>
</template>
<style lang="sass" scoped>
.test
width: 100px
height: 100px
background: orange
</style> So, you wrap every component with empty div that gets This way the inner class |
@vostokwork This approach works fine until you want to maintain the chain of nested For instance, consider <script>
export default {
computed: {
component () { return x ? Page1 : Page2 }
}
}
</script>
<template>
<div>
<div class="layout">
<component :is="component" />
</div>
</div>
</template>
<style scoped>
.layout { display: flex; flex-direction: column; }
</style> and <template>
<div>
<div class="page1">Page 1</div>
</div>
</template>
<style scoped>
.page1 { display: flex; flex-direction: column; } // not working as expected
</style> The flex chain breaks, which affects e.g. nested scrolling in vertically constrained elements. My current approach is to dedicate a special class prefix for top-level component elements, such as Also, both these approaches suffer from an another problem: style leak when a parent styles the nested component. Consider <template>
<div>
<div class="parent">
<Child class="child" />
</div>
</div>
</template>
<style scoped>
.child {
border: 1px solid red;
}
</style> and <template>
<div>
<header>Child Header</header>
<div class="child">Child Content</div>
</div>
</template>
<style scoped>
.child {
color: blue;
}
</style> Result: The child header becomes blue, even though it's not referenced by either |
This got me recently. Good to see I wasn't the only one. I don't remember seeing this in the vue docs. Is there a section that discusses this in there that I glazed over? |
After a bit of digging I did find it in loader docs. https://vue-loader.vuejs.org/guide/scoped-css.html#mixing-local-and-global-styles. I wonder if there should be a shoutout to it somewhere in the essentials. |
+1 for being able to turn this feature off Maybe with the following simple syntax |
+1 for being able to turn this feature off |
I can't believe this is still open! As an experienced React developer, I'd been having a great time learning Vue until I ran into this glaring flaw. Such an obvious and fundamental issue sticking around for almost 5 years leaves me concerned... what other fundamental problems could be lurking around? Has anyone found a workaround/naming convention that:
|
Here is yet another demonstration of this problem, based on how I ran into it. https://codesandbox.io/s/vue-scoped-style-problem-ibpskr
<template>
<div class="icon">icon</div>
</template>
<style scoped>
.icon {
color: blue;
}
</style>
<template>
<Icon class="icon" />
<Icon />
</template>
<style scoped>
.icon {
color: red;
}
</style> Top text should be red, bottom should be blue. Instead they are both red. Critically: If you change the I feel that this is a very insidious problem that can easily go unnoticed for a long time until it causes a very hard to track down bug. Most importantly, I don't see this edge case documented anywhere. Other than having an option to turn this "leaking" completely off for a whole component or the whole app (which I admittedly wouldn't want either), I can't think of any good solution to this that wouldn't make things more complex and unexpectedly break things. It's just an inherent flaw with how it works. As Linus said:
As such, css modules might be more fool proof. This seems to be closer to how other frameworks like React tend to do scoped css, with hashed class names like Like others have said, wrapping every component in a |
Unless I'm mistaken, with css modules there's no way you can style child component's inner elements from within the parent component. The |
Yes you're right. If you're trying to select an element in a child component that's deeper than the root element, and you're trying to do it with classes, that's not possible with css modules. You could still do it with generic selectors, like Luckily it's very rare in my code base that I need the Another possibility might be using <template>
<div :class="'gallery ' + $style.gallery">
<slot />
</div>
</template>
<style lang="scss" module>
.gallery {
display: grid;
}
</style>
<style lang="scss" scoped>
.gallery > .some-child {
margin: 0 !important;
}
</style> It looks like CSS modules have been considering a way to import hashed class names for reasons like this, but it doesn't seem like vue-loader supports this: |
+1 for being able to turn this "feature" off... where by "feature" I mean a complete encapsulation break that defies the very point of having scoped styles. If you really want to style children, you have the deep selector for that. But generally you shouldn't. If you want to create e.g. a flexbox-based layout, where you need to style both the container and the items, then you shouldn't mix the responsibilities of layout item & whatever the child is anyway. So you're better off doing something like this (but not possible now due to the leak): <!-- parent: Layout -->
<template>
<div class="root">
<div class="item">
<Card />
</div>
<div class="item">
<Card />
</div>
</div>
</template>
<!-- child: Card -->
<template>
<div class="root">
...
</div>
</template> This is a structure that actually helps separation of concens & style decoupling, allowing you e.g. to use padding for gutters without preventing the children from having inner paddings of their own. In some cases this separation may be redundant, but you only do it when you need it. Contrast that with the earlier proposed hack where you wrap every component in a dummy <!-- parent: Layout -->
<template>
<div>
<div class="root">
<Card />
<Card />
</div>
</div>
</template>
<!-- child: Card -->
<template>
<div>
<div class="root">
...
</div>
</div>
</template> |
This is clearly an issue with the current way we work, separating everything into components, it is obvious that the style has to be isolated only in the component and not leak to children, unless I myself decide that this should happen. CSS modules do this perfectly and have not received an update for years. The only problem with css modules is bad writing. |
Turning this feature off means a lot of "dirty" work and will add unneeded complexity to the framework |
@altrusl In the example by @jacekwilczynski just 3 messages above, if the parent component has a rule: |
First of all, I'm not complaining. There is no reason to get upset about this "issue" after everything VueJS has given us. I just want to share my not so great experience I had with this feature. If you use After spending some (a lot) of time trying to figure out what is happening, my best guess is that Vue tries to pass the My current workaround is add a ghost <template>
<TooltipRoot>
<TooltipContent>...<TooltipContent/>
</TooltipRoot>
<span class="sad-span-without-reason-to-exist" />
<template/> |
What problem does this feature solve?
Right now "scoped" is not really scope: styles from components leak to child components (this is by design, "A child component's root node will be affected by both the parent's scoped CSS and the child's scoped CSS."). Is it possible to make such feature optional? In practice, I often define simple class names like "root" for flex-placing children and it always leak to child components, wreaking hawok to ones that are unfortunate enough to use same short names.
Right now the workaround is to use "module" syntax, but it adds lots of unneded text, especially if compilers like PUG are used for CSS.
Making vuejs config option like "dontLeakScopedModules" with default to "false" will be a bless! I can create a patch, if it's an acceptable feature and i'm not getting anything of this wrong.
What does the proposed API look like?
{
test: /.vue$/,
loader: 'vue-loader',
options: {
dontLeakScopedModules: true
The text was updated successfully, but these errors were encountered: