1. Style pollution

Consider this scenario. If the same.h1 class is defined in the style of component A and component B, then the packaged CSS file will have two identical.h1 classes. When the page displays component A or component B, the style will be affected by the two defined.h1 classes, resulting in style confusion. To better illustrate, I wrote two components, A and B, with the following code:

  1. Component A
<template>
  <div class="compA">
    <h1 class="h1">A Hello</h1>
    <B></B>
  </div>
</template>

<script>
import B from "./B";
export default { 
  components: {
    B
  }
};
</script>

<style>
.h1 {
  margin: 40px 0 0;
  color: red;
}
</style>
Copy the code
  1. The component B
<template>
  <div class="compB">
    <h1 class="h1">B</h1>
    <h1 class="h1">World</h1>
  </div>
</template>

<script>
export default {};
</script>

<style>
.h1 {
  margin: 40px 0 0;
  color: green;
}
</style>

Copy the code

When the page is opened, the color of the three H1’s is red.

In component B. H1 defines color: green; It’s covered. Open the browser console and you can see that the.h1 below overwrites the color defined by the.h1 above.

2. Use scoped

  1. A component
<template>
  <div class="compA">
    <h1 class="h1">A Hello</h1>
    <B></B>
  </div>
</template>

<script>
import B from "./B";
export default { 
  components: {
    B
  }
};
</script>

<style scoped>
.h1 {
  margin: 40px 0 0;
  color: red;
}
</style>
Copy the code
  1. B component
<template>
  <div class="compB">
    <h1 class="h1">B</h1>
    <h1 class="h1">World</h1>
  </div>
</template>

<script>
export default {};
</script>

<style scoped>
.h1 {
  margin: 40px 0 0;
  color: green;
}
</style>
Copy the code

When the page opens, it looks like this:

That’s what we’re looking for. When I open the console, I see the following two changes:

  1. There are more CSS selectors on the packagedata-v-XXXXXX

  1. I have a lot of DOM elementsdata-v-XXXXXX

From this, we can probably guess that the style tag is scoped and compiled CSS and DOM elements in the page are appended with data-V-xxxxxx. So the browser can tell the difference between.h1 for component A and.h1 for component B.

This is using scoped to solve style contamination problems. That’s not my goal, of course, but as the title says, I want to try to clarify what scoped is and how it works. From the following questions as a breakthrough point.

  1. What is scopeId? How is scopeId generated?
  2. The SFCtemplateWe have not defineddata-vSo why are there more dom elements on the pagedata-v-XXXXXXWhat about this property?
  3. The SFCstyleThe selector we wrote indata-v-XXXXXXSo how does that add up?

2. What is scopeId? How is scopeId generated?

Each SFC component has its own unique scopeId, similar to the scopeId in data-v-xxxxxx. As we all know, vUE single file components are processed by vue-loader. Since the scopeId of each vue file is unique, it is easy to wonder if the scopeId was generated by vue-loader. Vue-loader does have this logic.

  const id = hash(
    isProduction
      ? (shortFilePath + '\n' + source.replace(/\r\n/g.'\n'))
      : shortFilePath
  )
Copy the code

The code above is the code that generates the scopeId execution.

3. In SFCtemplateWe have not defineddata-vSo why are there more dom elements on the pagedata-v-XXXXXXWhat about this property?

Have you taken a closer look at what the exported object looks like?

import B from "./B";
console.log('component B', B)
Copy the code

Printed results:

The exported component configuration will have a _scopeId attribute. The attribute value data-V-5277DF62 is not defined by us.

Is this the same value as the scopeId on the DOM element inside component B in the DOM screenshot above?

It is clear that the SFC component just adds the _scopeId attribute to the component configuration object at compile time. The value of the attribute is not written randomly, but is generated by vue-loader. The generation logic was covered above.

So when is scope added to the DOM element? There is a code in vue2’s patch logic inside createElm in SRC /core/vdom/patch.js. The general logic is to call setScope to append the scopeId to the DOM element after creating the actual DOM inside createElm.

  vnode.elm = vnode.ns
    ? nodeOps.createElementNS(vnode.ns, tag)
    : nodeOps.createElement(tag, vnode)
  setScope(vnode)
Copy the code

The logic of setScope is as follows:

  function setScope (vnode) {
    let i
    if (isDef(i = vnode.fnScopeId)) {
      nodeOps.setStyleScope(vnode.elm, i)
    } else {
      let ancestor = vnode
      while (ancestor) {
        if (isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)) {
          nodeOps.setStyleScope(vnode.elm, i)
        }
        ancestor = ancestor.parent
      }
    }
    // for slot content they should also get the scopeId from the host instance.
    if(isDef(i = activeInstance) && i ! == vnode.context && i ! == vnode.fnContext && isDef(i = i.$options._scopeId) ) { nodeOps.setStyleScope(vnode.elm, i) } }Copy the code

Data-v-xxxxxx on the DOM element is added during patch run time.

4. In SFCstyleThe selector we wrote indata-v-XXXXXXSo how does that add up?

All this is done by a loader called stylePostLoader.

  1. StylePostLoader handles CSS code

  1. CompileStyle is used internallypostCssTransform the CSS code. You can see it in use in the screenshot belowscopedWill use ascoped_1The plug-in.

  1. To viewscoped_1Plug-in code, found such a code.
 selector.insertAfter(node, selectorParser.attribute({
    attribute: id
 }));
Copy the code

Data-v-xxxxxx is added to the CSS code using stylePostLoader.

5, /deep/?

ScopedId is appended to the last level of the selector by default. For example, the following example has components A and B. Component B is used in component A, when modifying the interior of component B in component A. El-autocomplete style:

.compA .el-autocomplete {
  width: 280px;
}

/* Compiled code */
.compA .el-autocomplete[data-v-19388c91] {
  width: 280px;
}
Copy the code

Use/deep/after

.compA /deep/.el-autocomplete {
  width: 280px;
}

/* Compiled code */
.compA[data-v-19388c91] .el-autocomplete {
  width: 280px;
}
Copy the code

By comparing the following DOM structure diagram, it is clear why deep works.

  • use/deep/We wanted to change it earlierThe component BWith internal.el-autocompleteThe style of the element and the selector used after packaging is.compA .el-autocomplete[data-v-19388c91]Obviously with.el-autocompleteDoes not exist on the elementdata-v-19388c91Property, so styles don’t work.
  • use/deep/After? The packaged CSS looks like this.compA[data-v-19388c91] .el-autocompleteThat will bescopedIdMoved to.compAAt this point the style is in effect.

conclusion

  1. When styling the root node of a child component within a parent component, you do not add it/deep/Because there are both parent and child components on the root node of the child componentscopeId.
  2. This is required when styling non-root nodes of child components within the parent/deep/Because the last level of the parent component definition selector is used by the parent componentscopeIdThe element that needs to be selected in the child component is used by the child componentscopeId, so it won’t match, and the style won’t work.