從搭建工程講到CSS Modules

背景

這周的主要的工做就是搭建新工程的架子,項目基於vue-cli構建。基本的功能在腳手架裏都已經具有,可是仍是須要針對具體的業務場景來作一些定製。css

mock

以如今先後端分離的開發模式來說,一個正常的開發流程大概是這樣:
前端

這樣前端能夠不用等後端接口徹底寫完才能夠開發,先後端並行開發,提升團隊效率。vue

而腳手架提供的功能只有一個proxy,不能知足咱們的需求。因此須要咱們本身寫一個mock-middleware。大概是這樣的一個功能,就不貼mock-middleware的代碼了:webpack

module.exports = () => {
    const argv = Array.prototype.slice.call(process.argv, 2);
    const proxyAddress = argv[0];
    if (!argv.length || proxyAddress === 'mock') {
        return mockMiddleware;
    } else {
        return proxyMiddleware(proxyAddress);
    }
}複製代碼

最終的效果就是運行 npm run dev mock ,會將api請求(注意:這裏只會代理api請求,而不代理靜態資源)轉發到mock數據。在先後端開發完成以後,能夠運行npm run dev {proxy_ip}將api請求代理到某臺開發服務器進行聯調。git

固然這篇文章的重點不是這個,而是想總結下在SPA應用裏如何規範的寫CSS。github

CSS模塊化

故事的起點從組件庫的選擇提及,項目選擇了開源的element 做爲組件庫。而這套組件庫並不符合考拉前端組的視覺規範,而目前也沒有計劃fork一條分支來維護,因而就採起了比較簡單粗暴的作法,樣式覆蓋。先來看一段element中的樣式:web

@component-namespace el {
  @b progress {
    position: relative;
    line-height: 1;

    @e text {
      font-size:14px;
      display: inline-block;
      vertical-align: middle;
      margin-left: 10px;
      line-height: 1;
    }
    @m circle {
      display: inline-block;
    }
  }
}複製代碼

可能有同窗在剛看到這段樣式的時候會有點蒙,可是仔細看@b,@e,@m,不就是bem規範麼。
vue-cli

BEM規範

關於BEM規範,網上有不少文章介紹。知乎上也有一篇文章來討論其優劣,這裏不去討論BEM規範的好處和壞處,我以爲只要在一個工程里約定好一種規範並嚴格執行,這樣老是不會錯的。npm

一方面爲了和組件庫的CSS規範保持統一,另外一方面我我的以爲BEM的優勢仍是大於缺點,所以在項目裏也準備按照這個規範來寫。gulp

那麼問題來了,瀏覽器是不認識上面這些@b,@e,@m的語法的,這個時候就須要postcss來幫忙了。

postcss

postcss和gulp,webpack等工具同樣,他自己並非一種預處理器或者後處理器,而是經過各類插件來完成轉換(好比很流行的Autoprefixer就是它的一個插件)。

postcss-salad能夠認爲是一個postcss插件的集合,支持最新的css語法,一些sass嵌套的語法以及bem轉化。這時候先來一份配置文件postcss.config.js

module.exports = {
  plugins: [
    require('postcss-salad')({
      browsers: ['ie > 9', 'last 2 versions'],
      features: {
        bem: {
          shortcuts: {
            component: 'b',
            descendent: 'e',
            modifier: 'm'
          },
          separators: {
            descendent: '__',
            modifier: '--'
          }
        }
      }
    })
  ]
}複製代碼

bem之間的鏈接符能夠自定義,這邊是爲了和element的組件保持一致。這時候咱們能夠先經過postcss-cli這個工具來看一下效果。咱們就用上述那段css來用postcss+postcss-salad進行轉換,獲得結果以下:

.el-progress {
    position: relative;
    line-height: 1
}

.el-progress__text {
    font-size: 14px;
    display: inline-block;
    vertical-align: middle;
    margin-left: 10px;
    line-height: 1
}

.el-progress--circle {
    display: inline-block
}複製代碼

已經達到了咱們預想的效果,那麼接下來要想在webpack中使用postcss確定會須要一個loader,也就是postcss-loader

而後將剛纔的postcss.config.js做爲postcss-loader的配置文件導入。

{
  loader: 'postcss-loader',
  options: {
    config: {
      path: 'path/to/postcss.config.js'
    }
  }
}複製代碼

至此,就能夠在webpack項目中,用這種嵌套的bem語法來進行css的書寫了。

CSS Modules

故事尚未結束,你們都知道在寫單頁面應用的時候,一個常見的需求是但願組件間的css做用域是互相隔離的。這時候第一反應想到就是Scoped CSS,vue-loader也是支持Scoped CSS的(實際上對CSS進行轉換的仍是postcss)。那麼Scoped CSS是如何來處理這個問題的呢:

<style scoped>
.example {
  color: red;
}
</style>

=>

<style>
.example[_v-f3f3eg9] {
  color: red;
}
</style>複製代碼

能夠看到Scoped CSS會在class後面加上一段hash,從而來實現CSS做用域的隔離,可是在實踐過程當中,會發現幾個問題:

  1. Scoped CSS將同時做用在父組件和子組件上,也就是沒法作到父子組件樣式的隔離。
  2. 因爲Scoped CSS改變了DOM中的class,所以就沒法在組件內去覆蓋element組件的全局樣式了。

這時候就須要CSS Modules出場了,CSS Modules是目前CSS模塊化方案中被接受度較高的一種方案,網上也有不少的文章去介紹它。一樣的,咱們須要先看下CSS Modules能作什麼事情呢?

剛纔講到,postcss有不少的插件,那麼確定也會有CSS Modules的插件了。咱們一樣經過以前的postcss-cli來進行測試,首先配置上這個插件:

require('postcss-modules')({
  generateScopedName: '[local]--[hash:base64:5]'
})複製代碼

這裏的[local]表明類名,[hash:base64:5]按照給定規則生成的hash值,還能夠用的變量有[name]表明標籤名,[path]表明路徑等
CSS Modules 生成的class能夠自定義規則,所以也能夠用自定義的規則,而不用bem規範,能夠看項目的具體狀況來定

仍是一樣那段css代碼,看下轉換的結果:

.el-progress--3tuDF {
    position: relative;
    line-height: 1
}

.el-progress__text--1W8n3 {
    font-size: 14px;
    display: inline-block;
    vertical-align: middle;
    margin-left: 10px;
    line-height: 1
}

.el-progress--circle--3OD0E {
    display: inline-block
}複製代碼

跟咱們預設的樣式規則一致,這樣就能夠解決上述的第一個問題,作到每一個組件內的樣式是惟一的。

可是咱們發現CSS Modules一樣會改變class,那麼也一樣沒法在組件內覆蓋element組件的全局樣式,這時候能夠用CSS Modules的全局樣式寫法,:global{.class}

:global(.el-progress) {
  position: relative;
  line-height: 1;
}

=>

.el-progress {
  position: relative;
  line-height: 1;
}複製代碼

這樣就不會在class上加上hash後綴,從而能夠達到覆蓋全局樣式的目的。

CSS Modules 在Vue+webpack項目中的實踐

首先在Vue-loader的配置文件中配置生成class規則:

cssModules: {
    localIdentName: '[local]--[hash:base64:5]',
    camelCase: true
}複製代碼

而後在組件內經過在style上添加module打開CSS Modules。

<style module>
</style>複製代碼

css-loader 會將一個 $style對象注入到當前組件。因此在實際中使用大概是這個樣子:

<header :class="$style['titan-header']">
</header>複製代碼

有時候咱們會有這樣的用法:

<div :class="{ 'active': selectedIndex == index} ">
</div>複製代碼

這時候樣式就會做爲對象的屬性名,而咱們知道用了CSS Modules,就必須用$style.active來替換'active',還好咱們有ES6!
ES6有一個特性是用雙括號支持用計算屬性做爲屬性名,也就是這樣:

<div :class="{ [$style.active]: selectedIndex == index} ">
</div>複製代碼

總結

bem+CSS Modules在新項目的實踐已經有一週的時間,並無發現什麼問題,才寫下這篇文章來總結。以上只是我在本次工程搭建過程當中的一些總結,並不保證觀點徹底正確,供你們參考。

相關文章
相關標籤/搜索