Astroにおけるscssのglob機能みたいなものを作ってみた
一時期twitterのTLで埋め尽くされた、静的ビルドツールであるAstro。
コンポーネントで完結し、同じクラス名を指定しても勝手にスコープするので上書きされる心配もありません。
そもそもscssを使用する想定ではないためか、Viteのようにglobできるプラグインがありません。
ただ個人的にはscssは使用したいのです。
こちら試行するにあたり、ふりふぁんさんの環境をお借りしました。ありがとうございます。
globできない問題
例えばViteではstyle.scssでprojectフォルダ内のscssファイルを読み込みたい場合は以下のようにuseして終わるだけなのですが
@use "project";
Astroではこれだとエラーが出てしまうので、projectフォルダ内に_index.scssを作成し以下のようにファイルを指定する必要があります。
@use "hoge";
@use "fuga";
@use "hige";
//以下ファイルが増えるたび手動で追加
ファイル数が少なければそこまで手間ではないものの、増えた時は記述を追加する必要があり、また削除した際は@useも削除しないとエラーが出るので出来れば自動化したところです。
そこで「ないなら作ってしまえ」ということで、作りました。
階層
今回階層は以下のようにしています
詳細はふりふぁんさんの記事をご参照ください
/public
/src
├─┬─ /components
│ ├─ /layouts
│ ├─ /pages
│ └─ /scss ─┬─ /project ─┬─ _index.scss
│ ... ├─ _hoge.scss
│ ├─ _fuga.scss
│ ├─ _hige.scss
│ ...
│
├─ astro.config.mjs
├─ package.json
...
コード
const { log } = console;
import { defineConfig } from "astro/config";
import path from "path";
import fs from "fs";
import chokidar from "chokidar";
//_index.scss書き込み用変数
const preDir = "./"; //ディレクトリ接頭辞
const DIR_SCSS = "src/scss"; //ディレクトリ接頭辞以降のパス
const SCSS_PATH = preDir + DIR_SCSS; //scssフォルダまでのパス
const splitScss = (/(_|.scss)/); //split用正規表現 ".scss"
let USE_STRINGS = ""; //書き込み用ファイル
/**
* fsによる_index.scss書き込み関数
* @param {*} dirPath path.joinの第一引数
*/
const writeUSE_STRINGS = (dirPath) => {
fs.writeFileSync(
path.join(dirPath, '_index.scss'), //対象ファイル
USE_STRINGS //書き込む文字列
);
}
/**
* 対象フォルダ内のscssを@useした_index.scssを作成する関数
* @param {*} dir
*/
const writeIndexSCSS = (dir) => {
//USE_STRINGSリセット
USE_STRINGS = "";
//指定フォルダへのパス(./からの相対パス)
const DIR_PATH = `${SCSS_PATH}${dir}`;
//ファイル一覧取得
const files = fs.readdirSync(DIR_PATH);
//_index.scss以外のscssファイルの@useをUSE_STRINGSに追加する
files.forEach(file => {
const fileName = file.split(splitScss)[2];
if (fileName !== "index") {
USE_STRINGS += `@use "${fileName}";` + "\n";
}
});
//_index.scssにUSE_STRINGSの内容を書き込む
writeUSE_STRINGS(DIR_PATH);
}
/**
* chokidarを使ったファイル追加・削除の検知
* @param {*} dir
*/
const watcherFunc = (dir) => {
//指定フォルダへのパス(./からの相対パス)
const DIR_PATH = `${SCSS_PATH}${dir}`;
//split用
const splitText = DIR_PATH.split("./")[1] + "/_";
//chokidarの初期化
const watcher = chokidar.watch(DIR_PATH, {
ignored: /[\/\\]\./,
persistent: true
});
watcher.on("ready", () => {
//準備完了
log("ready watching by chokidar...");
//ファイルの追加
watcher
.on('add', scss => {
//追加する@useの記述
const fileName = scss.split(splitText)[1].split(".scss")[0];
const usePath = `@use "${fileName}";` + "\n";
//USE_STRINGSの更新
USE_STRINGS += usePath;
//_index.scssにUSE_STRINGSの内容を書き込む
writeUSE_STRINGS(DIR_PATH);
log("added scss file and added @use to _index.scss!")
});
//ファイル削除
watcher
.on('unlink', scss => { //削除検知後、_index.scssを再生成する
//削除する@useの記述
const fileName = scss.split(splitText)[1].split(".scss")[0];
const usePath = `@use "${fileName}";` + "\n";
//USE_STRINGの更新
USE_STRINGS = USE_STRINGS.replace(usePath, "");
//_index.scssにUSE_STRINGSを書き込む
writeUSE_STRINGS(DIR_PATH);
log("deleted scss file and deleted @use from _index.scss!")
})
// watcher.on("all", (event, path) => log(event, path))
});
}
/**
* _index.scssの@use自動作成とファイルの追加・削除の検知
*
* @param {*} dir scssフォルダ以降のパス(/から記述すること)
*/
const indexScssUse = (dir) => {
writeIndexSCSS(dir);
watcherFunc(dir);
}
//@use自動生成の対象フォルダを追加する(src/scss以降のパス)
indexScssUse("/project");
export default defineConfig({
...
});
何やってるか
indexScssUse("/hoge")
により、npm時にsrc/scss/hoge
内で関数を実行(以下はこの関数内で勝手に実行される)
1. writeIndexSCSS
でsrc/scss/hoge
内のscssファイルを取得し、@useの形に成形し_index.scss
を作成/上書きする
2. scssファイル追加時はwatcherFunc
のadd
で対象記述を追加した_index.scss
を生成する
3. scssファイル削除時はwatcherFunc
のunlink
で対象記述を削除後、_inde.scss
を再生成する
fs.watchだと延々ループに陥りうまくいかなかったのでchokidarを使用しました
あくまでこの環境で使えるものであり、scssの中でさらにフォルダ分けしてたりする場合はこのままだと動きません
Discussion