如何實現css模組化

小飛貓_發表於2019-01-30

    模組化目前在前端的日常開發中已經不是什麼新鮮詞,早期AMD規範的requirejs,CMD規範的seajs,以及nodejs的模組化規範commonjs,但是css的模組化一直進展不大,雖然有想less,sass,postcss…的出先,但是這些只是改善了css弱程式設計方面的問題,在模組化方面還是進展比較緩慢。

一.為什麼要說CSS模組化?

    首先,前端模組化是大勢所趨,而CSS又是前端開發中重要的一部分,在大型web專案中,沒有規範的模組化方案的問題對日常的開發和維護影響越來越大。
    其次,模組化的好處在專案的維護,提高程式碼的複用率,便於組內協同開發,提高開發效率方面的效果是肉眼可見的
    所以CSS樣式的模組化一直在不斷的嘗試和探索


如何實現CSS的模組化

    在講如何實現CSS模組化之前先講講要利用模組化來解決什麼問題:

  1. 樣式私有化
  2. 避免被其他樣式檔案汙染
  3. 可複用

我們在寫樣式的時候最常見的方式就是為各個模組的根節點設定一個唯一的類名,然後採用樣式的後代選擇器的方式來實現的,比如下面這樣:

<div class="container">
   <div class="area1"></div>
   <div class="area2">
      <div class="area3"></div>
   </div>
</div>

樣式檔案:

.container > .area1{
}
.container > .area2 {

}
.container > .area2 .area3 {
}

    當然,我們可以藉助less,sass這類前處理器來寫上面的樣式,這樣樣式的可程式設計能力會更好,同時樣式檔案的層次也會更加清晰,比如我們用less寫上的樣式檔案
index.less

.container{
   .area1{}
   .area2{
         .area3{
         }
   }
}

    這樣看起來一方面解決了不影響其他模組的問題,同時css的程式設計能力弱的問題也得到了解決。but ! but ! but ! 這只是針對前端專案比較小的時候,開發人員少比如就一個人就搞定了,像這類前端工程,這種方式確實就能解決問題了。如果這個前端工程師有多個前端開發人員協同開發,甚至可能還設計到跨小組開發的情況,那麼這種方式就明顯不適用了,因為別人在寫類名的時候,他是不知道什麼類名已經使用了,所以這也可能導致類名重名,並不能真正解決樣式不汙染其他的樣式和被其他樣式汙染的問題。

    既然有可能重名,那麼能不能使用hash值來生成dom元素的唯一標識呢?這樣就可以避免重名了。別說,還是真可以。下面我們就來說說在目前前端流行框架Vue和React在CSS模組化方面的嘗試。


Vue中CSS模組化

寫過vue專案的人對scoped應該不陌生,比如像下面這樣

<template>
   <div class="conatianer">
     <div class="area1"></div>
   </div>
</template>
<script></script>
<style scoped>
  .conatianer{...}
  .conatianer .area1{...}
</style>

    這樣就能讓我們的樣式只對當前模組起作用,不會影響其他的模組的樣式。那他是如何實現的呢?先來看看這樣寫最後呈現出來的樣式檔案到底長什麼樣子,開啟瀏覽器的除錯視窗我們會看到style中的樣式變成了下面這樣:

.conatianer[data-v7ba5bd90] {...}
.conatianer[data-v7ba5bd90] .area1 {...}

當我們不使用scoped屬性的瀏覽器中看到的結果結項下面這樣:

.conatiane {...}
.conatianer .area1{...}

    是不是豁然開朗了,Vue本身實現模組化的實質就是在.vue檔案編譯的時候為dom元素生成一個唯一的屬性,然後css使用屬性選擇器來選擇唯一的元素,這樣就可以實現私有化的效果了。但這不是我們追求的CSS 模組化,這麼說呢?
    我們知道CSS選擇器是有權重這個概念的,不同的選擇器的權重是不一樣的,權值也高,優先順序的樣式就也高,低權值的樣式會被高權值的樣式覆蓋。
    Vue的Scoped方案是用來避免自身模組的樣式不會干擾其他模組,但是你不惹人家,並不代表人家不來惹你,就像中美貿易戰一樣,這就是很好的例子。自身的樣式可能會被其他沒有私有化的樣式干擾,在Vue的開發中這類問題不是沒有碰見過,比如一些第三方的樣式檔案,以及一些沒有進行私有化的樣式檔案。所以自身強大才是硬道理。
    其實從Vue使用scoped這個詞也可以知道,scoped的意思是範圍,官方並沒有使用modules模組這個詞,也就是說Vue的Scoped只是解決了私有化的問題。
    當然,Vue的scoped雖然有瑕疵,但是人家在模組化這方面還是做了貢獻的,就是先管好自己,自己先做一個好好先生,如果人人都是好好先生,那麼不就是人間到處充滿愛了嗎?當然這有點理想主義,現實是很複雜的,除了自己做個好好先生外,我們還有足夠的方法來保護自己和親人不受他人的傷害。那麼還有什麼方式來實現樣式的模組呢?說過Vue怎麼能少了React呢?


React中CSS模組化

    在react中,官方推薦使用的是 css modules,具體用法就像下面這樣:
main.jsx:

import React,{Component} from "react";
import styles from "assets/css/main.css";
class main extends Component{
   constructor(props){
      super(props);
   }
   render(){
       return (
               <div class={styles.container}>
                   <div class={styles.child}></div> 
              </div>)
   }
}
export default main;

main.css:

.container{
 ...
}
.child{
 ...
}

頁面正常顯示,開啟瀏覽除錯視窗的時候我們會發現原來我們寫的樣式已經變成下面這樣了:

.container-5c73dda7{
  ...
}
.child-46hgsds3{
  ...
}

頁面中元素的型別也變成了了對應的

  <div class="container-5c73dda7">
     <div class="child-46hgsds3"></div> 
  </div>

    通過上面的程式碼我們可以發現,這是通過唯一的類名和樣式做對映,從而達到css模組化的一種方案,這種方案相比於Vue的CSS私有化屬性scoped來說好處就是可以防止樣式被其他的樣式檔案汙染,也可以阻止自己的樣式汙染別的模組。當然這裡有一個問題,那就是我們在寫類名的時候需要用到了變數,這和我們平時寫html的習慣有點不一致,那有沒有什麼解決辦法,還是按照我們平常的編碼方式。當然有,在React中有高階元件這個概念,我們可以使用高階元件,然後正常寫元素的類名,就像下面這樣:

import React,{Component} from "react";
import styles from "assets/css/main.css";
import CSSModules from "react-css-modules";
class main extends Component{
   constructor(props){
      super(props);
   }
   render(){
       return (
               <div class={styles.container}>
                   <div class={styles.child}></div> 
              </div>)
   }
}
export default CSSModules(styles,main);

還可以使用ES7的"裝飾器"decorator來使用高階元件,程式碼會更加簡潔,向下面這樣:

import React,{Component} from "react";
import styles from "assets/css/main.css";
import CSSModules from "react-css-modules";
@CSSModules(styles)
class main extends Component{
   constructor(props){
      super(props);
   }
   render(){
       return (
               <div class={styles.container}>
                   <div class={styles.child}></div> 
              </div>)
   }
}
export default main;

是不是更方便了,和我們平時寫React沒什麼區別。

    看了上面提到的CSS模組方式,明顯感覺到其實React中提倡的這種方案是一種目前為止比較能接受的方式,一方面他能達到模組化的要求,另一方面在程式碼的編寫方式上也沒有太大的改變,那麼這裡就要提到這邊文章的豬腳了,也就是react推崇的這種樣式模組化方案 CSS Modules

CSS Modules
    就像他的名字一樣,就叫CSS模組化,牛逼先吹出來。它不像Vue中的scoped方案,他是由css-loader去實現的,也就是由webpack的loader去實現的,所以在使用CSS Modules時需要配合webpack的css-loader進行編譯,就像下面這樣先配置webpack配置:

module.exports = {
   entry:{...},
   output:{...},
   module:{
     rules:[{
        test:/\.css$/,
        exclude:/node_module/,
        use:[{
          loader:'style-loader'
        },{
          loader:'css-loader',
          options:{
	           importLoaders:1
	           modules:true,//開啟樣式模組化
	           loaclIdentName:'[name]-[hash:8]'//class名稱生產規則
          }
        }]
     }]
   }
}

除了css,less,sass這類前處理器也可以配合css-loader,使用開啟樣式的模組化。
    具體的使用規範,可以去看阮玉峰的CSS Modules使用教程上面有關於它的詳細使用教程,當然他的教程是在React的基礎上來說的,但並不表示CSS Modules是專門為React服務的,在Vue中也是能使用的,只是Vue官方已經實現了scoped方案了,正常情況下該方案也能完成我們的需求。

總結:

    總結一下上面提到的三種方式:

  1. 就是通過每個頁面根節點唯一類名,然後加上CSS後代選擇器的方式來實現私有樣式,這種方式是最簡單,基本上和模組化不搭邊,他只適合在比較小的前端中使用。
  2. Vue中scoped方案,通過給每個模組生成一個唯一的屬性值,然後將該屬性新增到每個dom節點上,然後配合CSS的屬性選擇器來時實現私有樣式,這種方式只能解決樣式私有化的問題,但是也架不住被其他樣式檔案干擾
  3. 開啟css-loader的modules,使用CSS Modules方案,它不僅能實現樣式的私有化,還能有效的避免被其他樣式檔案干擾,只是他需要藉助webpack進行進行編譯,寫法上也有點不一樣。

    總的來說,CSS Modules方案通過js來維護CSS中的類名的唯一對映值的方案是目前來說更加可行的一種方案。相比較於像CSS Inline完全使用js來編寫樣式來說已經有比較大的進步了。相信在後面版本的CSS規範中,會有更好的解決方案。

相關文章