Vue3
樣式與 CSS 管理 – PostCSS / Sass 整合
簡介
在 Vue 3 專案中,樣式的管理往往是開發者最容易忽視卻最關鍵的部分。單純寫原生 CSS 雖然可以快速完成 UI,但隨著專案規模擔大,樣式重複、瀏覽器相容性、維護成本 都會急速上升。
PostCSS 與 Sass 正是解決這些問題的利器:
- PostCSS 以插件機制為核心,讓你在編譯階段自動加入前綴、使用未來 CSS 語法、進行 CSS 壓縮等。
- Sass(SCSS)則提供變數、巢狀、Mixin、函式等高階語法,讓 CSS 更具可讀性與可維護性。
本文將說明如何在 Vue 3(以 Vite 為建構工具)中同時整合 PostCSS 與 Sass,並提供實作範例、常見陷阱與最佳實踐,幫助你在真實專案中快速上手、穩定運行。
核心概念
1. 為什麼同時使用 PostCSS 與 Sass
| 功能 | PostCSS | Sass (SCSS) |
|---|---|---|
| 語法擴充 | 使用插件(如 postcss-preset-env)支援未來 CSS |
變數、巢狀、Mixin、函式 |
| 瀏覽器相容 | autoprefixer 自動加上前綴 |
無直接相容功能 |
| 檔案大小 | cssnano 壓縮、移除無用規則 |
只負責語法轉譯 |
| 開發體驗 | 支援 CSS Modules、CSS-in-JS 等 | 讓樣式更模組化、易讀 |
兩者互補:Sass 讓開發者寫出結構化、可重用的樣式;PostCSS 則在編譯階段完成瀏覽器相容、優化與未來語法的轉換。
2. 在 Vite 中安裝與設定
2.1 安裝必要套件
npm install -D sass postcss postcss-preset-env autoprefixer cssnano
sass:提供 SCSS 編譯器。postcss:核心套件。postcss-preset-env:讓你使用最新 CSS 語法,會自動轉換成兼容的舊版語法。autoprefixer:自動加上瀏覽器前綴。cssnano:生產環境壓縮 CSS。
2.2 建立 postcss.config.js
// postcss.config.js
module.exports = {
plugins: [
// 1️⃣ 讓你可以使用未來 CSS (如 :focus-visible, nesting 等)
require('postcss-preset-env')({
stage: 1, // 啟用 stage 1 的提案
features: {
'nesting-rules': true, // 開啟 CSS 巢狀規則
},
}),
// 2️⃣ 自動加上前綴
require('autoprefixer')(),
// 3️⃣ 生產環境壓縮 (只在 build 時啟用)
...(process.env.NODE_ENV === 'production'
? [require('cssnano')({ preset: 'default' })]
: []),
],
};
Tip:
postcss-preset-env的stage越低,支援的提案越多,但穩定度也會較低。建議在 開發階段 先使用stage: 1,若遇到不穩定的語法再調整。
2.3 在 vite.config.js 中引入 Sass 全域變數
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'path';
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
css: {
// 讓所有 *.vue 中的 <style lang="scss"> 自動引入此檔案
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/variables.scss";`,
},
},
},
});
additionalData會在每個 SCSS 檔案的最前面自動插入內容,省去每次手動@import。
3. 實作範例
以下示範在 Vue 3 元件中結合 Sass 與 PostCSS 的常見寫法。
3.1 基本 SCSS 元件
<!-- src/components/Button.vue -->
<template>
<button class="btn" :class="{ primary: isPrimary }">
<slot />
</button>
</template>
<script setup>
import { ref } from 'vue';
const isPrimary = ref(true);
</script>
<style lang="scss" scoped>
/* 變數來自 global variables.scss */
$primary-color: #409eff;
$border-radius: 4px;
.btn {
display: inline-block;
padding: 0.5rem 1rem;
border: none;
border-radius: $border-radius;
background-color: #eee;
cursor: pointer;
transition: background-color 0.2s;
// 使用 Sass 巢狀 + PostCSS nesting (stage 1) 皆可
&:hover {
background-color: darken(#eee, 5%);
}
&.primary {
background-color: $primary-color;
color: #fff;
&:hover {
background-color: lighten($primary-color, 5%);
}
}
}
</style>
說明:
lang="scss"讓 Vue 內建的sass編譯器處理。scoped只會在此元件內產生唯一的屬性選擇器,避免樣式外洩。darken/lighten為 Sass 函式,PostCSS 的nesting-rules會在編譯後把巢狀寫法轉成普通 CSS。
3.2 使用 PostCSS autoprefixer
/* src/styles/mixins.scss */
@mixin flex-center {
display: flex;
justify-content: center;
align-items: center;
}
/* src/components/Card.vue */
<template>
<div class="card">
<slot />
</div>
</template>
<style lang="scss" scoped>
@import "@/styles/mixins.scss";
.card {
@include flex-center;
width: 300px;
height: 200px;
background: #fff;
border: 1px solid #ddd;
border-radius: 8px;
/* 下面的屬性會被 autoprefixer 自動加上 -webkit- 前綴 */
user-select: none;
}
</style>
編譯後的 CSS(npm run build)會變成:
.card {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
3.3 使用 postcss-preset-env 的未來語法
/* src/components/Alert.vue */
<template>
<div class="alert">
<slot />
</div>
</template>
<style lang="scss">
.alert {
padding: 1rem;
background: #fef3c7;
border-left: 4px solid #f59e0b;
/* 使用 CSS 的 :is() 選擇器(stage 1) */
:is(&, & *) {
font-family: system-ui, sans-serif;
}
}
</style>
在支援度較低的瀏覽器(如 IE11)中,postcss-preset-env 會把 :is(&, & *) 轉成等價的普通選擇器,確保樣式仍然正確。
3.4 全域 SCSS 變數與函式
/* src/styles/variables.scss */
$color-primary: #42b983;
$color-success: #28a745;
$spacing-unit: 0.5rem;
// 產生間距工具類
@function spacing($n) {
@return $n * $spacing-unit;
}
在任意元件中直接使用(因為在 vite.config.js 已設定 additionalData):
<style lang="scss" scoped>
.box {
margin: spacing(2); // 產生 1rem
background: $color-primary;
}
</style>
3.5 結合 CSS Modules(PostCSS)
<!-- src/components/Badge.vue -->
<template>
<span :class="$style.badge">{{ label }}</span>
</template>
<script setup>
defineProps({ label: String });
</script>
<style module lang="scss">
.badge {
display: inline-block;
padding: 0.2rem 0.5rem;
background: $color-success;
color: #fff;
border-radius: 2px;
font-size: 0.75rem;
}
</style>
使用 module 後,Vite 會自動把樣式編譯成 CSS Modules,而 PostCSS 的插件(如 autoprefixer)仍會在最後階段作用於產出的 CSS。
常見陷阱與最佳實踐
| 陷阱 | 可能的結果 | 解決方案 / 最佳實踐 |
|---|---|---|
| Sass 與 PostCSS 同時處理巢狀 | 產出重複或錯誤的 CSS(兩次嵌套) | 只保留一方的巢狀功能:建議在 Sass 中寫巢狀,使用 postcss-preset-env 的 nesting-rules 只在 純 CSS 時啟用。 |
| 全域 SCSS 變數未被正確載入 | 編譯錯誤 Undefined variable |
確認 vite.config.js 中的 additionalData 路徑正確,且檔案使用 UTF‑8 編碼。 |
| autoprefixer 產生過多前綴 | 打包檔案過大、編譯速度變慢 | 在 browserslist 中明確列出目標瀏覽器,避免過度支援舊版瀏覽器。 |
scss 檔案未加 lang="scss" |
Vite 會把內容當成純 CSS 處理,導致 Sass 語法錯誤 | 永遠 在 <style> 標籤上加上 lang="scss"(或 lang="sass")。 |
| CSS Modules 與 Scoped 同時使用 | 樣式作用範圍不明,容易衝突 | 若要使用 CSS Modules,請移除 scoped,讓模組化管理取代作用域限制。 |
| PostCSS 插件版本不相容 | 編譯失敗或警告訊息 | 使用 npm 或 pnpm 的 npm outdated 檢查,盡量保持同一 major 版本。 |
最佳實踐小結
- 先規劃樣式架構:全域變數、Mixin、Utility Class 先放在
src/styles,再在vite.config.js設定自動引入。 - 分層使用插件:Sass 處理變數、巢狀、Mixin;PostCSS 處理前綴、未來語法、壓縮。
- 開發 / 生產環境分離:在
postcss.config.js中使用process.env.NODE_ENV判斷是否加入cssnano。 - 利用 CSS Modules:對於 UI library 或高度可重用的元件,使用
module讓樣式名稱自動 hash,避免衝突。 - 持續檢查相容性:在
package.json加入browserslist,讓 autoprefixer 與 postcss-preset-env 依據實際需求產出前綴。
實際應用場景
1. 多主題(Theme)切換
- 需求:根據使用者選擇切換深色/淺色主題,且所有元件的顏色、間距都要同步變更。
- 做法:在
variables.scss中定義 CSS 變數,並用 Sass 產生對應的主題檔案。
/* src/styles/theme.scss */
:root {
--color-primary: #42b983;
--color-bg: #fff;
}
[data-theme="dark"] {
--color-primary: #ffcc00;
--color-bg: #1e1e1e;
}
在 SCSS 中使用 var(--color-primary),PostCSS 會在編譯時保留變數,讓瀏覽器在切換 data-theme 時即時更新樣式。
2. 設計系統(Design Tokens)
- 需求:設計師提供一套 Design Tokens(顏色、字體、間距),開發者需在程式碼中直接引用。
- 做法:把 token 轉成 SCSS 變數或 CSS 自訂屬性,並透過
additionalData讓所有元件自動取得。
// tokens.json
{
"color": {
"primary": "#409eff",
"danger": "#f56c6c"
},
"spacing": {
"sm": "4px",
"md": "8px",
"lg": "16px"
}
}
使用簡單的腳本(如 node scripts/generate-scss.js)把 JSON 轉成 variables.scss,再在 Vite 中自動載入。
3. 老舊瀏覽器支援
- 需求:必須支援 IE11(公司內部系統仍在使用)。
- 做法:在
postcss.config.js設定postcss-preset-env的stage: 0,並在browserslist中加入IE 11。PostCSS 會把:focus-visible、nesting等新語法編譯成 IE 能理解的 CSS。
// package.json
{
"browserslist": [
"ie 11",
"last 2 Chrome versions",
"last 2 Firefox versions"
]
}
4. 大型 UI 元件庫(如 Element Plus)
- 需求:建立自訂化的 UI 套件,讓使用者可以自行覆寫樣式。
- 做法:使用 CSS Modules + Sass,把每個元件的樣式獨立成
.module.scss,再透過 PostCSS 的postcss-modules插件產生對應的 TypeScript 定義檔,保證型別安全。
npm install -D postcss-modules
在 vite.config.js 中加入:
css: {
modules: {
generateScopedName: '[name]__[local]___[hash:base64:5]',
},
},
總結
- PostCSS 與 Sass 的結合,是在 Vue 3 專案中同時兼顧 可維護性、相容性 與 效能 的最佳方案。
- 透過 Vite 的
css.preprocessorOptions與 postcss.config.js,我們可以在 開發階段 享受 SCSS 的高階語法,在 建置階段 自動加入前綴、壓縮檔案、使用未來 CSS。 - 常見陷阱多與 配置路徑、插件衝突、作用域 有關,只要遵守 先規劃、分層使用、環境分離 的原則,就能避免大多數問題。
- 在實務上,從 多主題切換、Design Tokens、老舊瀏覽器支援 到 大型 UI 套件,PostCSS + Sass 都能提供彈性且穩定的解決方案。
掌握了上述概念與設定後,你的 Vue 3 專案將能在 樣式管理 上更上一層樓,寫出 乾淨、可擴充且跨瀏覽器一致 的 UI。祝開發順利! 🚀