背景
這周的主要的工作就是搭建新工程的架子,專案基於vue-cli構建。基本的功能在腳手架裡都已經具備,但是還是需要針對具體的業務場景來做一些定製。
mock
以現在前後端分離的開發模式來講,一個正常的開發流程大概是這樣:
這樣前端可以不用等後端介面完全寫完才可以開發,前後端並行開發,提高團隊效率。
而腳手架提供的功能只有一個proxy,不能滿足我們的需求。所以需要我們自己寫一個mock-middleware
。大概是這樣的一個功能,就不貼mock-middleware
的程式碼了:
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請求代理到某臺開發伺服器進行聯調。
當然這篇文章的重點不是這個,而是想總結下在SPA應用裡如何規範的寫CSS。
CSS模組化
故事的起點從元件庫的選擇說起,專案選擇了開源的element 作為元件庫。而這套元件庫並不符合考拉前端組的視覺規範,而目前也沒有計劃fork一條分支來維護,於是就採取了比較簡單粗暴的做法,樣式覆蓋。先來看一段element中的樣式:
@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規範麼。
BEM規範
關於BEM規範,網上有很多文章介紹。知乎上也有一篇文章來討論其優劣,這裡不去討論BEM規範的好處和壞處,我覺得只要在一個工程里約定好一種規範並嚴格執行,這樣總是不會錯的。
一方面為了和元件庫的CSS規範保持統一,另一方面我個人覺得BEM的優點還是大於缺點,因此在專案裡也準備按照這個規範來寫。
那麼問題來了,瀏覽器是不認識上面這些@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作用域的隔離,但是在實踐過程中,會發現幾個問題:
- Scoped CSS將同時作用在父元件和子元件上,也就是無法做到父子元件樣式的隔離。
- 由於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在新專案的實踐已經有一週的時間,並沒有發現什麼問題,才寫下這篇文章來總結。以上只是我在本次工程搭建過程中的一些總結,並不保證觀點完全正確,供大家參考。