CSS 的全域性性
相當長一段時間 CSS 總是在頁面上作為一個全域性的存在,以前這個『特性』影響還不算很大,命名上注意一點,比如使用 BEM 也能一定程度上解決問題。
但是隨著 web 元件化的需求越來越強烈,CSS 的這種特性開始成為束縛開發者自由飛翔的繩索,每一個 CSS 類名都有可能產生意想不到的衝突,或者被各個元件覆蓋來覆蓋去,每當修改一個元件時,我們必須謹小慎微,注意是否會在全域性環境下產生衝突。
更嚴重的是,元件化的背景下,JS + 模板 + CSS 才能稱為一個完整的元件,每個元件如果單獨引用一個 CSS 檔案,你只能通過約束類命名來規避不同元件間可能產生的衝突。同時由於 CSS 的全域性性,元件的樣式可能會互相影響,這完全打破了元件的低耦合高內聚原則。
當然,CSS 並不能算程式語言,不過是一款 DSL,本來我們不能要求它那麼多,但是不甘心的程式設計師們想出了各種方法讓 CSS 更像程式語言,從 SASS、Less 到現在有點火的 CSS in JS 都是為了解決這個問題。
而 CSS Modules 的解決思路有所不同,它在編寫 CSS 的時候加入了區域性作用域/全域性作用域的概念。
CSS Modules
CSS Module 的規則非常簡單,所有你不指明是全域性作用域的都會當初區域性作用域來處理。
這裡以 SASS 為例,比如你寫一段:
.title {
height: 80px;
line-height: 80px;
font-size: 24px;
color: #0a95bf;
}複製程式碼
出來的樣式是:
.title_1hf8_ {
height: 80px;
line-height: 80px;
font-size: 24px;
color: #0a95bf
}複製程式碼
title
變成了 title_1hf8_
你大概猜到了,CSS Module 實現區域性作用域的方法是把類通過一定規則做個編碼。
元件中引用是這樣的:
import * as Style from './index.scss';複製程式碼
這樣就可以在模板中使用了:
<div v-bind:class="Sytles.title">MD Converter</div>複製程式碼
出來的 html 就是:
<div class="title_1hf8_">MD Converter</div>複製程式碼
這裡用的是 TypeScript 和 Vue ,其它地方用法也差不多。
『區域性作用域』有了,下面是全域性作用域:
:global {
.output {
background: #fff;
height: 100%;
min-height: 100%;
padding: 1em;
word-break: break-all;
}
}複製程式碼
把你想作為全域性作用域的東西用 :global{}
包起來就可以了。
配合 SASS 和 TypeScript
一般用 CSS Module 使用 Webpack 的 css-loader 即可,這裡因為用的是 TypeScript,會有點不一樣。
先來個完整的 Webpack 配置檔案:
var path = require('path');
var webpack = require('webpack');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
//頁面入口檔案配置
entry: {
"index.entry": __dirname + '/src/js/index.entry.js',
},
//入口檔案輸出配置
output: {
filename: '[name].js',
chunkFilename: "[name].chunk.js",
},
externals: {
"vue": "Vue",
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {warnings: false},
output: {comments: false},
sourceMap: true
})
],
module: {
rules: [
{
test: /\.html$/,
loader: 'text-loader'
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: {
loader: 'style-loader',
options: {
insertAt: 'top'
}
},
use: [
{
loader: 'typings-for-css-modules-loader',
options: {
modules: true,
namedExport: true,
camelCase: true,
minimize: true,
localIdentName: "[local]_[hash:base64:5]"
}
},
{
loader: 'sass-loader',
options: {
outputStyle: 'expanded',
sourceMap: true
}
}
]
})
},
],
},
plugins: [
new ExtractTextPlugin({
filename: (getPath) => {
return getPath('../css/[name].css').replace('css/js','css');
},
allChunks: true
}),
]
};複製程式碼
這裡主要用到三個 Loader,分別是 sass-loader,typings-for-css-modules-loader 和 style-loader。
如果不是 TypeScript 把 typings-for-css-modules-loader 換成 css-loader,稍微改下配置即可。
啟用 CSS Module 很簡單,就一句話 modules: true,
然後下面有個選項 localIdentName: "[local]_[hash:base64:5]"
是用來指定生成類名的規則的,title_1hf8_
就是根據這個規則來的,你也可以弄得複雜點,比如 [path][name]__[local]--[hash:base64:5]
簡單解釋下三個 Loader 的作用:
- sass-loader 的作用當然是把 SASS 檔案編譯成 CSS 檔案;
- typings-for-css-modules-loader 是在 css-loader 上包了一層,它的選項完全相容 css-loader。除此之外,它會為每個 SASS 檔案生成對應的 xxx.scss.d.ts 的解釋檔案,這樣在 TypeScript 中就可以正確解析,編輯器裡面也能有非常友好的程式碼提示。
- style-loader 就是把樣式使用
<style>
標籤打到頁面上。
整個過程就是,讀到一個 SCSS 檔案,丟給 sass-loader 處理成 css,然後給 typings-for-css-modules-loader 生成 xxx.scss.d.ts 檔案並且把 css 處理成 JavaScript 可以使用的樣子(這步其實是 css-loader 在處理,為啥要把 css 檔案處理成 JavaScript 可以用的樣子呢,因為 webpack 只能處理 JavaScript,所以需要做轉換),最後把處理好的給 style-loader,頁面載入的時候就會打到頁面上。
其實 loader 的本質就是 anything to JavaScript,因為 Webpack 只處理 JavaScript。記住這一點,就對為什麼要用這個 loader 那個 loader 有個清晰的認識了。
在用 TypeScript 寫 Vue 元件的時候,定義元件時,可以 require 一個 html 作為 template:
@Component({
template: require('./index.html'),
})複製程式碼
為啥可以 require html 檔案呢,因為上面的 webpack 配置中有這句:
{
test: /\.html$/,
loader: 'text-loader'
},複製程式碼
把 html 檔案用 text-loader
處理了,把 require html 變成一段 text,利用 Webpack, 模板和程式碼優雅分離。
其它
除此之外,CSS Modules 還有定義變數,繼承別的類,import 其它模組等特性,不過這些 SASS 大多也有。
最後上一個簡單的例子,TypeScript + Vue 的 Markdown 簡單編輯器。
demo 程式碼地址:
github.com/bob-chen/md
碎碎念
記錄一些所思所想,寫寫科技與人文,寫寫生活狀態,寫寫讀書心得,主要是扯淡和感悟。
歡迎關注,交流。
微信公眾號:程式設計師的詩和遠方
公眾號ID : MonkeyCoder-Life