Vue 的 Scoped CSS

前言

網路上講 Vue Scoped CSS 文章實在是很多,大都是講穿透兩個字就帶過去

其實觀察 vue-loaderwebpack compile 出來的檔案規則,蠻好懂的

很容易就舉一反三

原理

SFC裡面,vue-loader 會從該 component最外層的 div 開始

每一個元素都加上類似這樣的屬性data-v-7a7a37b1

然後在 css 樣式加上[data-v-7a7a37b1],把樣式在那個component裡面

7a7a37b1根據vue-loader 源碼,是透過以下的 function 產生的

檔案路徑還有檔案名稱有關,並不是真的隨機

import * as crypto from 'crypto'

// module id for scoped CSS & hot-reload
const rawShortFilePath = path.relative(rootContext || process.cwd(), filename).replace(/^(\.\.[\/\\])+/, '')
const shortFilePath = rawShortFilePath.replace(/\\/g, '/')
const id = hash(isProduction ? shortFilePath + '\n' + source.replace(/\r\n/g, '\n') : shortFilePath)

function hash(text: string): string {
    return crypto.createHash('sha256').update(text).digest('hex').substring(0, 8)
}

編譯後的的 CSS 跟 html,看下面的代碼會比較好懂

代碼

例如以下的代碼

<template>
    <div>
      <div class="container1">
        <div class="container2">
          <el-input />
        </div>
      </div>
    </div>
</template>

<style scoped lang="scss">
.container1 {
  .container2 {
    .el-input {
      background-color: red;
    }
  }
}
</style>

經過 webpack 會 compile 成如下圖

scoped css

屬性selector會被掛在最裡面class,也就是.el-input那邊

下面的el-input__wrapper是後來 element-ui 用 JS 去產生的

不會被 vue-loader 加上data-v-7a7a37b1,這一點很重要

因為通常都是想改套件用 JS 產出來的元件的樣式

:deep()

在 css 的選擇器用:deep()包起來,就可以控制[data-v-7a7a37b1]這個屬性選擇器顯示在哪裡

我們把.container2:deep()包起來

這告訴vue-loader,屬性選擇器要掛在.container2的上一層,也就是.container1

.container1 {
  :deep(.container2) {
    .el-input {
      background-color: red;
    }
  }
}

編譯出來的結果如下

:deep

那假如把:deep()掛在.container1會變成怎樣

就再往外一層,如下圖

:deep

了解 scoped css 產生的 attribute 掛的位置機制,問題就很容易分析理解了