從 VSCode Live Sass Compiler 遷移到 Webpack 一點心得

Eason Lin
13 min readFeb 17, 2022

--

https://unsplash.com/photos/xtvo0ffGKlI

目前有份專案的 Sass 是使用 VSCode extension Live Sass Compiler 將.scss 編譯成 .css 檔案,因為專案本身亦有使用 Webpack 去處理 JavaScript 的檔案,前陣子就花了點時間研究,並在不影響專案運作的情況下讓 Webpack 取代了 Live Sass Compiler,這篇文大致記錄如何實作。

我的解法參考一些網路上的做法拼湊而成,很可能不是最好的做法。如果有更好的做法,也希望能分享給我!

為何要改用 Webpack 來處理 .scss 檔案?

Live Sass Compiler 能正確監聽 .scss 變化並將其轉換為 .css、針對部分 CSS 屬性增加不同 prefix 藉以解決瀏覽器支援度問題、可以產生 SourceMap,其速度非常快之外,還有許多功能,本身是非常優秀的工具。

然而,基於它是 VSCode 的 extension,使用它就意味著在開發上必須使用 VSCode。

而 Webpack 本身在有所需套件的情況下,也能處理 .scss 的檔案,因此在評估及討論後,便決定針對 Webpack 設定進行修改,讓其可以一站式處理目前專案中的 .scss.js 檔。

處理上碰到的困難

看起來不是非常困難的任務,實際上在執行時其實還是有碰到一些問題:

我會使用一個基於 Create App 產出的 Webpack starter 作為範例並採用最簡易的套件庫做為開始,其中只是新增了一些 .scss 檔案作為演示,若需要做為參考,我已將其放在我的 Github 上,並分別以 master 和 finish 兩個分支作為起頭和完成後的程式碼。

使用 Live Sass Compiler 前
使用 Live Sass Compiler 後

在開始前,我們先安裝處理 .scss 所需的套件:

npm install css-loader sass-loader postcss-loader mini-css-extract-plugin sass autoprefixer --save-dev

webpack.config.js 中增加處理 .scss 的設定檔

const webpack = require("webpack");
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const jsConfig = { // 這裡完全沒有更動
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
};
const cssConfig = {
entry: {
// 輸出: 輸入
"src/assets/style/index": "src/assets/style/index.scss",
},
output: {
path: path.resolve(__dirname, ""), // webpack 預設 output 為 dist,將其改為根目錄
},
module: {
rules: [
{
test: /\.s?css$/i,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader", // 增加瀏覽器prefix用,設定檔位於根目錄 postcss.config.js
{
loader: "sass-loader",
options: {
implementation: require("sass"), // 使用 sass-loader 建議的 dart-sass
sassOptions: {
outputStyle: "expanded", // 不去空白
},
},
},
],
},
],
},
plugins: [new MiniCssExtractPlugin({})],
resolve: {
alias: {
src: path.resolve(__dirname, "./src"), // 讓設定檔可用 src/.. 作為路徑
},
},
};
module.exports = [jsConfig, cssConfig];

Live Sass Compiler 會針對瀏覽器差異為部分 CSS 增加 vendor prefix,因此我們增加 postcss.config.js

module.exports = {
plugins: [require("autoprefixer")],
};

browserlists 的設定我們採用 Live Sass Compiler 預設的

"liveSassCompile.settings.autoprefix": [
"> 1%",
"last 2 versions"
]

開啟 package.json,增加以下鍵值對:

"browserslist": [
"> 1%",
"last 2 versions"
]

於專案根目錄開啟終端機,輸入 npm run build-prod 或自訂的 production 打包指令:

成功打包了 index.scss
如果比較修改前後,會發現指缺少了斷行

接著處理會額外產出的空白 .js 檔案:

npm install webpack-fix-style-only-entries --save-dev

webpack.config.js 引入:

// 於最上方引入
const FixStyleOnlyEntriesPlugin = require("webpack-fix-style-only-entries");

並於 webpack.config.js plugins 區塊加入:

plugins: [
new FixStyleOnlyEntriesPlugin(), // 增加此plugin
new MiniCssExtractPlugin({})
],

刪除 src/assets/style/index.js,並重新打包:

Webpack 最終輸出檔案已不再包含 src/assets/style/index.js

接著我們來處理路徑問題。以此專案來說,僅有三個 .scss 檔案,輸入三組鍵值對就可以了。然而,若專案內有上百個 .scss 檔案且又包含在資料夾內,這樣做無非是增加工作量。要解決此問題,我們先安裝 glob

npm install glob --save-dev

glob 是一種尋找檔案的技術,在 VSCode 或其他編輯器上,可能曾經有使用過這樣的方式去快速尋找檔案:

以此例來說,我們希望抓出所有在 src/assets/style 中的 .scss 檔案:

const glob = require("glob");console.log(glob.sync("src/assets/style/**/*.scss"));

輸出會是:

我們希望可以輸出這樣的內容:

因此再針對其撰寫一個 toObject 函式,最後一起引入 webpack.config.js

// 最上方增加 glob 及 toObject 函式
const glob = require("glob");
function toObject(paths) {
const ret = {};
paths.forEach(function (path) {
if (/\/\_.*\.scss/.test(path)) return;
ret[path.replace(".scss", "")] = path;
});
return ret;
}
// 將 cssConfig entry 改為以下:
const cssConfig = {
entry: toObject(glob.sync("src/assets/style/**/*.scss")),
//...

重跑一次打包:

可以發現 about.css, contact.css, index.css 都成功被打包了,我們在內部增加兩層資料夾並加入一個 .scss

可以發現內層資料夾的 .scss 檔案也成功被編譯出來。最終的 webpack.config.js 會長這樣:

const webpack = require("webpack");
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const FixStyleOnlyEntriesPlugin = require("webpack-fix-style-only-entries");
const glob = require("glob");
function toObject(paths) {
const ret = {};
paths.forEach(function (path) {
if (/\/\_.*\.scss/.test(path)) return;
ret[path.replace(".scss", "")] = path;
});
return ret;
}
const jsConfig = {
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
};
const cssConfig = {
entry: toObject(glob.sync("src/assets/style/**/*.scss")),
output: {
path: path.resolve(__dirname, ""), // webpack 預設 output 為 dist,將其改為不指定
},
module: {
rules: [
{
test: /\.s?css$/i,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader", // 增加瀏覽器prefix用,設定檔位於根目錄 postcss.config.js
{
loader: "sass-loader",
options: {
implementation: require("sass"), // 使用 sass-loader 建議的 dart-sass
sassOptions: {
outputStyle: "expanded", // 不去空白
},
},
},
],
},
],
},
plugins: [new FixStyleOnlyEntriesPlugin(), new MiniCssExtractPlugin({})],
resolve: {
alias: {
src: path.resolve(__dirname, "./src"), // 讓設定檔可用 src/.. 作為路徑
},
},
};
module.exports = [jsConfig, cssConfig];

package.json 則是:

{
"name": "sass-compiler-to-webpack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"keywords": [],
"author": "",
"license": "ISC",
"scripts": {
"clean": "rm dist/bundle.js",
"build-dev": "webpack --mode development",
"build-prod": "webpack --mode production"
},
"dependencies": {},
"devDependencies": {
"autoprefixer": "^10.4.2",
"css-loader": "^6.6.0",
"glob": "^7.2.0",
"mini-css-extract-plugin": "^2.5.3",
"postcss-loader": "^6.2.1",
"sass": "^1.49.7",
"sass-loader": "^12.6.0",
"webpack": "^5.69.0",
"webpack-cli": "^4.9.2",
"webpack-fix-style-only-entries": "^0.6.1"
},
"browserslist": [
"> 1%",
"last 2 versions"
]
}

以及 postcss.config.js

module.exports = {
plugins: [require("autoprefixer")],
};

--

--

Eason Lin
Eason Lin

Written by Eason Lin

Frontend Web Developer | Books

No responses yet