CSS是一門15分鐘就能入門,但是卻需要很長(zhǎng)很長(zhǎng)的時(shí)間才能掌握好的語言只估。它有著它自身的一些復(fù)雜性與局限性。其中非常重要的一點(diǎn)就是着绷,本身不具備模塊化的能力蛔钙。
面臨的問題
你可能會(huì)說,CSS有@import
功能荠医。然而吁脱,我們都知道,這里的@import
僅僅是表示引入相應(yīng)的CSS文件彬向,但其模塊化核心問題并未解決——CSS文件中的任何一個(gè)選擇器都會(huì)作用在整個(gè)文檔范圍里兼贡。
因此,其實(shí)我們面臨的最大問題就是——所有的選擇器都是在一個(gè)全局作用域內(nèi)的娃胆。一旦引入一個(gè)新的CSS文件紧显,就有著與預(yù)期不符的樣式表現(xiàn)的風(fēng)險(xiǎn)(因?yàn)橐恍┎豢深A(yù)測(cè)的選擇器)。
而如今的前端項(xiàng)目規(guī)模越來越大缕棵,已經(jīng)不是過去隨便幾個(gè)css、js文件就可以搞定的時(shí)代涉兽。與此同時(shí)的招驴,對(duì)于一個(gè)大型的應(yīng)用,前端開發(fā)團(tuán)隊(duì)往往也不再是一兩個(gè)人枷畏。隨著項(xiàng)目與團(tuán)隊(duì)規(guī)模的擴(kuò)大别厘,甚至是項(xiàng)目過程中人員的變動(dòng),如何更好進(jìn)行代碼開發(fā)的管理已經(jīng)成為了一個(gè)重要問題拥诡。
回想一下触趴,有多少次:
- 我們討論著如何對(duì)class進(jìn)行有效的命名氮发,以避免協(xié)作開發(fā)時(shí)的沖突;
- 我們面對(duì)一段別人寫的css冗懦、html代碼爽冕,想要去修改,然后瘋狂查找披蕉、猜測(cè)每個(gè)類都是什么作用颈畸,哪些是可以去掉的,哪些是可以修改的——到最后我們選擇重新添加一個(gè)新的class没讲;
- 我們準(zhǔn)備重構(gòu)代碼時(shí)眯娱,重構(gòu)也就成了重寫
- ……
寫一段CSS往往并不是困難所在,難得確實(shí)團(tuán)隊(duì)的合作與后續(xù)的維護(hù)爬凑。
針對(duì)這些實(shí)際項(xiàng)目中的問題徙缴,一直以來,開發(fā)者們都在探索解決方案嘁信。這一篇文章主要介紹了于样,如何在webpack中使用一種類似“CSS模塊化”的解決方案———Local Scope,來規(guī)避一些開發(fā)中的問題吱抚。
什么是Local Scope
通常來說百宇,CSS中的所有選擇器可以算是“全局作用域”。而“Local Scope”顧名思義秘豹,使CSS具有類似于局部作用域的能力携御,同時(shí)搭配類似JavaScript中模塊化的寫法,到達(dá)CSS模塊化的效果既绕。
這么說可能有些抽象啄刹,我們可以來看一個(gè)例子。
在webpack中引入css往往是這樣的:
// index.css
.title {
font-size: 30px;
color: #333;
}
// index.js
import './index.css';
funciont createTitle(str) {
var title = document.createElement('h1');
title.appendChild(document.createTextNode(str));
title.setAttribute('class', 'title');
document.body.appendChild(title);
}
createTitle('Hi!');
由于凄贩,webpack中將js誓军、css、png等這些資源都視為模塊疲扎,所以可以通過import導(dǎo)入昵时。但是,實(shí)際上椒丧,對(duì)于導(dǎo)入的所有css壹甥,其“地位”都是平等的,都是在全局有效的壶熏。例如:
// index.css
.title {
font-size: 30px;
color: #333;
}
// other.css
.title {
font-size: 15px;
color: #999;
}
// index.js
import './index.css';
import './other.css';
funciont createTitle(str) {
var title = document.createElement('h1');
title.appendChild(document.createTextNode(str));
title.setAttribute('class', 'title');
document.body.appendChild(title);
}
createTitle('Hi!');
當(dāng)我們引入了新的CSS文件other.css后句柠,其中的.title
和index.css中的.title
有著同樣的“作用域”——全局玻靡。
回想一下在JavaScript中:
// a.js
var a = 1;
// other.js
var a = 2;
// index.html
<script src="./a.js"></script>
<script src="./other.js"></script>
<script>
console.log(a); // 2
</script>
如果某個(gè)html頁面通過script
標(biāo)簽引入這兩js文件木人,那么a的值必定會(huì)有沖突条辟,其中一個(gè)會(huì)被覆蓋媒熊。如果使用模塊化的方式,可以變成:
// a.js
export var a = 1;
// other.js
export var a = 2;
// app.js
import {a} from './a';
import {a as other} from './other';
console.log(a); // 1
console.log(a); // 2
而所謂的Local Scope就是webpack中在CSS上針對(duì)這種問題的一個(gè)解決方案谜酒。類似JavaScript的模塊化叹俏,通過對(duì)CSS文件進(jìn)行模塊引用與導(dǎo)出的方式,能夠在開發(fā)時(shí)甚带,更有效得控制各個(gè)class的作用范圍她肯。
使用方法
首先,需要在webpack中對(duì)css-loader
進(jìn)行一定的配置鹰贵。
// loader: 'css-loader',
// options: {
// modules: true,
// localIdentName: '[local]__[name]--[hash:base64:5]'
// }
const config = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [{
test: /\.css$/,
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[local]__[name]--[hash:base64:5]'
}
}]
}]
}
};
然后晴氨,我們還是使用前一節(jié)例子中的那個(gè)場(chǎng)景:
// index.css
:local .title {
font-size: 30px;
color: #333;
}
// other.css
:local .title {
font-size: 15px;
color: #999;
}
// index.js
import styles from './index.css';
import others from './other.css';
funciont createTitle(str) {
var title = document.createElement('h1');
title.appendChild(document.createTextNode(str));
// styles.title font-size: 30px;color: #333;
title.setAttribute('class', styles.title);
document.body.appendChild(title);
}
createTitle('Hi!');
其中需要注意的有三個(gè)地方:
- 第一個(gè)是在CSS文件中的,類選擇器前多了
:local
這個(gè)語法碉输。通過添加:local
就可以指示webpack籽前,這不是一個(gè)“全局”的選擇器(當(dāng)然,實(shí)際上也是全局的敷钾,后面會(huì)簡(jiǎn)單解釋)枝哄。 - 第二個(gè)地方是在js文件中,將
import 'index.css'
變?yōu)榱?code>import styles from './index.css'阻荒。是不是看著很熟悉挠锥,沒錯(cuò),和JavaScript中的模塊化方案用法一樣侨赡。 - 第三個(gè)地方蓖租,在使用到該class的地方,由原來的
title.setAttribute('class', ‘title’)
變?yōu)榱?code>title.setAttribute('class', styles.title)羊壹。這樣我們可以選擇在一部分dom元素上使用styles.title
蓖宦,即index.css
的樣式;在另一部分dom元素上使用other.css
的樣式油猫。
這樣就解決了我們之前提到的問題稠茂。
當(dāng)然,有些時(shí)候情妖,我們希望類選擇器中的某一部分仍然是“全局”的睬关,那么我們可以這么寫:
:local .title :global(.sub-title) { color: #666; }
關(guān)于Local Scope
雖然我們上面說了這么多次的“模塊化”、“作用域”毡证、“全局”电爹,然而,實(shí)際上情竹,對(duì)于CSS這門語言來說,它在自己本身的邏輯上是不具備這些特點(diǎn)的。而webpack中Local Scope的相關(guān)方案秦效,其實(shí)也并不是(CSS本身邏輯支持的)真正意義上所謂的模塊化雏蛮。所以很多地方我都打上了引號(hào)。
如果對(duì)著打包后的頁面阱州,打開chrome控制臺(tái)挑秉,會(huì)發(fā)現(xiàn),我們的html是這個(gè)樣子的
<body>
<h1 class="title__index--330EV">Hi!</h1>
</body>
h1
標(biāo)簽的class
并不是我們?cè)贑SS中所寫的title
苔货,而是一串奇怪的字符串title__index--330EV
犀概。
當(dāng)使用webpack進(jìn)行打包時(shí),由于檢查到:local
這個(gè)語法夜惭,因此會(huì)為.title
這個(gè)class生成一個(gè)新的class名稱姻灶,而我們?cè)趈s文件中所使用的styles.title
對(duì)應(yīng)的就是這個(gè)新的classname。
所以可以理解诈茧,其實(shí)當(dāng)前的CSS語法邏輯中中并沒有實(shí)際意義上所謂的local scope产喉,但是,通過webpack打包時(shí)的操作敢会,我們會(huì)為每個(gè):local
的class生成一個(gè)唯一的名稱曾沈,而我們使用樣式實(shí)際是指向了這個(gè)classname。這就實(shí)現(xiàn)了兩個(gè)CSS文件中鸥昏,相同名稱的class在使用時(shí)就不會(huì)有沖突了塞俱,相當(dāng)于避開了“全局作用域”。
如果打開打包后的bundle.js
,我們可以發(fā)現(xiàn)一段很有趣的代碼
// ……其余省略
// exports
exports.locals = {
"title": "title__index--330EV"
};
// ……其余省略
// exports
exports.locals = {
"title": "title__other--3vRzX"
};
這是在兩個(gè)不同的模塊內(nèi)的部分吏垮。上面一個(gè)就是導(dǎo)出的index.css
中對(duì)應(yīng)的classname障涯,下面一個(gè)就是other.css
的。通過styles.title
就可以引用到title__index--330EV
這個(gè)實(shí)際值惫皱。
最后像樊,再來說一下title__index--330EV
這個(gè)值得由來。在上一節(jié)的一開始旅敷,我們對(duì)webpack進(jìn)行了配置生棍,其中有一行
localIdentName: '[local]__[name]--[hash:base64:5]'
其實(shí)就是指示了唯一標(biāo)識(shí)的命名方式:local
是class的名稱,name
是文件的名稱媳谁,而最后加上hash
值涂滴。當(dāng)然,你完全可以使用其他你喜歡的方式晴音。默認(rèn)是使用[hash:base64]
柔纵。
最后
其實(shí)webpack中的CSS模塊化方案Local Scope,粗淺的來說也是通過生成唯一的classname來避免沖突锤躁,控制作用范圍搁料。只是和BEM不同,BEM是一個(gè)建議標(biāo)準(zhǔn),更多的還是人為的操控郭计,而webpack中的Local Scope則提供了一個(gè)完整的模塊化與打包方案霸琴。在一定程度上提高了開發(fā)的效率,降低了錯(cuò)誤率昭伸。