或者能夠這麼說,CSS Modules爲咱們解決了什麼痛點。針對以往我寫網頁樣式的經驗,具體來講能夠概括爲如下幾點:javascript
過程是這樣的:你如今有兩個模塊,分別爲A、B,你可能會單獨針對這兩個模塊編寫本身的樣式,例如a.css、b.css,看一下代碼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>'
下面是樣式:html
/* a.css */ .text { color: red; } /* b.css */ .text { color: blue; }
導入到入口APP中前端
// App.js import A from './A.js' import B from './B.js' element.innerTHML = 'xxx'
因爲樣式是統一加載到入口中,所以實際上的樣式合在一塊兒(這裏暫定爲mix.css)顯示順序爲:java
/* mix.css */ /* a.css */ .text { color: red; } /* b.css */ .text { color: blue; }
根據CSS的Layout規則,所以後面的樣式會覆蓋掉前面的樣式聲明,最終有效的就是text
的顏色爲blue
的那條規則,這就是全局樣式覆蓋,同理,這在js
中也一樣存在,所以就引入了模塊化,在js中能夠用當即執行函數表達式來隔離出不一樣的模塊node
var moduleA = (function(document, undefined){ // your module code })(document) var moduleB = (function(document, undefined){ // your module code })(document)
而在css中要想引入模塊化,那麼就只能經過namespace
來實現,而這個又會帶來新的問題,這個下面會講到webpack
爲了解決全局樣式的衝突問題,就不得不引入一些特意命名namespace
來區分scope
,可是每每有些namespace
命名得不夠清晰,就會形成要想下一個樣式不會覆蓋,就要再加一個新的namespace
來進行區分,最終可能一個元素最終的顯示樣式相似如如下:git
.widget .table .row .cell .content .header .title { padding: 10px 20px; font-weight: bold; font-size: 2rem; }
在上一個元素的顯示上使用了7個選擇器,總結起來會有如下問題:github
content
、title
以及item
這些通用的類名時,你可能要花上老半天才知道它們究竟是用在哪一個元素上【注】CSS的渲染規則能夠參看這篇文章探究 CSS 解析原理web
因爲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的時候,就會被編寫兩遍,雖然樣式不會被影響,可是這樣實際上也是一種字節浪費,固然,上述的這種狀況徹底是能夠經過公用全局樣式來達到目的,可是,這種代碼重複一般是在不知情的狀況下發生的。
針對上述的一些問題,也有一些解決方案,具體以下:
Sass,Less的用法這裏再也不贅述,若是不清楚,能夠本身查閱相關資料去了解一下。
CSS預處理器最大的好處就是能夠支持模塊引入,用js的方式來編寫CSS,解決了部分scope
混亂以及代碼冗餘的問題,可是也不能徹底避免。同時,也沒有解決全局樣式的衝突問題
一個SASS
的的文件是這樣的:
/* app.sass */ @import './reset' @import './color' @import './font'
能夠實際上編譯以後,終究仍是一個文件,所以不可避免的會出現衝突樣式
There are only two hard problems in Computer Science: cache invalidation and naming things — Phil Karlton
BEM
就是爲了解決命名衝突以及更好的語義化而生的。
Block:邏輯和頁面功能都獨立的頁面組件,是一個可複用單元,特色以下:
[可選]定義Block和Element的外觀及行爲,就像HTML屬性同樣,能讓同一種Block看起來不同
Block
做爲最小的可複用單元,任意嵌套不會影響功能和外觀,命名能夠爲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
是一個元素的狀態顯示,例如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__Element-father__Element-son_Modifer
這種類名的寫法,BEM只有三級<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解決了模塊複用、全局命名衝突等問題,配合預處理CSS使用時,也能獲得必定程度的擴展,可是它依然有它的問題:
說了這麼多,終於要到正文了
根據CSS Modules的repo上的話來講是這樣的:
CSS files in which all class names and animation names are scoped locally by default.
因此CSS Modules並非一個正式的聲明或者是瀏覽器的一個實現,而是經過構建工具(webpack or Browserify)來使全部的class達到scope的一個過程。
/* 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; }
應用到元素上能夠這樣使用:
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只關注與組件自己,組件自己基本均可以使用扁平的類名來寫,相似於這樣的:
.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不侷限於你使用哪一個前端庫,不管是React、Vue仍是Angular,只要你能使用構建工具進行編譯打包就可使用。
下面我使用webpack
爲例,一步一步引入CSS Modules.
. ├── 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>
全局安裝依賴,配置執行腳本:
npm install webpack webpack-cli --save-dev
package.json
"scripts": { "build": "npx webpack && open index.html" }
在控制檯執行npm run build
, 獲得的結果爲:
> 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]
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;
編譯以後的執行結果爲:
在瀏覽器中顯示爲:
能夠看到打包以後的build
目錄下只有一個bundle.js
,咱們如今要把樣式文件提取出來
./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>
能夠看到有main.css
生成
默認在css-loader
中是不開啓css modules
功能的,要開啓能夠設置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
文件,而如今改爲了導出一個對象的形式,咱們能夠把Index
對象打印出來,看看具體是些什麼東西:
直接對應咱們引用的方式,而後咱們再看看生成出來的main.css
中具體有哪些東西:
* { margin: 0; padding: 0; } ._2BQ9qrIFipNbLIGEytIz5Q { padding: 20px; } ._3Ukt9LHwDhphmidalfey-S { font-size: 32px; } ._3XpLkKvmw0hNfJyl8yU3i4 { border-bottom: 1px solid #ccc; padding-bottom: 20px; }
合成一個文件以後,全部的類名都通過了哈希轉換,所以確保了類名的惟一性,咱們再看看瀏覽器中inspector
中的樣式應用,以下:
事實上,container
樣式咱們是不須要轉換的,由於我是把它固定寫死在了容器上,那咱們應該怎麼作呢?
要想一個類名不須要被裝換,那麼可使用:global(className)
來進行包裝,這樣的類不會被轉換,會被原樣輸出,下面咱們修改global.css
/* global.css */ * { margin: 0; padding: 0; } :global(.container) { padding: 20px; }
咱們再來看看main.css
就能夠發現.container
類沒有被轉換
CSS Modules默認是以[hash:base64]來進行類名轉換的,可辨識度不高,所以咱們須要自定義
開啓自定義,可使用一個配置參數localIdentName
,具體配置以下:
{ loader: "css-loader", options: { modules: true, localIdentName: '[path][name]__[local]--[hash:base64:5]' } }
若是咱們實現相似於Sass
的繼承功能,咱們須要怎麼作呢?CSS Modules中提供了composes
關鍵字讓咱們來繼承另一個類,修改index.css
以下:
.red { color: red; } .header { font-size: 32px; } .title { composes: red; border-bottom: 1px solid #ccc; padding-bottom: 20px; }
咱們增長了一個red
的類名,在title
中實現繼承,編譯以後的結果爲:
發現多了一個src-styles-index__red--1ihPk
的類名,正是咱們上面繼承的那個類
除了在自身模塊中繼承,咱們還能夠繼承其餘文件中的CSS規則,具體以下:
咱們再styles
文件夾下新建一個color.css
/* color.css */ .red { color: red; } .blue { color: blue; }
而後在index.css
文件中導入
/* 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; }
最終咱們會發現文字的顏色爲綠色,可見自身模塊聲明優先級最高,若是把自身申明的color
去掉,那麼自身引入和從其餘文件引入的相同申明又該如何顯示呢?
答案是自身引入的聲明的優先級會比較高。
至此,全部的CSS Modules用法就已經介紹完畢了,至於後續的還有如何應用於React
、Vue
以及Angular
中,相信掌握了上面的內容以後就能夠知道怎麼寫了,如何與預處理器一塊兒使用相信問題也不大。
最後,本文代碼倉庫庫爲:https://github.com/Rynxiao/css-modules-demo