前端進階(1) - CSS 模塊化

CSS 模塊化

CSS(Cascading Style Sheets),從誕生之初就決定了它沒法編程,甚至連解釋性語言都算不上,只能做爲一種簡單的層疊樣式表,對 HTML 元素進行格式化。css

但隨着前端的發展,前端項目已經變得愈來愈龐大和複雜,社區也一直在探索如何以一種有效的方式去管理前端的代碼(js/css/html)和資源(images, fonts, ...)。html

在這個過程當中,社區探索出了 js 的模塊化(amd, commonjs, es6),如今用 js 開發大工程已經遊刃有餘,而 css 的模塊化卻尚未特別的深刻人心。前端

1. 分組式模塊化

這是最先對 css 模塊化的實現,也是最主要的一種方式,包括如今不少組件和開發者都是用這種方式開發的。vue

分組式模塊化就是用命名的方式,以不一樣的前綴表明不一樣的含義,實現樣式分組,文件分塊,達到模塊化的目的。react

好比:webpack

# 目錄結構
|-- one/page/css/ 某個頁面的 css 目錄
    |-- common.css 通用的 css
    |-- page1/ 單頁面1
        |-- section1.css 區域1 css
        |-- section2.css 區域2 css
    |-- page2/ 單頁面2
    |-- ...
    
# common.css 文件
.c-el-1 {
    ...
}
.c-el-2 {
    ...
}    
...    
    
# page1/section1.css 文件
.page1-section1 {
    ...
}
.page1-section1 .el-1 {
    ...
}    
.page1-section1 .el-2 {
    ...
}    
...

# page1/section2.css 文件
.page1-section2 {
    ...
}
.page1-section2 .el-1 {
    ...
}    
.page1-section2 .el-2 {
    ...
}    
...

這種方式並非真正意義上的模塊化,由於沒法避免全局衝突的問題,但原生 css 並不具有編程的能力,因此這個問題是沒法避免的。儘管分組式不算真正意義上的模塊化,可是這種方式沒有脫離 css 原生的機制,因此尤爲是第三方組件在導出 css 文件時,不少都使用的是這種方式。git

好比,ant-design 導出的 css 中使用 ant- 前綴標識,mui 導出的 css 中使用 mui- 前綴標識等等。es6

1.1 最佳實踐

css 命名分組實踐的時間很長,從 css 誕生之初就有了,因此社區已經發展很成熟了,好比網易的 css 規範框架 NECH-uigithub

補充:
  • 一個 css 文件不宜過大,可使用 @import 進行文件分塊;
  • 樣式渲染儘可能不要使用 #id [attr],應儘可能使用 .class
  • 使用 js 庫操做 dom 時,儘可能不要用 .class,應儘可能用 #id data-set,如 $('#main'), $('[data-tab="1"]')
<ul>
    <li data-tab="1">tab1</li>
    <li data-tab="2">tab2</li>
</ul>
<div data-tab-container="1"></div>
<div data-tab-container="2"></div>

1.2 css 語言擴充

由於 css 不是編程語言,因此不能聲明變量、函數,不能作判斷、循環和計算,也不能嵌套,因此這就使得寫樣式是一個效率底下且又枯燥的活兒。web

爲了解決這個問題,社區在探索中主要衍生出了兩種拓展語言 lesssass,它們兼容 css,而且拓展了編程的功能,主要是帶來了如下的特性:

  • 能夠聲明變量、函數,能夠進行一些簡單的計算、判斷、循環;
  • 能夠嵌套選擇器,這樣節省了書寫的內容,也更具閱讀性;
.page1-section1 {
    ...
    
    .el-1 {
        ...
        
        .el-1-1 {
            ...
        }
    }
        
    .el-2 {
        ...
    }   
}
  • @import 避免重複導入問題,所以能夠放心大膽的導入其餘文件。

從模塊化的角度來說,lesssass 只是擴充了 css 的功能,但並無在語言的層面作模塊化,由於全局命名衝突的問題依然還在。

2. 模塊化(導出爲 js 對象)

想要讓 css 具有真正意義上的模塊化功能,暫時還不能從語言的層面來考慮,因此只能從工具的角度來實現。

目前比較好的方式是使用 js 來加載 css 文件,並將 css 的內容導出爲一個對象,使用 js 來渲染整個 dom 樹和匹配相應的樣式到對應的元素上,在這個過程當中,咱們便有機會對 css 作額外的處理,來達到模塊化的目的。

好比:

源文件

# style.css 文件
.className {
  color: green;
}

# js 文件
import styles from "./style.css";

element.innerHTML = '<div class="' + styles.className + '">Hello!</div>';

實際效果

# style.css 文件
._23_aKvs-b8bW2Vg3fwHozO {
  color: green;
}

# DOM
<div class="_23_aKvs-b8bW2Vg3fwHozO">Hello!</div>

在這個轉換過程當中,根據文件的位置、內容生成一個全局惟一的 base64 字符串,替換原來的名稱,避免了全局命名衝突的問題,這樣便達到了模塊化的目的。因此,開發的過程當中便無全局樣式衝突的問題。

# common.css 文件
.container {
    ...
}
.el1 {
    ...
}
.el2 {
    ...
}    
...    
    
# page1/section1.css 文件
.container {
    ...
}
.title {
    ...
}    
.content {
    ...
}    
...

# page2/section1.css 文件
.container {
    ...
}
.title {
    ...
}    
.content {
    ...
}
...

對 css 模塊化的定義參見 css-modules,其中對 css 書寫需求主要是:

  1. 應當用 .class,而非#id [attr](由於只有 .class 才能導出爲對象的屬性);
  2. 推薦用 .className 書寫,而非 .class-name(前者能夠經過 styles.className 訪問,後者須要經過 styles['class-name'] 才能訪問)。

更多功能能夠查看 css-modules

固然這個功能須要構建工具的支持,若是你是使用 webpack 構建工程的話,可使用 css-loader,並設置 options.modulestrue, 即可使用模塊化的功能了。

3. 模塊化(內置 js,綁定組件)

隨着前端組件化的發展,組件化框架的更新,如 reactvue,慢慢的發展爲把整個組件的資源進行封裝,並只對外暴露一個對象,而調用者無需關心組件的內部實現和資源,直接調用這個對象就夠了。

好比(以 react 爲例),一個 Welcome 組件,包括一個 js 文件、一個 css 文件、圖片:

# Welcome 組件
|-- welcome.js
|-- welcome.css
|-- images/

welcome.js 中即可以下加載(使用「導出爲 js 對象」的 css 模塊化):

import styles from './welcome.css';
import image1 from './images/1.jpg';

其實,還有另一種思路,就是將 css 內置 js 中,成爲 js 的一部分。

這樣作的目的,一是 css 的模塊化,二是直接綁定到組件上。

好比,material-uistyled-jsxjssvue style scoped 即是使用的這種方式。

這種方式的實現有不少種,這裏主要介紹一下 styled-jsx

3.1 styled-jsx

styled-jsx 的原理是根據當前文件的位置、內容生成一個全局惟一的標識,而後把這個標識追加到組件每個元素上,每個樣式選擇器上,達到模塊化的目的。

能夠參考官方文檔,查看詳細的用法,我在這裏給個例子:

3.1.1 安裝工具(babel 轉碼所需)

npm install --save styled-jsx

3.1.2 配置 babel plugins(如 .babelrc

{
  "plugins": [
    "styled-jsx/babel"
  ]
}

3.1.3 添加源文件代碼

hello.js

export default () => (
    <div className={'container'}>
        <p className={'hello'}>Hello! Hello!</p>
        <div id={'hi'}>Hi!</div>
        <style jsx>{`
          .container {
            color: blue;
          }
          p:first-child {
            color: red;
          }
          .hello {
            color: yellow;
          }
          #hi {
            color: green;
          }
        `}</style>
    </div>
)

3.1.4 轉碼

babel path/to/hello.js -d target/dir

轉碼後的文件

import _JSXStyle from 'styled-jsx/style';

export default () => (
    <div className={'jsx-234963469' + ' ' + 'container'}>
        <p className={'jsx-234963469' + ' ' + 'hello'}>Hello! Hello!</p>
        <div id={'hi'} className={"jsx-234963469"}>Hi!</div>
        <_JSXStyle styleId={"234963469"} css={".container.jsx-234963469{color:blue;}p.jsx-234963469:first-child{color:red;}.hello.jsx-234963469{color:yellow;}#hi.jsx-234963469{color:green;}"} />
    </div>
);

3.1.5 運行

實際渲染效果

<style type="text/css" data-styled-jsx="">
.container.jsx-234963469{
  color:blue;
}
p.jsx-234963469:first-child{
  color:red;
}
.hello.jsx-234963469{
  color:yellow;
}
#hi.jsx-234963469{
  color:green;
}
</style>



<div class="jsx-234963469 container">
  <p class="jsx-234963469 hello">Hello! Hello!</p>
  <div id="hi" class="jsx-234963469">Hi!</div>
</div>

4. 後續

更多博客,查看 https://github.com/senntyou/blogs

做者:深予之 (@senntyou)

版權聲明:自由轉載-非商用-非衍生-保持署名(創意共享3.0許可證

相關文章
相關標籤/搜索