vue開發(fā)配置掃盲帖:什么是CSS Modules以及為什么引入CSS Modules现诀?

什么是CSS Modulse夷磕?

在做vue等Node項目時,經(jīng)常會有一項:是否啟用CSS Modules仔沿,如下圖所示:


image

那么坐桩,什么是CSS Modules呢?使用它有什么意義呢封锉?

CSS Modules,直接強譯撕攒,就是css模塊的意思。

當(dāng)web研發(fā)越來越多時烘浦,為了應(yīng)對CSS的凌亂,于是人們想到了用這種方法萍鲸。
比如有人在home.vue中闷叉,直接在文件中行內(nèi)寫了一行代碼

    <style>
    .btn{font-size:1rem;}
    </style>

接下來,這位作者脊阴,又在about.vue中握侧,同樣在文件中行內(nèi)寫了這么一個相同的css定義:

    <style>
    .btn{font-size:0.4rem;}
    </style>

那么,這兩個代碼最后編譯會不會沖突嘿期?有沒有辦法規(guī)避品擎?
需要知道,vue這樣的框架在誕生备徐,背后很大目的就是為了解決中后臺開發(fā)者不熟悉css甚至是切圖這些而生萄传,CSS Modules則能規(guī)避這個問題,他會將兩個.btn類分別生成.home_btn.about_btn蜜猾,從而規(guī)避秀菱。
當(dāng)然振诬,背后的邏輯會比這個復(fù)雜得多,我們慢慢展開講解衍菱。

為什么引入CSS Modeules

或者可以這么說赶么,CSS Modules為我們解決了什么痛點。針對以往我寫網(wǎng)頁樣式的經(jīng)驗脊串,具體來說可以歸納為以下幾點:

全局樣式?jīng)_突

過程是這樣的:你現(xiàn)在有兩個模塊辫呻,分別為A、B,你可能會單獨針對這兩個模塊編寫自己的樣式琼锋,例如a.css放闺、b.css,看一下代碼

// A.js
import './a.css'
const html = '<h1 class="text">module A</h1>'

// B.js
import './b.css'
const html = '<h1 class="text">module B</h1>'

下面是樣式:

/* a.css */
.text {
    color: red;
}

/* b.css */
.text {
    color: blue;
}

導(dǎo)入到入口APP中

// App.js
import A from './A.js'
import B from './B.js'

element.innerTHML = 'xxx'

由于樣式是統(tǒng)一加載到入口中斩例,因此實際上的樣式合在一起(這里暫定為mix.css)顯示順序為:

/* mix.css */

/* a.css */
.text {
    color: red;
}

/* b.css */
.text {
    color: blue;
}

根據(jù)CSS的Layout規(guī)則雄人,因此后面的樣式會覆蓋掉前面的樣式聲明,最終有效的就是text的顏色為blue的那條規(guī)則念赶,這就是全局樣式覆蓋础钠,同理,這在js中也同樣存在叉谜,因此就引入了模塊化旗吁,在js中可以用立即執(zhí)行函數(shù)表達式來隔離出不同的模塊

var moduleA = (function(document, undefined){
    // your module code
})(document)

var moduleB = (function(document, undefined){
    // your module code
})(document)

而在css中要想引入模塊化,那么就只能通過namespace來實現(xiàn)停局,而這個又會帶來新的問題很钓,這個下面會講到。

嵌套層次過深的選擇器

為了解決全局樣式的沖突問題董栽,就不得不引入一些特地命名namespac來區(qū)分scope码倦,但是往往有些namespace命名得不夠清晰,就會造成要想下一個樣式不會覆蓋锭碳,就要再加一個新的namespace來進行區(qū)分袁稽,最終可能一個元素最終的顯示樣式類似如以下:

.widget .table .row .cell .content .header .title {
  padding: 10px 20px;
  font-weight: bold;
  font-size: 2rem;
}

在上一個元素的顯示上使用了7個選擇器,總結(jié)起來會有以下問題:

  • 根據(jù)CSS選擇器的解析規(guī)則可以知道擒抛,層級越深推汽,比較的次數(shù)也就越多。當(dāng)然在更多的情況下歧沪,可能嵌套的層次還會更深歹撒,另外,這里單單用了類選擇器诊胞,而采用類選擇器的時候暖夭,可能對整個網(wǎng)頁的渲染影響更重。
  • 增加了不必要的字節(jié)開銷
  • 語義混亂,當(dāng)文檔中出現(xiàn)過多的content鳞尔、title以及item這些通用的類名時嬉橙,你可能要花上老半天才知道它們到底是用在哪個元素上
  • 可擴展性不好,約束越多寥假,擴展性越差
    【注】CSS的渲染規(guī)則可以參看這篇文章探究 CSS 解析原理 https://juejin.im/entry/5a123c55f265da432240cc90

會帶來代碼的冗余

由于CSS不能使用類似于js的模塊化的功能市框,可能你在一個css文件中寫了一個公共的樣式類,而你在另外一個css也需要這樣一個樣式糕韧,這時候枫振,你可能會多寫一次,類似于這樣的

/* a.css */

.modal {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1;
    background-color: rgba(0, 0, 0, 0.7);
}
.text {
    color: red;
}

/* b.css */
.modal {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1;
    background-color: rgba(0, 0, 0, 0.7);
}
.text {
    color: blue;
}

那么在合并成app.css的時候萤彩,就會被編寫兩遍粪滤,雖然樣式不會被影響,但是這樣實際上也是一種字節(jié)浪費雀扶,當(dāng)然杖小,上述的這種情況完全是可以通過公用全局樣式來達到目的,但是愚墓,這種代碼重復(fù)通常是在不知情的情況下發(fā)生的予权。

一些解決方案

針對上述的一些問題,也有一些解決方案浪册,具體如下:

CSS預(yù)處理器(Sass/Less等),Sass,Less的用法這里不再贅述扫腺,如果不清楚,可以自己查閱相關(guān)資料去了解一下村象,其中sass的使用文檔在這里可以看到http://code.z01.com/sass

CSS預(yù)處理器最大的好處就是可以支持模塊引入笆环,用js的方式來編寫CSS,解決了部分scope混亂以及代碼冗余的問題厚者,但是也不能完全避免躁劣。同時,也沒有解決全局樣式的沖突問題

一個SASS的的文件是這樣的:

/* app.sass */

@import './reset'
@import './color'
@import './font'

可以實際上編譯之后库菲,終究還是一個文件习绢,因此不可避免的會出現(xiàn)沖突樣式

BEM(Block Element Modifier)

There are only two hard problems in Computer Science: cache invalidation and naming things — Phil Karlton

BEM就是為了解決命名沖突以及更好的語義化而生的。

BEM名詞解釋
Block:邏輯和頁面功能都獨立的頁面組件蝙昙,是一個可復(fù)用單元,特點如下:

  • 可以隨意嵌套組合
  • 可以放在任意頁面的任何位置梧却,不影響功能和外觀
  • 可復(fù)用奇颠,界面可以有任意多個相同Block的實例
  • Element:Block的組成部分,依賴Block存在(出了Block就不能用)
  • [可選]定義Block和Element的外觀及行為放航,就像HTML屬性一樣烈拒,能讓同一種Block看起來不一樣

** 命名規(guī)則 **

Block作為最小的可復(fù)用單元,任意嵌套不會影響功能和外觀,命名可以為header荆几、menu等等

<style>
    .header { color: #042; }
</style>

<div class="header">...</div>

Element依附Block存在吓妆,沒有單獨的含義,命名上語義盡量接近于Block吨铸,比如title行拢、item之類

<style>
    .header { color: #042; }
    .header__title { color: #042; }
</style>

<div class="header">
    <h1 class="header__title">Header</h1>
</div>

Modifier是一個元素的狀態(tài)顯示,例如active诞吱、current舟奠、selected`

<style>
    .header--color-black { color: #000; }
    .header__title--color-red { color: #f00; }
</style>

<div class="header header--color-black">
    <h1 class="header__title">
        <span class="header__title--color-red">Header</span>
    </h1>
</div>

【說明】

  • Block完全獨立,可以嵌套房维,一個header是一個Block沼瘫,header下的搜索框也可以是一個Block
  • 不可能出現(xiàn)Block__Element-father__Element-son_Modifer這種類名的寫法,BEM只有三級
  • Modifier可以加在Block和Element上面
  • Modifier作為一個額外的類名加載Block和Element上面咙俩,只是為了改變狀態(tài)耿戚,需要保留原本的class

一個完整的示例

<form class="form form--theme-xmas form--simple">
  <input class="form__input" type="text" />
  <input
    class="form__submit form__submit--disabled"
    type="submit" />
</form>
.form { }
.form--theme-xmas { }
.form--simple { }
.form__input { }
.form__submit { }
.form__submit--disabled { }

參考鏈接:

BEM解決了模塊復(fù)用阿趁、全局命名沖突等問題膜蛔,配合預(yù)處理CSS使用時,也能得到一定程度的擴展歌焦,但是它依然有它的問題:

  • 對于嵌套過深的層次在命名上會給需要語義化體現(xiàn)的元素造成很大的困難
  • 對于多人協(xié)作上飞几,需要統(tǒng)一命名規(guī)范,這同樣也會造成額外的effort

CSS Modules

說了這么多独撇,終于要到正文了

什么是CSS Modules

根據(jù)CSS Modules的repo上的話來說是這樣的:

CSS files in which all class names and animation names are scoped locally by default.

所以CSS Modules并不是一個正式的聲明或者是瀏覽器的一個實現(xiàn)屑墨,而是通過構(gòu)建工具(webpack or Browserify)來使所有的class達到scope的一個過程。

CSS Modules 解決了什么問題

  • 全局命名沖突纷铣,因為CSS Modules只關(guān)心組件本身卵史,只要保證組件本身命名不沖突,就不會有這樣的問題搜立,一個組件被編譯之后的類名可能是這樣的:
/* App.css */
.text {
    color: red;
}

/* 編譯之后可能是這樣的 */
.App__text___3lRY_ {
    color: red;
}

命名唯一以躯,因此保證了全局不會沖突。

  • 模塊化

可以使用composes來引入自身模塊中的樣式以及另一個模塊的樣式:

.serif-font {
  font-family: Georgia, serif;
}

.display {
  composes: serif-font;
  font-size: 30px;
  line-height: 35px;
}

應(yīng)用到元素上可以這樣使用:

import type from "./type.css";

element.innerHTML = 
  `<h1 class="${type.display}">
    This is a heading
  </h1>`;

之后編譯出來的模板可能是這樣的:

<h1 class="Type__display__0980340 Type__serif__404840">
  Heading title
</h1>

從另一個模塊中引入啄踊,可以這樣寫:

.element {
  composes: dark-red from "./colors.css";
  font-size: 30px;
  line-height: 1.2;
}
  • 解決嵌套層次過深的問題

因為CSS Modules只關(guān)注與組件本身忧设,組件本身基本都可以使用扁平的類名來寫,類似于這樣的:

.root {
  composes: box from "shared/styles/layout.css";
  border-style: dotted;
  border-color: green;
}

.text {
  composes: heading from "shared/styles/typography.css";
  font-weight: 200;
  color: green;
}

CSS Modules 怎么用

CSS Modules不局限于你使用哪個前端庫颠通,無論是React址晕、Vue還是Angular,只要你能使用構(gòu)建工具進行編譯打包就可以使用顿锰。

下面我使用webpack為例谨垃,一步一步引入CSS Modules.

構(gòu)建最初始的應(yīng)用

.
├── build
│   └── bundle.js
├── index.html
├── node_modules
├── package-lock.json
├── package.json
├── src
│   ├── index.js
│   └── styles
└── webpack.config.js

index.js作為程序入口启搂,styles文件夾存放樣式文件,配合webpack.config.js作為webpack配置文件刘陶。

// index.js
var html = `<div class="header">
    <h2 class="title">CSS Modules</h2>
</div>`

document.getElementById('container').innerHTML = html;

樣式文件:

/* global.css */
* {
    margin: 0;
    padding: 0;
}

.container {
    padding: 20px;
}

/* index.css */
.header {
    font-size: 32px;
}

.title {
    border-bottom: 1px solid #ccc;
    padding-bottom: 20px;
}

模板文件:

<!-- index.html -->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>css modules</title>
</head>
<body>
    <div id="container" class="container"></div>
    <script src="./build/bundle.js"></script>
</body>
</html>

全局安裝依賴胳赌,配置執(zhí)行腳本:

npm install webpack webpack-cli --save-dev

package.json

"scripts": {
    "build": "npx webpack && open index.html"
}

在控制臺執(zhí)行npm run build, 得到的結(jié)果為:

> css-modules-demo@1.0.0 build /Users/yhhu/Documents/coding/css-modules-demo
> npx webpack && open index.html

Hash: 5810d2ecd760c08cc078
Version: webpack 4.17.1
Time: 78ms
Built at: 2018-08-26 15:09:31
    Asset      Size  Chunks             Chunk Names
bundle.js  3.97 KiB    main  [emitted]  main
Entrypoint main = bundle.js
[./src/index.js] 196 bytes {main} [built]

加入樣式以及l(fā)oaders

package.json中加入能夠處理css的loader

  module: {
    rules: [
      {
        test: /\.js/,
        loader: 'babel-loader',
        include: __dirname + '/src',
        exclude: __dirname + '/src/styles'
        },
      {
        test: /\.css$/,
        use: [
          { loader: 'style-loader' },
          {
            loader: 'css-loader',
            options: {         
            }
          }
        ]
      }
    ]
  }

index.js中引入兩個CSS文件

// index.js
import './styles/global.css'
import './styles/index.css'

const html = `<div class="header">
    <h2 class="title">CSS Modules</h2>
</div>`

document.getElementById('container').innerHTML = html;

編譯之后的執(zhí)行結(jié)果為:

build

在瀏覽器中顯示為:

css-loader

提取公有樣式

可以看到打包之后的build目錄下只有一個bundle.js匙隔,我們現(xiàn)在要把樣式文件提取出來

./build/
└── bundle.js
  • 安裝依賴
npm install --save-dev mini-css-extract-plugin
  • 修改webpack.config.js
var MiniCssExtractPlugin = require("mini-css-extract-plugin");

modules: {
    rules: [
        // {
        //   test: /\.css$/,
        //   use: [
        //      { loader: "style-loader" },
        //     {
        //      loader: "css-loader",
        //      options: {
        
        //      }
        //     }
        //   ]
        // },
        {
            test: /\.css$/,
            use: [
              {
                loader: MiniCssExtractPlugin.loader,
                options: {
                  publicPath: './build/styles'
                }
              },
              { 
                loader: "css-loader",
                options: {
                    
                }
              }
            ]
        }        
    ]
},
plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
      chunkFilename: "[id].css"
    })
],
  • 在模板中引入樣式文件
<!-- index.html -->

<!DOCTYPE html>
<head>
    <link rel="stylesheet" href="./build/main.css">
</head>
<body>
    <div id="container" class="container"></div>
    <script src="./build/bundle.js"></script>
</body>
  • 編譯打包
extract

可以看到有main.css生成

開啟css modules功能

默認在css-loader中是不開啟css modules功能的疑苫,要開啟可以設(shè)置modules: true即可,更多可以參看官方css-loader使用方法修改webpack.config.js牡直,如下:

{
    test: /\.css$/,
    use: [
      {
        loader: MiniCssExtractPlugin.loader,
        options: {
          publicPath: './build/styles'
        }
      },
      { 
        loader: "css-loader",
        options: {
            modules: true
        }
      }
    ]
}        

修改index.js文件中的引用方式:

import './styles/global.css'
import Index from './styles/index.css'

const html = `<div class=${Index.header}>
    <h2 class=${Index.title}>CSS Modules</h2>
</div>`

document.getElementById('container').innerHTML = html;

可以看到缀匕,之前都是直接import一個css文件,而現(xiàn)在改成了導(dǎo)出一個對象的形式碰逸,我們可以把Index對象打印出來乡小,看看具體是些什么東西:

image

直接對應(yīng)我們引用的方式,然后我們再看看生成出來的main.css中具體有哪些東西:

* {
    margin: 0;
    padding: 0;
}

._2BQ9qrIFipNbLIGEytIz5Q {
    padding: 20px;
}
._3Ukt9LHwDhphmidalfey-S {
    font-size: 32px;
}

._3XpLkKvmw0hNfJyl8yU3i4 {
    border-bottom: 1px solid #ccc;
    padding-bottom: 20px;
}

合成一個文件之后饵史,所有的類名都經(jīng)過了哈希轉(zhuǎn)換满钟,因此確保了類名的唯一性,我們再看看瀏覽器中inspector中的樣式應(yīng)用胳喷,如下:

no-transform

事實上湃番,container樣式我們是不需要轉(zhuǎn)換的,因為我是把它固定寫死在了容器上吭露,那我們應(yīng)該怎么做呢吠撮?

全局作用域

要想一個類名不需要被裝換,那么可以使用:global(className)來進行包裝讲竿,這樣的類不會被轉(zhuǎn)換泥兰,會被原樣輸出,下面我們修改global.css

/* global.css */
* {
    margin: 0;
    padding: 0;
}

:global(.container) {
    padding: 20px;
}

我們再來看看main.css

global

就可以發(fā)現(xiàn).container類沒有被轉(zhuǎn)換

定義哈希類名

CSS Modules默認是以[hash:base64]來進行類名轉(zhuǎn)換的题禀,可辨識度不高鞋诗,因此我們需要自定義

開啟自定義,可以使用一個配置參數(shù)localIdentName迈嘹,具體配置如下:

{ 
  loader: "css-loader",
  options: {
    modules: true,
    localIdentName: '[path][name]__[local]--[hash:base64:5]'
  }
}
localIdentName

類名組合

如果我們實現(xiàn)類似于Sass的繼承功能削彬,我們需要怎么做呢?CSS Modules中提供了composes關(guān)鍵字讓我們來繼承另外一個類秀仲,修改index.css如下:

.red {
    color: red;
}

.header {
    font-size: 32px;
}

.title {
    composes: red;
    border-bottom: 1px solid #ccc;
    padding-bottom: 20px;
}

我們增加了一個red的類名融痛,在title中實現(xiàn)繼承,編譯之后的結(jié)果為:

composes-inner

發(fā)現(xiàn)多了一個src-styles-index__red--1ihPk的類名神僵,正是我們上面繼承的那個類

除了在自身模塊中繼承雁刷,我們還可以繼承其他文件中的CSS規(guī)則,具體如下:

我們再styles文件夾下新建一個color.css

/* color.css */
.red {
    color: red;
}

.blue {
    color: blue;
}

然后在index.css文件中導(dǎo)入

/* index.css */
.red {
    color: red;
}

.header {
    font-size: 32px;
}

.title {
    color: green;
    composes: blue from './color.css';
    composes: red;
    border-bottom: 1px solid #ccc;
    padding-bottom: 20px;
}

最終我們會發(fā)現(xiàn)文字的顏色為綠色挑豌,可見自身模塊聲明優(yōu)先級最高安券,如果把自身申明的color去掉,那么自身引入和從其他文件引入的相同申明又該如何顯示呢氓英?

答案是自身引入的聲明的優(yōu)先級會比較高侯勉。

override

總結(jié)

至此,所有的CSS Modules用法就已經(jīng)介紹完畢了铝阐,至于后續(xù)的還有如何應(yīng)用于React址貌、Vue以及Angular中,相信掌握了上面的內(nèi)容之后就可以知道怎么寫了徘键,如何與預(yù)處理器一起使用相信問題也不大练对。

參考鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市吹害,隨后出現(xiàn)的幾起案子螟凭,更是在濱河造成了極大的恐慌,老刑警劉巖它呀,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件螺男,死亡現(xiàn)場離奇詭異,居然都是意外死亡纵穿,警方通過查閱死者的電腦和手機下隧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谓媒,“玉大人淆院,你說我怎么就攤上這事【涔撸” “怎么了土辩?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長宗弯。 經(jīng)常有香客問我脯燃,道長,這世上最難降的妖魔是什么蒙保? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任辕棚,我火速辦了婚禮,結(jié)果婚禮上邓厕,老公的妹妹穿的比我還像新娘逝嚎。我一直安慰自己,他們只是感情好详恼,可當(dāng)我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布补君。 她就那樣靜靜地躺著,像睡著了一般昧互。 火紅的嫁衣襯著肌膚如雪挽铁。 梳的紋絲不亂的頭發(fā)上伟桅,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天,我揣著相機與錄音叽掘,去河邊找鬼楣铁。 笑死,一個胖子當(dāng)著我的面吹牛更扁,可吹牛的內(nèi)容都是我干的盖腕。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼浓镜,長吁一口氣:“原來是場噩夢啊……” “哼溃列!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起膛薛,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤听隐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后相叁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體遵绰,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年增淹,在試婚紗的時候發(fā)現(xiàn)自己被綠了佛舱。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片听系。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡拱层,死狀恐怖骇两,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拳喻,我是刑警寧澤哭当,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站冗澈,受9級特大地震影響钦勘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜亚亲,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一彻采、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧捌归,春花似錦肛响、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至巾兆,卻和暖如春猎物,著一層夾襖步出監(jiān)牢的瞬間虎囚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工蔫磨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留溜宽,地道東北人。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓质帅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親留攒。 傳聞我的和親對象是個殘疾皇子煤惩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容