筆記 - CSS 預處理器與 webpack

認識 CSS preprocessor - Sass、任務管理工具 webpack,以及 CSS naming convention。

CSS 預處理器

CSS (Cascading Style Sheets) 階層樣式表,是從上而下將所有樣式設定逐條夾到 .css 文件中,但有語法重複、可維護性不佳、可讀性不佳的問題。

更有效的 CSS 撰寫與管理:CSS Preprocessor 預處理器

目前流行的 CSS 預處理器: Sass, Less, Stylus

名詞釐清

  • Sass/SCSS 為目前業界主流,SCSS (Sassy CSS) 副檔名為 .scss
  • Sass indented syntax 副檔名為 .sass 為舊版語法

編寫 .scss 檔案 透過編譯 (compile) 成 plain CSS 讓瀏覽器使用。

安裝 Sass

1
npm install sass@1.26.7

常用情境指令

編譯單一 sass 檔案為 css 檔案

1
npx sass main.scss output.css

會跑出很多檔案,其中 output.css 是編譯的結果

追蹤單一 sass 檔案並持續編譯成 css

使用 --watch 當 .scss 檔案有變動,自動編譯成 .css 檔案

1
npx sass --watch main.scss:output.css

追蹤資料夾並持續編譯成 css

追蹤整個資料夾(包含子資料夾)中的 .scss 檔案,並輸出 .css 檔案至另一個資料夾。

1
npx sass --watch scss_dir:css_dir

接下來在 scss_dir 資料夾裡的變動,都會同步到 css_dir 資料夾中。


Webpack

Webpack 與 gulp 為前端實務上常使用的任務管理工具。

Webpack 為 module bundler (模組打包工具),從進入點 entry point 中開始分析專案結構,找出每個模組間的依賴關係 dependency,並分析其中是否有瀏覽器不能直接使用的語法(例如 scss),甚至檢查程式碼中是否有引入 CSS, 圖片檔等,將他們轉換並打包在一起,最後產出瀏覽器可以識別的檔案(html, js, css)以方便部署。

  • 模組化:大型專案中,依照功能切成一個個的小 module (檔案),方便組織與管理。
  • 打包:模組會依照各自負責的功能,會有父子組件相互依賴,解析模組間的依賴關係並把最後的結果輸出,方便部署

另外 webpack 對模組有更廣泛的定義,除了 JavaScript 功能之外,另外像是

  • 透過 npm 安裝引用的第三方套件
  • 使用 CSS 預處理器與法
  • 各種圖檔格式引入

其他用途:

  • 利用各種 loader 來識別檔案類型,並轉譯成 瀏覽器可接受的語法,例如 Babel 設定來轉譯 JavaScript ES6-11 的語法
  • 打包過程中可透過 plugin 工具 (minify, uglify) 來進行程式碼優化。

現今前端框架的 CLI 工具,例如 create-react-app 或 Vue CLI,都有整合 Webpack 專案打包工作的通用的預設。

webpack 官網 | 參考文章-AC | 參考文章-教學文

webpack 開發環境建立

專案初始化

1
npm init -y

安裝套件

1
npm install webpack@4.43.0 webpack-cli@3.3.11 mini-css-extract-plugin@0.9.0 css-loader@3.5.3 sass-loader@8.0.2 
  • webpack & webpack-cli:主要的打包工具
  • css-loader & sass-loader:編譯 sass 檔案
  • mini-css-extract-plugin:抽離 css 檔案

建立設定檔

專案架構規劃如下圖:

專案架構規劃|20

root:index.html 及 webpack.config.js,
src 資料夾::編譯前的原始碼 (source)

網頁的 index.html 放在專案的第一層
Sass 設定檔放在 src/scss/main.scss
設定 webpack 進入點: src/main.js,webpack 會以這個檔案作為出發點依序去找相關連的檔案

1
2
import './scss/main.scss'
console.log('JS loaded!')

webpack 設定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// webpack.config.js
const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
]
}
]
},
plugins: [new MiniCssExtractPlugin()]
}
  • entry:進入點指向 ‘./src/main.js’
  • output:檔案編譯後的輸出位置,把 main.js 打包成 dist 資料夾裡面的 bundle.js
  • rules 裡面針對 *.scss 副檔名的檔案,使用 mini-css-extract-plugin、css-loader 及 sass-loader 三個 loader 來編譯
  • 編譯後的 scss 只會改變副檔名,檔名的部分不變 (main.css)

html 載入編輯後檔案

在 index.html 來載入預計編譯好的檔案位置(dist/bundle.js, dist/main.css)

1
2
3
4
5
6
7
8
<head>
...
<link rel="stylesheet" href="dist/main.css">
</head>
<body>
...
<script src="dist/bundle.js"></script>
</body>

設定編譯腳本 script

在 package.json 中新增一個 script

1
2
3
"scripts": {
"build": "webpack --mode production"
}

執行編譯

1
npm run build

run 完之後就可以在專案中看到 dist 資料夾,包含 js 以及被編譯過的 css


Sass 核心語法

變數與運算子

冒號進行變數指派,分號結尾。可以針對數值進行計算。
例如
Sass

1
2
3
4
5
6
7
$font-stack: Helvetica, sans-serif;
$font-size: 18px;

.comment {
font: 100% $font-stack;
font-size: 8/10*$font-size;
}

CSS

1
2
3
4
.comment {
font: 100% Helvetica, sans-serif;
font-size: 14.4px;
}

補充:考量到網頁的 accessibility,使用相對單位讓瀏覽器自行調整字體大小,以適用於不同使用者。

巢狀結構

在 sass 中可以使用巢狀語法來編寫 組合選擇符(nested combinator)例如空白、> + ~ 等,以達到更好的可讀性。
例子為針對 <article> 底下的 <h1><h2> 標籤進行設定,使文章中標題的表現與全站的標題不同。
Sass

1
2
3
4
5
6
7
8
9
10
11
article {
h1 {
font-size: 1.2em;
}
h2 {
font-size: 1.0em;
}
+div{
margin: 20px;
}
}

CSS

1
2
3
4
5
6
7
8
9
article h1 {
font-size: 1.2em;
}
article h2 {
font-size: 1em;
}
article + div {
margin: 20px;
}

檔案模組化

我們可以將檔案拆成多個,配合上妥善的命名,讓專案變得更容易管理。

Image

  • 開頭為底線 _ 的檔名在 Sass 中稱之為 partial,也就是一個低階的模組
  • 使用 @use 來引用 partial。引用名稱不包含底線與副檔名
  • 模組有自己的命名空間 (namespace) ,使用模組中的變數時,必須給命名空間加上前綴,例如 base.$primary-color

繼承與覆寫

讓多個類別都使用共同的屬性。使用 % 宣告類別,並使用 @extend 來執行繼承。若要覆寫屬性,則直接宣告即可。

例如,頁面中三種訊息要使用不同字體顏色,但同樣的邊線、padding,透過繼承(邊線與 padding)與覆寫(字體顏色)來寫程式碼。

Sass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
%message {
border: 1px solid black;
padding: 10px;
color: black;
}

.message-success {
@extend %message;
color: green;
}

.message-info {
@extend %message;
color: yellow;
}

.message-error {
@extend %message;
color: red;
}

CSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.message-error, .message-info, .message-success {
border: 1px solid black;
padding: 10px;
color: black;
}

.message-success {
color: green;
}

.message-info {
color: yellow;
}

.message-error {
color: red;
}

mixin

在物件導向程式設計中,mixin 意指一種工具形式的類別,用以附加在目標類別之上,為目標類別增添額外的方法或屬性,可以將它視作一種更彈性的繼承 (inherit) 的實作方式。

在 Sass 裡,使用 @mixin 進行宣告,使用 @include 使用 mixin。在以下例子中,我們創建了一個名為 font-setting 的 mixin

Sass

1
2
3
4
5
6
7
8
@mixin font-setting($size, $weight, $color) {
font-size: $size;
font-weight: $weight;
color: $color;
}

article { @include font-setting(1.0em, normal, black); }
footer { @include font-setting(0.8em, bolder, grey); }

CSS

1
2
3
4
5
6
7
8
9
10
11
article {
font-size: 1em;
font-weight: normal;
color: black;
}

footer {
font-size: 0.8em;
font-weight: bolder;
color: grey;
}

推薦教學影片 - Learn Sass In 20 Minutes


CSS naming convention

命名慣例透過 class name 了解結點功能或是帶有什麼屬性,以利多人協作。

主流命名慣例分為:

  • OOCSS (Object oriented CSS):物件導向命名法,例如 Bootstrap 中 .btn 代表按鈕的樣式,d-flex 代表以 flex 方式排版
  • SMACSS (Scalable and Modular Architecture for CSS):明確地透過結構模組化來命名,如 Base、Layout、Module、State、Theme
  • BEM (Block Element Modifier):透過 BEM 三者來命名,可以避免樣式衝突,確保每個組件名字都是獨一無二的,同時能以透過觀察 class 名稱,就能了解 CSS 階層架構,例如 list__item--hover 為滑鼠滑過表單子項目時的樣式
    • Block: 指在 Web 開發中的模組,每個 Block 在邏輯和功能上都應該是互相獨立的
    • Element: 相依於 Block 中的一環,不能單獨使用,以 ‘__’ 連接 Block
    • Modifier: 用於定義 Block 或 Element 的外觀、狀態或類型,以 ‘–’ 連接對象
1
2
3
4
<form class="order-form">
<input class="order-form__name-input">
<input class="order-form__name-input--disabled" disabled>
</form>

由於 BEM 命名會產生很長的類別名稱,在 Sass 中可以透過 & 符號帶入父層名稱,來使得代碼更加簡潔:
Sass

1
2
3
4
5
6
7
8
9
.order-form {
width: 50%;
&__name-input { // 等同 .order-form__name-input
width: 50%;
&--disabled { // 等同 .order-form__name-input--disabled
width: 50%;
}
}
}

CSS

1
2
3
4
5
6
7
8
9
.order-form {
width: 50%;
}
.order-form__name-input {
width: 50%;
}
.order-form__name-input--disabled {
width: 50%;
}