SCSSっぽい連想配列からCSSを生成するTypeScript関数
CSSをTypeScriptの連想配列で記述できるようにするやつ
かなり大雑把に作ったので、対応していない構文もあるかもしれない
記述した連想配列は、toCSSText()でCSS形式の文字列に変換することができる
SCSSっぽく入れ子にすることもできる
&をセレクタに入れるのにも対応している
できないこと
SCSS特有の構文(変数や関数など)には対応していないので注意
あくまでCSSを楽に書く程度の感覚でしか作っていないので
そこらへんを使いたいのであれば、TypeScriptやJavaScriptの構文でどうにかしてください
入力補完には対応していない
プロパティ等を型に記述していないため
@import構文などの{}に囲まれていない記述にも対応していない
これはそのうち直すかも
関連リンク
GitHub版
#github.com/MijinkoSD/array2cssText.ts
ドキュメント
呼び出し例
code:sample.ts
/// <reference no-default-lib="true" />
/// <reference lib="es2022" />
/// <reference lib="dom" />
import { Style, toCSSText } from "https://scrapbox.io/api/code/Mijinko/SCSSっぽい連想配列からCSSを生成するTypeScript関数/style.ts";
const styles: Style = {
".conteiner": {
"background-color": "#333",
"color": "white",
"&.flex": {
"display": "flex",
},
".button": {
"height": "2em",
"width": "auto",
"border-radius": "5em",
},
},
};
console.log(toCSSText(styles));
ソースコード本体
code:style.ts
/// <reference no-default-lib="true" />
/// <reference lib="es2022" />
/// <reference lib="dom" />
/**
* CSSで使用するスタイルを設定します。 \
* SCSS風にセレクタを入れ子にすることも可能です。
* - "親セレクタ"の中に"子セレクタ"を入れ子にした場合、テキスト化した後のセレクタは"親セレクタ 子セレクタ"となります。
* - "子セレクタ"にアンパサンド(&)を入れた場合、全てのアンパサンドが"親セレクタ"に置換されます。
* - 例1:"&子セレクタ" -> "親セレクタ子セレクタ"(空白無しで連結している)
* - 例2:& + & -> "親セレクタ + 親セレクタ"
*
* ### 設定例
* `js
* {
* "セレクタ": {
* "プロパティ": "値",
* "プロパティ2": "値",
* "子セレクタ": {
* "プロパティ": "値",
* :
* }
* :
* }
* }
* `
*/
export interface Style {
K: string: ChildStyle;
}
export interface ChildStyle {
K: string: number | string | ChildStyle;
}
/**
* Style型のオブジェクトを受け取り、<style>要素内で使われるようなCSSのテキストに変換します
*/
export function toCSSText(style: Style): string {
let cssText = "";
for (const k, v of Object.entries(style)) {
cssText += generateRuleText(v, k);
}
return cssText;
}
/** 再帰呼び出しをしたいのでCSSテキストを生成する関数を分けた */
function generateRuleText(
style: ChildStyle,
parentSelector?: string,
): string {
let cssText = "";
const innerStyles: {
name: string;
style: ChildStyle;
}[] = [];
const innerProperties: {
name: string;
value: number | string;
}[] = [];
for (const k, v of Object.entries(style)) {
if (typeof v === "number" || typeof v === "string") {
innerProperties.push({ name: k, value: v });
} else {
innerStyles.push({ name: k, style: v });
}
}
if (innerProperties.length > 0) {
const selector = parentSelector;
let defines = "";
for (const v of innerProperties) {
defines += ${v.name}:${v.value};;
}
cssText += ${selector}{${defines}};
}
if (innerStyles.length > 0) {
for (const v of innerStyles) {
const selector = makeSelector(v.name, parentSelector);
cssText += generateRuleText(v.style, selector);
}
}
return cssText;
}
/**
* セレクタ部分を生成する関数
*/
function makeSelector(selector: string, parentSelector?: string): string {
const p = parentSelector === undefined ? "" : parentSelector.trim();
const s = selector.trim();
if (s.includes("&")) {
return s.replace("&", p);
} else {
return p + " " + s;
}
}
余談
結局使われることの無かった哀れなコードをここに置いておく
code:style.extra.ts
function isStyle(obj: unknown): obj is Style {
if (typeof obj !== "object") return false;
if (obj === null) return false;
for (const k, v of Object.values(obj)) {
if (typeof k !== "string") return false;
if (typeof v === "object") {
if (!isChildStyle(v)) return false;
} else return false;
}
return true;
}
function isChildStyle(obj: unknown): obj is ChildStyle {
if (typeof obj !== "object") return false;
if (obj === null) return false;
for (const k, v of Object.values(obj)) {
if (typeof k !== "string") return false;
if (typeof v === "number" || typeof v === "string") continue;
else if (typeof v === "object") {
if (!isChildStyle(v)) return false;
} else return false;
}
return true;
}
// deno-lint-ignore ban-types
type UnknownObject<T extends object> = {
P in keyof T: unknown;
};