Purge SVG icons in Rails deployment
Over the last few months I’ve enjoyed using Heroicons and Phosphor icon sets. These icons have variants at multiple sizes, are squares, and I don’t require loading a font library. I guess things might change as FontAwesome 7 rolls out, but that hasn’t happened yet.
The Setup
My setup has been in contant flux but lately I’ve started copying all the icon files into app/assets/images
and rendering them with inline_svg
and a custom helper.
module ApplicationHelper
def svg(name, **args)
return if name.blank?
filename = "#{name}.svg" unless name.end_with?(".svg")
inline_svg(filename, **args)
end
end
The icons are stored with in directories based on the icon source and variant.
$ find app/assets/images -name "*.svg"
# app/assets/images/fontawesome/regular/*.svg
# app/assets/images/heroicons/micro/*.svg
# app/assets/images/heroicons/mini/*.svg
# app/assets/images/heroicons/outline/*.svg
# app/assets/images/heroicons/solid/*.svg
Using this helper and this file structure makes it quick to add icons to views and in other helper methods.
<%= link_to post, class: 'flex items-center' do %>
<span>View More</span>
<%= svg "heroicons/micro/chevron-right", class: 'size-4 ml-1' %>
<% end %>
The Problem
One of the projects I’m working on right now has almost 1,600 svg icons saved into the project. I’m perfectly happy with this during development but it is very slow during deployment when precompiling assets.
The solution I came up with the help of ChatGPT :) was to remove the icons that are not referenced in app/controllers
, app/helpers
, and app/views
. This script purges 1,500+ icons in less than a second and allows me to be fast during development and pushing to production.
#!/usr/bin/env bash
set -euo pipefail
namespaces=("heroicons" "fontawesome" "phosphor")
pattern="$(IFS='|'; echo "${namespaces[*]}")"
# Step 1: Find all SVG files under the selected namespaces
mapfile -t svg_files < <(find app/assets/images -type f -name "*.svg" | grep -E "$pattern")
# Step 2: Extract all possible used keys from views using grep
# This builds a list like: heroicons/outline/arrow-left, etc.
mapfile -t used_keys < <(grep -rhoE "($pattern)/[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+" app/controllers app/helpers app/views | sort -u)
# Step 3: Put keys into a lookup set (associative array for fast matching)
declare -A used_lookup
for key in "${used_keys[@]}"; do
used_lookup["$key"]=1
done
# Step 4: Check all SVGs in one loop (still O(n), but lookup is O(1))
unused_files=()
for path in "${svg_files[@]}"; do
key="${path#app/assets/images/}"
key="${key%.svg}"
if ! [[ ${used_lookup["$key"]+_} ]]; then
unused_files+=("$path")
fi
done
# Step 5: Delete unused files in batches (fast)
if [[ ${#unused_files[@]} -gt 0 ]]; then
printf "%s\n" "${unused_files[@]}" | xargs -P 4 rm
fi