Last updated on

不要コードを納品しない — npm run check:unused の仕組みと考え方

  • Node.js
  • SCSS
  • JavaScript
  • ワークフロー
  • WordPress

案件が進むにつれ、「試したけど使わなかった SCSS」「いつの間にか参照されなくなった画像」「import だけして呼んでいない関数」が少しずつ積み重なる。

これらを除去せずに納品すると、後でメンテナンスを担当する人が余計な読み解きコストを負う。「このスタイルは今も使っているのか」を調べる時間は、不要コードがなければ発生しないはずだ。自分が半年後に戻るケースでも、別の制作者が引き継ぐケースでも、その負担は変わらない。

だから納品前・区切りのタイミングで不要コードを除去することを習慣にしている。ただし手作業で探すのは漏れが多いし時間もかかる。そこで scripts/check-unused.mjs という Node.js スクリプトを書いて、4つの視点で不要コードを自動検出できるようにした。

npm run check:unused

WordPress テンプレ・静的テンプレ・各案件リポジトリに配置して使っている。

Vite を使った静的サイトであれば、HTML から参照されていない画像はビルド出力(dist/)に含まれない。ただし WordPress テーマでは PHP テンプレートを Vite が静的解析できないため、src/assets/images/ の全画像を dist/ に出力するカスタム処理を挟んでいる——未使用画像は dist/ にもそのまま残る。どちらの構成でも SCSS の未使用セレクタはビルドでは除去されないし、src/ のソースは常に残り続ける。このスクリプトが整理するのはビルド結果ではなくソースだ。

SCSS の未使用セレクタを PurgeCSS 等で自動除去しない理由は2つある。ひとつは is-activejs-*has-* など JS が実行時に付け外しするクラスや、WordPress プラグインが出力するクラスをビルド時に判別できないこと。もうひとつは、よしあきの運用として HTML や PHP に書いたクラスはすべて SCSS にブロックを書き出すようにしているためだ。後からスタイルを追加する際、クラス名でそのまま検索してどこに書くかをすぐ見つけられるようにしている。この運用ではスタイルが空に近いブロックが意図的に存在するので、「セレクタがある=使っている」とは言えず、ツールによる自動除去とは相性が悪い。


スクリプトが見る4つのポイント

A. コメントアウトされた SCSS

// .hero { padding: 4rem; }   ← 検出される
// ここはあとで調整する       ← 検出されない(説明コメント)

src/assets/sass/ 以下の .scss を再帰走査して、// のあとに CSSコードらしいパターン が続く行を抽出する。

const CSS_CODE_RE = /^\s*\/\/\s*(@?(function|mixin|include|return|if|else|each|for)\b|[\w-]+\s*:\s+|[.#&]\w|[{}]|@media\b|@supports\b)/;

property: value のコロン+スペース、セレクタ(.foo#bar&)、@mixin などの SCSS キーワード、{}——このどれかが含まれていれば「コードが眠っている」と判定する。

B. 未使用の画像

src/assets/images/ 内のファイル名(および拡張子なし)を、PHP・HTML・SCSS・JS の全文字列に対して検索する。どこにも出てこなければ未参照と判断する。

注意点は 動的パスへの false positive だ。/images/slide-${i}.jpg のように文字列連結でパスを作っている場合、スクリプトからは見つからない。報告されたファイルは使われているかを実際のコードで確認してから削除する。

C. 未使用の JS インポート

import { gsap, ScrollTrigger } from 'gsap/all';
// ScrollTrigger をファイル内で1回も使っていなければ検出される

import { X } from '...'(名前付き)と import X from '...'(デフォルト)を正規表現で抽出し、インポート文を除いた残りのテキストに識別子が現れるかを確認する。

副作用目的の import './polyfill.js' は名前が出ないので対象外になる。

D. PHP に出てこない SCSS クラス(components/)

src/assets/sass/components/ 内で**行頭(インデントなし)**に書かれたクラスセレクタを起点に、PHP・HTML ファイルに同名の文字列があるかを調べる。

.hero-section { }         ← 行頭 → PHP に "hero-section" がなければ検出
.hero-section__title { }  ← BEM 子要素(__)→ 自動除外
.hero-section--dark { }   ← モディファイア(--)→ 自動除外
.is-active { }            ← is-* クラス → 自動除外

is-*js-*has-* のような状態クラス、BEM の __-- を含むものは自動除外する。これらは PHP テンプレートではなく JS や他の SCSS で付け外しされることが多いためだ。


出力サンプル

────────────────────────────────────────────────────────────────
 A. コメントアウトされたSCSS
────────────────────────────────────────────────────────────────
  src/assets/sass/components/_hero.scss:42
    // .hero { padding: 4rem; }

────────────────────────────────────────────────────────────────
 B. 未使用の画像
────────────────────────────────────────────────────────────────
  src/assets/images/top/top-about_deco.svg

────────────────────────────────────────────────────────────────
 C. 未使用のJSインポート
────────────────────────────────────────────────────────────────
  src/assets/js/main.js:3  → ScrollTrigger

────────────────────────────────────────────────────────────────
 D. PHPに出てこないSCSSクラス(components/)
────────────────────────────────────────────────────────────────
  src/assets/sass/components/_old-cta.scss:1  .old-cta

よくある false positive

検出内容実際は
_function.scss のコメントアウト関数ピクパ(Figma)作業時の切り替え用として意図的に残している
_setting.scss@include mq(){} コメント使い方説明のドキュメントコメント
mixin ファイル内のクラス名(コメント内の使用例)ドキュメントとして意図的に記載されているもの

これらは「なし」にならなくて正解なので、確認したら読み飛ばす。


スクリプトの実装ポイント

外部依存ゼロで Node.js 標準モジュール(node:fsnode:path)だけで動く。package.json への追加は1行だ。

"scripts": {
  "check:unused": "node scripts/check-unused.mjs"
}

ファイル走査は readdirSync の再帰関数で、node_modulesdist.git.vite は最初にスキップする。読み込みに失敗したファイルは空文字列にフォールバックするため、権限エラーでプロセスが止まらない。


スクリプトが検出しないもの

WordPress プラグイン

check:unused はコード・ファイルを対象にするが、WordPress のプラグインは対象外だ。納品前に手動で確認する。

  • デバッグ・開発用プラグイン(例: Query Monitor)— 開発中だけ必要なものは削除する
  • 途中で試して使わなかったプラグイン — 有効化・停止のまま放置されているものも削除する

不要なプラグインが残ると、後でメンテナンスする人が「これは必要なのか」を調べるコストを負う。コードの不要ファイルと同じ問題だ。

そもそも書かない

スクリプトは既存のコードを検出するが、最初から書かないことで不要な記述を減らせるケースもある。

  • reset.scss / base.scss で済む記述 — リセットやベーススタイルで既に適用されているプロパティを各コンポーネントで再宣言しない
  • 冗長なフォールバック — Flexbox・Grid が十分普及した現在、display: block を前置きする、現在は不要な -webkit- プレフィックスを書く、ブラウザデフォルト値をわざわざ明示するといった記述を避ける

使うタイミング

  • 案件の区切り(ページ完成・機能リリース後)
  • 納品前の最終クリーンアップ
  • リポジトリを次の案件のベースに流用する前

全件 なし にする義務はない。false positive を除いて残ったものを一つひとつ「消す・残す・後回し」と判断するための 検出ツール として使っている。

スクリプトは検出して報告するだけで、ファイルの削除は行わない。削除は必ずよしあき自身が判断して手動で実行する。AI に「不要なファイルを消して」と指示する運用はしない——誤削除のリスクと、false positive の見極めが人間の判断を必要とするためだ。