Description
What problem does this feature solve?
Disclaimer: I could not create this issue via the Vue.js issue helper and therefore unfortunately could not auto-attach the
feature request
label. Got a414
error from the GitHub servers, apparently this proposal is just too long to fit into a URL. 🙃
For Clarification: Whenever this proposal mentions parent/child components, it's not about nesting (as in
vm.$parent
/vm.$children
) but about the relation between two components where oneextends
the other.
Status Quo
As described in this forum thread, there currently seems to be a lack of an easy way to incrementally add scoped styles to .vue
child components. This makes creating reusable components harder than it needs to be.
Regarding styles, using extends
currently is an all-or-nothing approach:
-
When no
<style>
block is present on a child component, it gets the samedata-v-*
attribute as its parent and thus inherits all of its styling.By example:
Parent Component Source
<template> <button class="button">Click me!</button> </template> <style scoped> .button { border: 1px solid red; } </style>
Child Component Source
<script> import Parent from 'parent' export default { extends: Parent } </script>
Pseudo-Compiled Output
<!-- Rendered Parent Component --> <!-- has a red border --> <button class="button" data-v-parent>Click me!</button> <!-- Rendered Child Component --> <!-- has a red border --> <button class="button" data-v-parent>Click me!</button> <style> .button[data-v-parent] { border: 1px solid red; } </style>
-
When a scoped
<style>
block is added to the child, it gets its owndata-v-*
attribute and parent styles are no longer applied to it at all.By example:
Parent Component Source
The same as before.
<template> <button class="button">Click me!</button> </template> <style scoped> .button { border: 1px solid red; } </style>
Child Component Source
<script> import Parent from 'parent' export default { extends: Parent } </script> <style scoped> .button { color: blue; } </style>
Pseudo-Compiled Output
<!-- Rendered Parent Component --> <!-- has a red border --> <button class="button" data-v-parent>Click me!</button> <!-- Rendered Child Component --> <!-- has blue text but no red border --> <button class="button" data-v-child>Click me!</button> <style> .button[data-v-parent] { border: 1px solid red; } </style> <style> .button[data-v-child] { color: blue; } </style>
What does the proposed API look like?
I'd like to propose a new option for Vue component definitions. The name is debateable, but for now let's agree on calling it extendsScopedStyle
.
The option
- is a boolean flag, defaulting to
false
- is only available in Single File Components
- does only apply if the component
extend
s another Single File Component
Setting extendsScopedStyle
to true
on a child component definition will cause its relevant DOM nodes to inherit the parent's data-v-*
attribute as well as receiving its own custom data-v-*
attribute. This will apply both, parent and child styles, to the child component.
This also is best explained by example. The following code reflects the proposed behaviour with a custom <style>
on the child component. The child does receive the parent's styling as well as its own styling.
Parent Component Source
Still the same as before.
<template>
<button class="button">Click me!</button>
</template>
<style scoped>
.button {
border: 1px solid red;
}
</style>
Child Component Source
<script>
import Parent from 'parent'
export default {
extends: Parent,
extendsScopedStyle: true
}
</script>
<style scoped>
.button {
color: blue;
}
</style>
Pseudo-Compiled Output
<!-- Rendered Parent Component -->
<!-- has a red border -->
<button class="button" data-v-parent>Click me!</button>
<!-- Rendered Child Component -->
<!-- has blue text AND a red border -->
<button class="button" data-v-parent data-v-child>Click me!</button>
<style>
.button[data-v-parent] {
border: 1px solid red;
}
</style>
<style>
.button[data-v-child] {
color: blue;
}
</style>
Gotchas / Caveats
Specificity
To make sure parent styles are actually overridden by child styles, the order of <style>
blocks in the resulting bundle matters. The child component styles would have to be inserted after the parent styles.
As far as I can tell there shouldn't be any conflicts in determining that order since there shouldn't be any kinds of cyclic dependencies between components. (Am I right here?)
Dependency Chains
While some component A
extends component B
, component B
may extend component C
and thus also be a child component itself.
Therefore, the data-v-*
attribute of C
needs to fall through to B
as well as to A
.
Location of the Flag
To be honest, having a new property on the Vue component object for an approach tied so tightly to Single File Components does not feel great.
However I was encouraged by the fact that
-
it'd not be the first option to not be globally applicable (speaking of
name
being respected everywhere but in Single File Components) -
the alternative ways to signal the described behaviour seem terrible to me:
Regarding the feature's scope, an
extends-scoped-style
attribute on the<template>
block would probably be the best fitting option, since the template is what's actually affected. However, this is not possible because child components may not even have a<template>
block.An
extends-scoped-style
attribute on a<style>
block (which was my first approach for this proposal) does not feel very clean either because the described behaviour actually has nothing to do with a specific<style>
block.Also it's valid to have multiple
<style scoped>
blocks in the same component — some may have that marker attribute, others may not. This would be a weird inconsistency because the feature can only be an on/off thing for the component as a whole.