こんにちは!日々のフロントエンド開発、お疲れ様です。
大規模なWebアプリケーションを開発していると、こんな絶望的な瞬間に出会ったことはありませんか?
「なんか最近、ビルド時間がやたらと遅いな……」
「このコンポーネント、どこからインポートされているのか分からないから消せない!」
「ああっ、またモジュールの循環参照(Circular Dependency)でバグが出た!」
プロジェクトが成長するにつれて、コードベースは美しかったツリー構造から、誰も全貌を把握できない「スパゲッティコード」へと変貌していきます。
実は、Webpackはビルド時に、すべてのモジュールのつながりを「Dependency Graph(依存関係グラフ)」というデータとして内部で完全に把握しています。今回は、この強力なデータをWebpackの牢獄から解放し、グラフデータベース(Neo4j)に取り込んで視覚的・数学的に解析するという、一歩進んだデータ駆動型リファクタリングの手法を伝授します。
これをマスターすれば、あなたのチームのコードベースの「見えない構造」が手に取るようにわかり、毎日のコーディングが劇的に楽になりますよ。さあ、一緒にコードの迷宮を解き明かしましょう!
—
1. なぜ「グラフデータベース」でWebpackの依存関係を解析するのか?
Webpackの標準機能や、`webpack-bundle-analyzer`のようなビジュアライザーも素晴らしいツールです。しかし、それらは「ファイルのサイズ」や「大まかな親子関係」を2Dで表示するにとどまります。
「特定のユーティリティから、どのルートコンポーネントまでパスが繋がっているか?」
「意図しない循環参照がどこに潜んでいるか?」
こうした複雑なパス探索や多対多のネットワーク解析において、ツリー構造やリレーショナルデータベースは無力です。ここで登場するのがグラフデータベース(Neo4jなど)です。モジュールを「ノード(点)」、インポート関係を「エッジ(矢印)」として表現することで、以下のような高度なクエリが瞬時に実行できるようになります。
- 「モジュールAからモジュールBに至る最短の依存パスをすべて洗い出す」
- 「循環参照を引き起こしているモジュールのループを自動検知する」
- 「特定のエントリポイントから到達不可能な『デッドコード(孤立ノード)』を特定する」
それでは、実際に手を動かしてこの解析環境を構築していきましょう。
—
2. 基礎セットアップ:WebpackからStats(統計情報)を抽出する
まずは、Webpackにビルドの全内訳をJSONファイルとして吐き出させます。これがすべての源泉データとなります。
Webpack設定の拡張 (`webpack.config.js`)
通常のビルド設定に、`stats` の詳細出力設定を追加し、JSONファイルとして保存するスクリプトを組み込みます。
const path = require(‘path’);
const webpack = require(‘webpack’);
module.exports = {
// エントリポイント(解析の起点となるファイル)
entry: ‘./src/index.js’,
output: {
filename: ‘bundle.js’,
path: path.resolve(__dirname, ‘dist’),
},
mode: ‘production’,
plugins: [
// ビルド完了時にstats.jsonをファイルとして出力するためのカスタムプラグイン
{
apply: (compiler) => {
compiler.hooks.done.tap(‘StatsJsonPlugin’, (stats) => {
const fs = require(‘fs’);
// 依存関係のグラフを含むJSONデータを取得
const statsJson = stats.toJson({
source: false, // ソースコード本体を含めると巨大化するため除外
modules: true, // モジュール一覧を含める(必須)
chunks: true, // チャンク情報を含める
assets: false, // アセット情報は今回は不要なので除外
});
// プロジェクトのルートに stats.json として書き出す
fs.writeFileSync(
path.resolve(__dirname, ‘stats.json’),
JSON.stringify(statsJson, null, 2)
);
console.log(‘✨ [Architecture] Dependency Graph stats.json generated successfully!’);
});
},
},
],
};
この設定でビルドを実行すると、プロジェクトのルートに `stats.json` が生成されます。この中には、どのファイルがどのファイルを読み込んでいるかの全情報が詰まっています。
—
3. Neo4j環境の起動とデータインポートの仕組み
次に、このJSONをグラフデータベース「Neo4j」にインポートします。
Dockerを使えば、一瞬でローカルにグラフデータベース環境を立ち上げることができます。
Docker ComposeによるNeo4jの起動 (`docker-compose.yml`)
以下の設定ファイルをプロジェクトのルートに配置してください。
version: ‘3.8’
services:
neo4j:
image: neo4j:5.11-community
container_name: webpack-graph-analyzer
ports:
- “7474:7474” # ブラウザから可視化UIにアクセスするためのポート
- “7687:7687” # Cypherクエリなどを実行するためのBoltプロトコル用ポート
environment:
- NEO4J_AUTH=neo4j/password123 # 初期ユーザー名とパスワード
- NEO4J_PLUGINS=[“apoc”] # 便利なグラフアルゴリズム拡張(APOC)を有効化
ターミナルで以下のコマンドを実行し、コンテナを起動します。
docker compose up -d
ブラウザで `http://localhost:7474` にアクセスし、ユーザー名 `neo4j`、パスワード `password123` でログインすれば、準備完了です。
—
4. HelloWorld的動作確認:モジュールをグラフ化してクエリを叩く
ここからが本番です。`stats.json` を読み込み、Neo4j上に「モジュール(Node)」と「依存関係(Relationship)」のグラフを構築します。
Pythonによるインポートスクリプト (`import_to_neo4j.py`)
JSONを手動でパースしてNeo4jに流し込むための、シンプルなPythonスクリプトを作成します。(※事前に `pip install neo4j` を実行しておいてください)
import json
from neo4j import GraphDatabase
Neo4jへの接続情報
URI = “bolt://localhost:7687”
AUTH = (“neo4j”, “password123”)
def import_webpack_stats():
# 1. Webpackが生成した stats.json を読み込む
with open(“stats.json”, “r”, encoding=”utf-8″) as f:
data = json.load(f)
driver = GraphDatabase.driver(URI, auth=AUTH)
with driver.session() as session:
# 既存のグラフデータを一度クリア(初期化)
session.run(“MATCH (n) DETACH DELETE n”)
print(“🧹 Old graph data cleared.”)
# 2. モジュールノードの作成
modules = data.get(“modules”, [])
for mod in modules:
mod_id = mod.get(“id”)
# ファイルパス(識別子)を取得。ない場合は名前を使用
mod_name = mod.get(“name”, “unknown”)
# Neo4jに Module ノードを作成
session.run(
“CREATE (m:Module {id: $id, name: $name, size: $size})”,
id=str(mod_id), name=mod_name, size=mod.get(“size”, 0)
)
print(f”📦 Imported {len(modules)} modules as nodes.”)
# 3. モジュール間の依存関係(リレーションシップ)の構築
for mod in modules:
source_id = str(mod.get(“id”))
# 各モジュールが持つ依存先(reasons や issuerId)を走査
for reason in mod.get(“reasons”, []):
target_id = str(reason.get(“moduleId”))
if target_id and target_id != “None”:
# target -> source の方向で依存関係エッジを作成
session.run(
“””
MATCH (source:Module {id: $source_id})
MATCH (target:Module {id: $target_id})
CREATE (source)-[:DEPENDS_ON]->(target)
“””,
source_id=source_id, target_id=target_id
)
print(“🔗 Dependency edges created successfully!”)
driver.close()
if __name__ == “__main__”:
import_webpack_stats()
このスクリプトを実行すると、Webpackの依存関係がそのままNeo4j上のネットワークグラフとして再現されます。
—
5. 現場で震えるほど役立つ!スパゲッティコードを解剖する実践クエリ
データがグラフ化されたら、Neo4jのブラウザ画面(`http://localhost:7474`)で「Cypher(サイファー)」と呼ばれるクエリ言語を叩いてみましょう。これが開発現場で圧倒的な威力を発揮します。
活用例1:地獄の「循環参照(Circular Dependency)」を自動検知する
コードの保守性を著しく下げる循環参照。以下のクエリを流すだけで、どのモジュールとどのモジュールが互いに依存し合っているかを一発で炙り出せます。
// モジュールAから出発して、依存関係をたどって再びモジュールAに戻ってくるループを検出
MATCH path = (m:Module)-[:DEPENDS_ON2..6]->(m)
RETURN m.name AS CircularModule, [n in nodes(path) | n.name] AS Path
LIMIT 5;
もし結果にファイル名が表示されたら、それがあなたのプロジェクトに巣食う循環参照の犯人です。ES Modulesのインポート順序依存バグの温床を、こうして視覚的かつ網羅的に特定できます。
活用例2:肥大化したコンポーネントの「被依存度(Fan-out / Fan-in)」を測定する
「この共通コンポーネントを修正したら、アプリ全体のどこに影響が出るんだろう?」という恐怖から解放されます。特定のファイルに依存している上位モジュールの数を数えてみましょう。
// 最も多くの他のモジュールから依存されている(使われている)コアなモジュールトップ10
MATCH (m:Module)<-[:DEPENDS_ON]-(dependent:Module)
RETURN m.name AS ModuleName, count(dependent) AS ReferenceCount
ORDER BY ReferenceCount DESC
LIMIT 10;
このクエリにより、「どのファイルが真のコアロジックなのか」「どのファイルが不要な結合を持っているか」がデータとして浮き彫りになります。
---
6. まとめ:データ駆動型リファクタリングでコードに秩序を
今回は、Webpackの `stats.json` とグラフデータベース(Neo4j)を組み合わせ、複雑な依存関係を可視化・解析する手法を解説しました。
- Webpackのビルド結果から `stats.json` を出力する。
- Python等を使ってNeo4jにノードとエッジとして取り込む。
- Cypherクエリを用いて、循環参照や依存の深さを数学的に解析する。
何となく「コードが汚い気がする」という感覚でリファクタリングを始めると、終わりが見えなくなります。しかし、データをグラフ構造に落とし込んで客観的な事実からアプローチすることで、メスを入れるべき場所が明確になり、自信を持ってコードを整理できるようになります。
これをマスターすれば、毎日のコーディングが劇的に楽になり、巨大なコードベースを統率する真のアーキテクトに一歩近づくことができますよ。ぜひ、あなたのプロジェクトでも試してみてください!