在Vue工做流中使用CSS Modules

CSS在工程化上的一些問題

關於React的CSS in JS,有一個著名的talk,由Facebook的工程師vjeux帶來。css

裏面最有名的一張slide是這樣的:vue

css in js

裏面列舉了CSS的一些問題。其中,Dead Code Elimination,Minification,和Sharing Constants這些問題咱們已經經過在咱們的工做流中加入SASS和PostCSS這樣的CSS預處理器解決了。react

然而還有一些問題沒有解決,好比全局命名空間。同一個document下的全部CSS的類名,都是在同一個「做用域」下的,所以咱們經常要考慮如何避免命名衝突問題。現有的解決辦法主要是靠BEM這樣的命名慣例,或者是用多層CSS父子選擇器來模擬命名空間。然而這樣的辦法對工程師有許多的限制。多級選擇器有比較高的優先級,不容易維護。webpack

解決全局做用域:Webpack css-loader

Webpack的css-loader首先作出瞭解決全局做用域的嘗試。解決辦法就是在寫CSS類名時加入:local(...)這樣的標記。git

好比:github

:local(.className) { background: red; }
:local .className { color: green; }
:local(.className .subClass) { color: green; }
:local .className .subClass :global(.global-class-name) { color: blue; }

會被轉化爲:web

._23_aKvs-b8bW2Vg3fwHozO { background: red; }
._23_aKvs-b8bW2Vg3fwHozO { color: green; }
._23_aKvs-b8bW2Vg3fwHozO ._13LGdX8RMStbBE9w-t0gZ1 { color: green; }
._23_aKvs-b8bW2Vg3fwHozO ._13LGdX8RMStbBE9w-t0gZ1 .global-class-name { color: blue; }

這裏的辦法就是把CSS類名轉化爲hash字符串,這樣就能夠保證每一個類名都是獨一無二的,天然也就不用在乎命名衝突的問題了。只要在類名在當前模塊內不會相互衝突就能夠了。設計模式

CSS Modules

上述的辦法,仍是有一些不便。大多數狀況下,好比在JavaScript中,變量都默認是局部變量。你想要聲明一個全局變量,只能去全局做用域聲明,或者把變量掛到local上(非嚴格模式下,不寫var聲明的是全局變量這種坑就不說了)。sass

Webpack的開發者以後將css-loader中的local變成了默認設定,因而CSS Modules這個規範就呼之欲出了。app

CSS Modules規範。咱們能夠經過css-loader?modules這個參數來開啓CSS Modules。

CSS Modules中的類名默認就是local的,若是你想要聲明全局類名,能夠加上:global(...)這個標記。

Single Responsibility Principle

講CSS Modules的下一個特性以前。咱們先聊點其餘的,咱們知道設計模式中有一條叫作Single Responsibility Principle。

好比咱們有一個button:

.button {
    display:inline-block;
    padding:2em;
    background-color:red;
}

與其把這些屬性寫在一個class裏,咱們能夠把它拆分紅多個單獨的class:

.button {
    display:inline-block;
}
.button--large{
    padding:2em;
}
.button--warnning{
    background-color:red;
}

而後在HTML中組合使用就能夠了。

<button class="button button--large button--warnning">

這樣的好處是什麼呢?咱們的UI中,一個組件每每有不少不一樣的狀態。若是咱們將每個class寫成只專一於一個屬性,作好一件事,那就能夠用這些class組合成全部咱們想要的不一樣狀態的組件。相比給每一個狀態的組件寫一個單獨的class,代碼要更優雅簡潔一些。

好比咱們想要一個small尺寸的普通button,只要加兩個class:

.button {
    display:inline-block;
}
.button--small{
    padding:1em;
}
.button--large{
    padding:2em;
}
.button--normal{
    background-color:blue;
}
.button--warnning{
    background-color:red;
}

而後組合就能夠了:

<button class="button button--small button--normal">

CSS Classes Composing

要想實現上述的這種組合,可使用SASS的Mixin,但Mixin主要是提供了源代碼中的抽象,最後生成的代碼,和手寫不一樣狀態class的代碼量,是同樣的。

CSS Modules提供的Classes Composing則恰好能夠知足咱們的需求。

好比咱們想渲染一段文字:

.text{
  font-size: 20px;
  composes: red from "./common/color.css";
}

color.css裏是這樣的

.red{
    color: red;
}

最後渲染出的class是這樣的

<div class="App-text-2AEnE_0 color-red-3ag3h_0"></div>

composes引入的類被做爲一個單獨的class引入,而不是和text類合在一塊兒。

CSS Modules和Vue工做流的整合

Vue-loader在v9.8.0以後加入了對CSS Modules的支持。

咱們只要在.vue文件的<style>處加一個module就行

<style lang="sass" module>
.text{
  font-size: 20px;
  composes: red from "sass!./common/color.scss";
}
</style>

這裏有一點要注意,就是composes引入的若是是須要預處理器處理的,要在前面加上預處理器的標記,好比SASS用戶就加上sass!

若是須要對CSS Modules進行一些配置(其實這個是對Webpack的css-loader的配置,因此配置時能夠參考css-loader的文檔),寫在vue-loader的配置的cssModules屬性裏便可

loader: 'vue',
options: {
    cssModules: {
        localIdentName: '[name]-[local]-[hash:base64:5]',
        camelCase: true
    }
}

vue-loader會自動將一個$style屬性注入到對應的Vue實例中。在模板中用class binding語法寫就能夠了。

<template>
  <div :class="$style.app">
    <div :class="$style.text">
      some text
    </div>
    <main-text></main-text>
  </div>
</template>

$style實際上是一個原class名和處理以後class名的hash,像這樣:

{
  app: "App-app-3cl75_0",
  text: "App-text-2AEnE_0 color-red-3ag3h_0"
}

我寫一了一個簡單的DEMO倉庫,能夠供參考。

結語

CSS Modules能夠解決全局做用域和Class組合兩個問題,加上SASS等預處理器,着實讓咱們在寫CSS時的工程化程度大大提升了。

對於使用Vue的同窗來講,vue-loader可使CSS Modules能夠輕鬆的整合到已有的工做流中。若是你正在使用Vue,能夠試試使用CSS Modules。

Links

博客原文連接

相關文章
相關標籤/搜索