本文 AI 產出,尚未審核

Vue3

樣式與 CSS 管理 – PostCSS / Sass 整合


簡介

Vue 3 專案中,樣式的管理往往是開發者最容易忽視卻最關鍵的部分。單純寫原生 CSS 雖然可以快速完成 UI,但隨著專案規模擔大,樣式重複、瀏覽器相容性、維護成本 都會急速上升。

PostCSSSass 正是解決這些問題的利器:

  • 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' })]
      : []),
  ],
};

Tippostcss-preset-envstage 越低,支援的提案越多,但穩定度也會較低。建議在 開發階段 先使用 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 元件中結合 SassPostCSS 的常見寫法。

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 函式,PostCSSnesting-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-envnesting-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 插件版本不相容 編譯失敗或警告訊息 使用 npmpnpmnpm outdated 檢查,盡量保持同一 major 版本。

最佳實踐小結

  1. 先規劃樣式架構:全域變數、Mixin、Utility Class 先放在 src/styles,再在 vite.config.js 設定自動引入。
  2. 分層使用插件:Sass 處理變數、巢狀、Mixin;PostCSS 處理前綴、未來語法、壓縮。
  3. 開發 / 生產環境分離:在 postcss.config.js 中使用 process.env.NODE_ENV 判斷是否加入 cssnano
  4. 利用 CSS Modules:對於 UI library 或高度可重用的元件,使用 module 讓樣式名稱自動 hash,避免衝突。
  5. 持續檢查相容性:在 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-envstage: 0,並在 browserslist 中加入 IE 11。PostCSS 會把 :focus-visiblenesting 等新語法編譯成 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]',
  },
},

總結

  • PostCSSSass 的結合,是在 Vue 3 專案中同時兼顧 可維護性相容性效能 的最佳方案。
  • 透過 Vitecss.preprocessorOptionspostcss.config.js,我們可以在 開發階段 享受 SCSS 的高階語法,在 建置階段 自動加入前綴、壓縮檔案、使用未來 CSS。
  • 常見陷阱多與 配置路徑插件衝突作用域 有關,只要遵守 先規劃、分層使用、環境分離 的原則,就能避免大多數問題。
  • 在實務上,從 多主題切換Design Tokens老舊瀏覽器支援大型 UI 套件,PostCSS + Sass 都能提供彈性且穩定的解決方案。

掌握了上述概念與設定後,你的 Vue 3 專案將能在 樣式管理 上更上一層樓,寫出 乾淨、可擴充且跨瀏覽器一致 的 UI。祝開發順利! 🚀