Skip to content

Sass 與建置工具整合:Vite、Webpack 與元件框架

Sass 與 Vite、Webpack 的整合設定

現代專案很少直接下 sass 指令,編譯這件事會併進建置工具。好消息是:多數情況什麼設定都不用。

Vite

bash
npm i -D sass

裝完就能用:

js
// main.js
import "./styles/main.scss";
vue
<style lang="scss" scoped>
.card { padding: 16px; }
</style>

Vite 偵測到 .scss 就會自動處理,不需要外掛也不需要 loader。

全域注入變數

每個元件都要寫 @use "../abstracts" as a; 有點煩,可以讓 Vite 自動加上:

js
// vite.config.js
export default {
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@use "@/styles/abstracts" as a;`,
      },
    },
  },
  resolve: {
    alias: { "@": "/src" },
  },
};

之後每個 .scss<style lang="scss"> 都能直接用 a.space(3)

只能注入「不產生 CSS」的東西

js
// ❌ 每個元件都會多出一份 reset 的樣式
additionalData: `@use "@/styles/base/reset";`

abstracts 裡只有定義,注入幾百次都不會產生樣式;basecomponents 裡有規則,注入就會爆炸。原則見 abstracts 該放什麼

這個設定有隱形成本

從檔案本身看不出 a 是哪來的,新人會找不到。如果團隊會困惑,寧可每個檔案自己寫一行 @use

Webpack

bash
npm i -D sass sass-loader css-loader style-loader
js
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/i,
        use: [
          "style-loader",
          "css-loader",
          {
            loader: "sass-loader",
            options: {
              additionalData: `@use "@/styles/abstracts" as a;`,
            },
          },
        ],
      },
    ],
  },
};

loader 的順序是由後往前

陣列裡 sass-loader 在最後,卻是 最先 執行的。順序寫反會出現一堆看不懂的錯誤。

正式環境要把 CSS 抽成獨立檔案:

js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

// 把 style-loader 換成 MiniCssExtractPlugin.loader

Vue 單檔元件

vue
<style lang="scss" scoped>
@use "@/styles/abstracts" as a;

.card {
  padding: a.space(3);

  &__title {
    @include a.truncate;
  }
}
</style>

scoped 會給每個元素加上唯一的屬性,樣式不會外洩。搭配 BEM 時其實可以不用 scoped,類別名本身就夠獨特了。

深層選取器

scoped 的樣式選不到子元件內部,需要穿透:

scss
.wrapper :deep(.child) {
  color: red;
}

CSS Modules

scss
// Card.module.scss
.card {
  padding: 16px;
}
jsx
import styles from "./Card.module.scss";

<div className={styles.card} />

類別名會被編譯成獨特的字串,不會撞名。

CSS Modules 與 BEM 是兩套方案

用了 CSS Modules 就不需要 BEM 的長類別名了,.title 就夠。兩個一起用只會讓類別名變成 Card_card__title__x7f2q

別名讓載入路徑好看

scss
// ❌ 相對路徑一深就難讀
@use "../../../abstracts" as a;

// ✅
@use "@/styles/abstracts" as a;

Vite 用 resolve.alias,Webpack 用 resolve.alias,兩者都要 同時 讓 Sass 認得。Sass 對 ~@ 開頭路徑的處理各家工具略有不同,設定完務必實測一次。

產出的順序

元件樣式的順序由 模組的引入順序 決定,不是檔案路徑:

js
import "./styles/main.scss";   // 全域樣式先
import Card from "./Card.vue";  // 元件樣式後

不要賭順序

需要確保覆蓋關係時,靠選取器的設計而不是載入順序。順序在不同工具、不同模式下可能不一樣。

警告訊息在終端機

Sass 是在 Node 端 編譯的,@debug@warn 的輸出會印在跑 npm run dev 的那個終端機裡,不是瀏覽器的主控台。

第三方套件的棄用警告很吵的話:

js
css: {
  preprocessorOptions: {
    scss: { quietDeps: true },
  },
}

常見問題

Vite 要裝什麼才能用 Sass?

只要把 sass 裝成開發相依就好,不需要任何外掛或 loader。Vite 偵測到 .scss 就會自動處理,import 進 JavaScript 或寫在單檔元件裡都能運作。

additionalData 該注入什麼?

只注入變數與混入這類不會產生 CSS 的東西。注入會產生規則的檔案會讓每個元件都多輸出一份樣式,這是這個設定最常見的誤用。

為什麼元件裡的樣式順序和預期不同?

因為元件樣式的載入順序由模組的引入順序決定,不是由檔案路徑決定。需要確保覆蓋關係時,應該靠選取器的設計而不是賭順序。

Sass 的警告訊息在瀏覽器看不到?

因為編譯是在 Node 端進行的,訊息會印在跑開發伺服器的那個終端機裡,不是瀏覽器的主控台。

延伸閱讀