🧰

サイトの独自アイコン、全部"社内完結"で管理してみた ─ Nodeを使ってSVG → SCSS自動生成

に公開

📝 前書き

これまで長年 icomoon を使ってアイコンフォントを管理してきました。
便利ではあるのですが、結局は 外部ツール依存 ですし、更新のたびに手間がかかるのが正直つらい……。

さらに最近は Figma で作成したマスターデータから、
Variables 管理されたアイコンをそのまま出して使いたい、というニーズも強くなってきました。

「外部サービスに頼らず、もっと 楽に管理したい
──そんな思いから、今回の Node.js を使った SVG → SCSS 自動生成システムを作りました。

これにより

  • 外部ツール不要
  • アイコンは color に追従して色変更可能
  • 更新は SVG を置くだけ

という、数年単位で安心して使えるアイコン管理が実現しました。

🖥 前提環境

  • Node.js v20以上
  • パッケージ追加不要(標準モジュールのみで完結)
  • プロジェクト直下で以下を実行するだけ:
node ./scripts/export-icons.mjs

📁 ディレクトリ例

project/
├─ assets/
│  └─ icons/
│     └─ svg/           ← 入力SVGを置く
├─ scripts/
│  └─ export-icons.mjs  ← 変換スクリプト
└─ styles/
   └─ _icons.scss       ← 自動生成されるSCSS

🎯 ゴール

  • ./assets/icons/svg/*.svg をスキャン
  • width / height を削除、fill を除去
  • ルートに fill="currentColor" を付与(JS①)
  • Data URI 化して SCSS に書き出し(JS②, SCSS①)
  • .i-xxx クラスでそのまま使える
  • アイコン色は CSS の color に追従

export-icons.mjs

#!/usr/bin/env node
// scripts/export-icons.mjs
import fs from "node:fs/promises";
import path from "node:path";

const SRC_DIR = "./assets/icons/svg";   // 入力SVG
const OUT_FILE = "./styles/_icons.scss"; // 出力SCSS

function normalizeSvg(svg) {
  return svg
    // XML宣言/コメント除去
    .replace(/<\?xml[\s\S]*?\?>/g, "")
    .replace(/<!--[\s\S]*?-->/g, "")
    // <svg> の width/height を削除
    .replace(/\swidth="[^"]*"/gi, "")
    .replace(/\sheight="[^"]*"/gi, "")
    // すべての fill を削除
    .replace(/\sfill="[^"]*"/gi, "")
    // <svg> に fill="currentColor" を付与
    .replace(/<svg\b/i, "<svg fill='currentColor'");
}

function toDataUri(svg) {
  return `url("data:image/svg+xml;utf8,${svg
    .replace(/"/g, "'")
    .replace(/%/g, "%25")
    .replace(/#/g, "%23")
    .replace(/</g, "%3C")
    .replace(/>/g, "%3E")}")`;
}

function buildScss(icons) {
  const header = `// AUTO-GENERATED: Do not edit
.i {
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -.125em;
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
}
`;
  const body = icons.map(({ name, svg }) => {
    const base = name.replace(/\.svg$/i, "");
    return `.i-${base} { background-image: ${toDataUri(svg)}; }`;
  }).join("\n");
  return header + body + "\n";
}

async function main() {
  const files = (await fs.readdir(SRC_DIR))
    .filter(f => f.toLowerCase().endsWith(".svg"))
    .sort();

  const icons = [];
  for (const f of files) {
    const raw = await fs.readFile(path.join(SRC_DIR, f), "utf8");
    icons.push({ name: f, svg: normalizeSvg(raw) });
  }

  await fs.mkdir(path.dirname(OUT_FILE), { recursive: true });
  await fs.writeFile(OUT_FILE, buildScss(icons), "utf8");
  console.log("done:", OUT_FILE);
}

main();

💡 仕上がりイメージ

入力 SVG(ビフォー):

<svg width="24" height="24" viewBox="0 0 24 24" fill="#333" xmlns="http://www.w3.org/2000/svg">
  <path d="M10 18a8 8 0 1 1 5.292-14.06L22 10.65l-1.35 1.35-6.3-6.3A8 8 0 0 1 10 18z"/>
</svg>

整形後(アフター):

<svg viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
  <path d="M10 18a8 8 0 1 1 5.292-14.06L22 10.65l-1.35 1.35-6.3-6.3A8 8 0 0 1 10 18z"/>
</svg>

生成された SCSS(SCSS①):

.i {
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -.125em;
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
}
.i-search {
  background-image: url("data:image/svg+xml;utf8,<svg%20...%3E...</svg>");
}

🧭 実際の使い方

<span class="i i-search" aria-hidden="true"></span>
<button class="btn">
  <span class="i i-mail" aria-hidden="true"></span>お問い合わせ
</button>
.btn {
  font-size: 16px;   // ← アイコンの基準サイズ
  color: #0b6;       // ← アイコン色も追従
  display: inline-flex;
  align-items: center;
  gap: .5em;

  .i { font-size: 1.25em; } // ちょい大きめ
}

✅ よくある詰まりポイント

  • 色が変わらない → 生成 SVG に fill="currentColor" が入っているか確認
  • 縦横比が崩れる → 入力 SVG に viewBox が必須
  • にじむ → background-size: contain; を維持し、font-size で調整
  • mask-imageとの違い → mask は background-color 指定が必要。currentColor 方式は color 連動で直感的

🎬 まとめ

  • 新規は .i-xxx を素直に本体直書き
  • 既存移行時のみ ::before 互換で対応
  • 外部ツール不要、Node v20 以上で SVG → SCSS 自動生成
  • これで「数年は放置できる安心アイコン管理」へ 🚀

🔗 参考リンク

Discussion