Skip to content

Replace iconify/json with build-time icon fetching (Composer plugin) #498

@titouanmathis

Description

@titouanmathis

Problem

The iconify/json dependency is ~690MB because it bundles all icon sets. Most projects only use 10-100 icons, making this extremely wasteful.

// Current dependencies
"iconify/json": "^2.2",        // ~690MB
"iconify/json-tools": "^1.0"

Proposed Solution

Replace runtime icon lookup with a build-time approach:

  1. Scan templates for meta_icon() calls during composer install/update
  2. Fetch only used icons from the Iconify API
  3. Save them locally as SVG files
  4. Read from local files at runtime

How It Would Work

composer install
    ↓
┌─────────────────────────────────────┐
│  studiometa/ui Composer Plugin      │
│                                     │
│  1. Scan templates for meta_icon()  │
│  2. Extract icon references         │
│  3. Diff with existing icons        │
│  4. Fetch new icons from API        │
│  5. Save SVGs to assets/icons/      │
└─────────────────────────────────────┘
    ↓
assets/icons/
├── mdi/
│   ├── home.svg
│   └── arrow-right.svg
└── heroicons/
    └── chevron-down.svg

Size Comparison

Approach Vendor Size Example (50 icons)
iconify/json 690MB 690MB
Build-time fetching ~5KB plugin ~250KB

Implementation

1. Change package type to composer-plugin

{
    "type": "composer-plugin",
    "require": {
        "php": "^8.1",
        "composer-plugin-api": "^2.0",
        "studiometa/twig-toolkit": "^2.1.1",
        "twig/twig": "^3.19.0"
    },
    "extra": {
        "class": "Studiometa\\Ui\\Composer\\Plugin"
    }
}

2. Add Composer plugin classes

packages/
└── composer-plugin/
    ├── Plugin.php        # Hooks into post-install/post-update
    ├── Config.php        # Reads from composer.json extra
    ├── IconScanner.php   # Scans templates for meta_icon() calls
    ├── IconFetcher.php   # Fetches from Iconify API in batches
    └── IconStorage.php   # Saves/manages local SVG files

3. Modify meta_icon() to read local files

public function run(Environment $env, string $icon): string
{
    [$prefix, $name] = explode(:, $icon);

    // Try local file first
    $localPath = sprintf(%s/%s/%s.svg, $this->iconPath, $prefix, $name);
    if (file_exists($localPath)) {
        return file_get_contents($localPath);
    }

    // Fallback: try iconify/json if installed (backward compat)
    if (class_exists(\Iconify\JSONTools\Collection::class)) {
        return $this->fromIconifyJson($prefix, $name, $env);
    }

    return $env->isDebug() 
        ? "<!-- Icon '$prefix:$name' not found. Run 'composer ui:icons' -->" 
        : "";
}

4. Configuration via composer.json

{
    "extra": {
        "studiometa/ui": {
            "icons": {
                "enabled": true,
                "output": "assets/icons",
                "scan": ["templates", "app"],
                "include": ["mdi:loading"],
                "exclude": ["mdi:test-*"]
            }
        }
    }
}

5. CLI commands

composer ui:icons          # Sync icons (runs automatically)
composer ui:icons:scan     # Show detected icons (dry-run)
composer ui:icons:prune    # Remove unused icons

Benefits

  1. ~99% smaller - Only download icons you use
  2. No runtime network - Everything local after build
  3. Version controlled - Icons committed to git
  4. Automatic sync - Runs on composer install/update
  5. CI-friendly - Catch missing icons in pipeline
  6. Backward compatible - Still works with iconify/json if installed

Backward Compatibility

Users who prefer the old approach can still require iconify/json:

{
    "require": {
        "studiometa/ui": "^2.0",
        "iconify/json": "^2.2",
        "iconify/json-tools": "^1.0"
    },
    "extra": {
        "studiometa/ui": {
            "icons": { "enabled": false }
        }
    }
}

The meta_icon() function auto-detects and uses iconify/json if installed.

Context

This came up while evaluating studiometa/ui integration for the Foehn WordPress framework. The 690MB dependency is the main blocker for including it as a suggested/default package.


Happy to help implement this if there's interest!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions