vuejs / component-compiler-utils

Lower level utilities for compiling Vue single file components
319 stars 75 forks source link

:root Selector should not be scoped #85

Open joe223 opened 4 years ago

joe223 commented 4 years ago

The :root CSS pseudo-class matches the root element of a tree representing the document, it identical to the selector html usually.

But a scoped :root selector dose not match any Element.

With a demonstration which created by @vue/cli 4.0.5:

<!--HelloWorld.vue-->
<style scoped>
:root {
  --font-color: red;
}
h3 {
  color: var(--font-color);
}
</style>

And we got:

<style type="text/css">
[data-v-469af010]:root {
  --font-color: red;
}
h3[data-v-469af010] {
  color: var(--font-color);
}
</style>

h3's font-color wont change, it still inherits from parent element rather than red.

How to resolve?

Scoped :root does not match any element, but a attribute selector with :root do. So, we can get the expected result if changing:

[data-v-469af010]:root { /*balabala*/ }

to

/* proposal A: */
:root { /*balabala*/ }

/* or  */

/* proposal B: */
:root [data-v-469af010] { /*balabala*/ }

I prefer proposal B that will make sure the scoped style rendered as wished. Such as:

<!-- HelloWorld.vue -->
<template>
  <h3>Hello</h3>
</template>

<style scoped>
:root {
  --font-color: red;
}
h3 {
  color: var(--font-color);
}
</style>

<!-- another unscoped style in HelloWorld.vue-->
<style>
:root {
  --font-color: blue;
}
</style>

We will get some css code like this:

<style type="text/css">
:root [data-v-469af010] {
  --font-color: red;
}
h3[data-v-469af010] {
  color: var(--font-color);
}
</style>
<style type="text/css">
:root {
  --font-color: blue;
}
</style>

h3 will rendered with red font color as expected.

I'll create a PR if this issue is logical.

hrobertson commented 3 years ago

After noticing the current behaviour I came here with the intention of fixing this. Happy to see you've already submitted a PR :+1:

I would question whether the :root selector should remain in the compiled CSS. Surely it is superfluous and can be removed leaving just the attribute selector:

<!-- HelloWorld.vue -->
<template>
  <h3>Hello</h3>
</template>

<style scoped>
:root {
  --font-color: red;
}
h3 {
  color: var(--font-color);
}
</style>

<!-- another unscoped style in HelloWorld.vue-->
<style>
:root {
  --font-color: blue;
}
</style>

Would compile to:

<style type="text/css">
[data-v-469af010] {
  --font-color: red;
}
h3[data-v-469af010] {
  color: var(--font-color);
}
</style>
<style type="text/css">
:root {
  --font-color: blue;
}
</style>

Additionally, I'd like to pre-empt a potential objection to this change which might be something like "You should just use * or ::v-deep instead of :root." Consider a .scss file from a 3rd party library that uses the :root selector which I want to @import within a scoped style block in my component(s). Example:

// styles.scss
:root {
  --font-color: red;
}
<!-- HelloWorld.vue -->
<template>
  <h3>Hello</h3>
</template>

<style scoped>
@import "styles.scss";
h3 {
  color: var(--font-color);
}
</style>

Should compile to:

<style type="text/css">
[data-v-469af010] {
  --font-color: red;
}
h3[data-v-469af010] {
  color: var(--font-color);
}
</style>