Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 49 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ bundle exec rubocop # Run RuboCop linter (uses rubocop-rails-omakase)

### Icon Management
```bash
rake move_to_assets # Move icons from tmp/heroicons to app/assets/images/icons/
rake move_to_assets # Move icons from tmp/heroicons to app/assets/images/icons/
rake heroicons:sync # Sync icons: copy used icons, remove unused ones
rake heroicons:dry_run # Preview what would be synced (dry run)
rake heroicons:list_used # List all icons currently used in the application
```

### Gem Development
Expand Down Expand Up @@ -60,4 +63,48 @@ The gem is designed to be included in Rails applications via:
- Icons are organized by type (outline/solid/mini/micro) corresponding to different sizes and styles from Heroicons
- The `icon_tag` helper defaults to `:outline` type and `"w-6 h-6"` classes
- Icon lookup first checks the Rails app's assets, then falls back to the gem's assets
- The `move_to_assets` rake task syncs icons from the upstream Heroicons repository
- The `move_to_assets` rake task syncs icons from the upstream Heroicons repository

## Automatic Icon Management

The gem includes an intelligent icon management system that automatically manages which icons are included in your Rails application's assets:

### How It Works

1. **IconScanner**: Scans your Rails application code (views, helpers, components, controllers) for `icon_tag` usage
2. **IconSyncer**: Copies only the used icons from the gem to your app's `app/assets/images/icons/` directory and removes unused ones

### Usage in Host Applications

**Manual Sync** (recommended workflow):
```bash
# Preview what would change
rake heroicons:sync

# List all icons currently used
rake heroicons:list_used

# Preview sync without making changes
rake heroicons:dry_run
```

**Auto-sync on Asset Precompilation** (optional):
```ruby
# config/application.rb
config.heroicons.auto_sync = true # Auto-sync before assets:precompile
```

### Benefits

- **Smaller asset size**: Only includes icons you actually use
- **Clean assets directory**: Automatically removes icons when you stop using them
- **Version control friendly**: Your app only commits the icons it needs
- **Easy auditing**: Use `rake heroicons:list_used` to see all icons in use

### Technical Details

- **IconScanner** (`lib/heroicons/icon_scanner.rb`): Searches for `icon_tag` calls using regex patterns
- **IconSyncer** (`lib/heroicons/icon_syncer.rb`): Manages copying/removing icon files
- **Rake Tasks** (`lib/tasks/heroicons.rake`): Provides CLI commands for icon management
- Supports all icon name formats: symbols, strings, underscored, and dashed names
- Automatically normalizes underscored names to dashed format for file lookup
72 changes: 33 additions & 39 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
GIT
remote: https://github.com/rails/rails.git
revision: c72f6713a427851a20a46c00c8ef3ab689d5bd32
branch: main
PATH
remote: .
specs:
heroicons-rails (0.4.1)

GEM
remote: https://rubygems.org/
specs:
action_text-trix (2.1.15)
railties
actioncable (8.1.0.beta1)
actionpack (= 8.1.0.beta1)
activesupport (= 8.1.0.beta1)
Expand Down Expand Up @@ -75,40 +80,6 @@ GIT
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
uri (>= 0.13.1)
rails (8.1.0.beta1)
actioncable (= 8.1.0.beta1)
actionmailbox (= 8.1.0.beta1)
actionmailer (= 8.1.0.beta1)
actionpack (= 8.1.0.beta1)
actiontext (= 8.1.0.beta1)
actionview (= 8.1.0.beta1)
activejob (= 8.1.0.beta1)
activemodel (= 8.1.0.beta1)
activerecord (= 8.1.0.beta1)
activestorage (= 8.1.0.beta1)
activesupport (= 8.1.0.beta1)
bundler (>= 1.15.0)
railties (= 8.1.0.beta1)
railties (8.1.0.beta1)
actionpack (= 8.1.0.beta1)
activesupport (= 8.1.0.beta1)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
thor (~> 1.0, >= 1.2.2)
tsort (>= 0.2)
zeitwerk (~> 2.6)

PATH
remote: .
specs:
heroicons-rails (0.4.1)

GEM
remote: https://rubygems.org/
specs:
action_text-trix (2.1.15)
railties
ast (2.4.3)
base64 (0.3.0)
benchmark (0.4.1)
Expand Down Expand Up @@ -193,13 +164,36 @@ GEM
rack (>= 1.3)
rackup (2.2.1)
rack (>= 3)
rails (8.1.0.beta1)
actioncable (= 8.1.0.beta1)
actionmailbox (= 8.1.0.beta1)
actionmailer (= 8.1.0.beta1)
actionpack (= 8.1.0.beta1)
actiontext (= 8.1.0.beta1)
actionview (= 8.1.0.beta1)
activejob (= 8.1.0.beta1)
activemodel (= 8.1.0.beta1)
activerecord (= 8.1.0.beta1)
activestorage (= 8.1.0.beta1)
activesupport (= 8.1.0.beta1)
bundler (>= 1.15.0)
railties (= 8.1.0.beta1)
rails-dom-testing (2.3.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.2)
loofah (~> 2.21)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
railties (8.1.0.beta1)
actionpack (= 8.1.0.beta1)
activesupport (= 8.1.0.beta1)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
thor (~> 1.0, >= 1.2.2)
tsort (>= 0.2)
zeitwerk (~> 2.6)
rainbow (3.1.1)
rake (13.3.0)
rbs (3.9.4)
Expand Down Expand Up @@ -283,7 +277,7 @@ PLATFORMS
DEPENDENCIES
heroicons-rails!
puma
rails!
rails
rubocop-rails-omakase
ruby-lsp
sqlite3
Expand Down
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,50 @@ app/assets/images/icons/custom/
<%= icon_tag "custom-arrow", type: :custom, class: "w-4 h-4" %>
```

## Icon Management

The gem includes intelligent icon management that keeps your assets directory clean and optimized:

### Automatic Icon Syncing

Only include icons you actually use in your application's assets:

```bash
# Scan your app and sync icons (copies used icons, removes unused ones)
$ rake heroicons:sync

# Preview what would be synced without making changes
$ rake heroicons:dry_run

# List all icons currently used in your application
$ rake heroicons:list_used
```

### How It Works

1. The scanner searches your Rails app for `icon_tag` usage in views, helpers, and components
2. Only the icons you're actually using are copied to `app/assets/images/icons/`
3. Icons you've removed from your code are automatically deleted from assets
4. Your repository stays clean with only the icons you need

### Benefits

- **Smaller asset bundles**: Only includes icons you use
- **Automatic cleanup**: Removes unused icons automatically
- **Easy auditing**: See exactly which icons your app uses
- **Version control friendly**: Only commit icons you actually need

### Optional Auto-sync

Enable automatic syncing before asset precompilation:

```ruby
# config/application.rb
config.heroicons.auto_sync = true
```

When enabled, icons will be automatically synced before `rake assets:precompile` runs.

## Installation
Add this line to your application's Gemfile:

Expand Down
2 changes: 2 additions & 0 deletions lib/heroicons-rails.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
require_relative "heroicons/version"
require_relative "heroicons/engine"
require_relative "heroicons/errors"
require_relative "heroicons/icon_scanner"
require_relative "heroicons/icon_syncer"

module Heroicons
def self.root
Expand Down
16 changes: 16 additions & 0 deletions lib/heroicons/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,26 @@
class Engine < ::Rails::Engine
isolate_namespace Heroicons

# Configuration options
config.heroicons = ActiveSupport::OrderedOptions.new
config.heroicons.auto_sync = false # Set to true to auto-sync icons before asset compilation

# Load rake tasks
rake_tasks do
load File.expand_path("../tasks/heroicons.rake", __dir__)
end

config.to_prepare do
ActiveSupport.on_load(:action_view) do
include Heroicons::ApplicationHelper
end
end

# Auto-sync icons before asset precompilation if enabled
config.before_initialize do |app|
if app.config.heroicons.auto_sync && defined?(Rake) && Rake::Task.task_defined?("assets:precompile")
Rake::Task["assets:precompile"].enhance(["heroicons:sync"])

Check failure on line 23 in lib/heroicons/engine.rb

View workflow job for this annotation

GitHub Actions / lint

Layout/SpaceInsideArrayLiteralBrackets: Use space inside array brackets.

Check failure on line 23 in lib/heroicons/engine.rb

View workflow job for this annotation

GitHub Actions / lint

Layout/SpaceInsideArrayLiteralBrackets: Use space inside array brackets.
end
end
end
end
62 changes: 62 additions & 0 deletions lib/heroicons/icon_scanner.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
module Heroicons
# Scans Rails application for icon_tag usage to determine which icons are being used
class IconScanner
# Matches: icon_tag(:name), icon_tag("name"), icon_tag :name, icon_tag "name"
# Also captures type parameter if present: type: :solid
ICON_TAG_PATTERN = /icon_tag\s*\(?\s*[:"']([a-z_-]+)["']?\s*(?:,\s*type:\s*:([a-z]+))?\)?/i
SEARCHABLE_EXTENSIONS = %w[.erb .haml .slim .rb].freeze

attr_reader :rails_root

def initialize(rails_root = Rails.root)
@rails_root = rails_root
end

# Returns a hash of used icons grouped by type
# Example: { outline: ['x-mark', 'home'], solid: ['check'], mini: ['star'] }
def scan_used_icons
used_icons = Hash.new { |h, k| h[k] = Set.new }

searchable_files.each do |file_path|
content = File.read(file_path)
extract_icons_from_content(content, used_icons)
end

# Convert sets to sorted arrays
used_icons.transform_values { |set| set.to_a.sort }
rescue => e
Rails.logger&.error("[Heroicons] Error scanning for icons: #{e.message}")
{}
end

private

def searchable_files
search_paths = [
File.join(rails_root, "app/views/**/*"),
File.join(rails_root, "app/helpers/**/*"),
File.join(rails_root, "app/components/**/*"),
File.join(rails_root, "app/controllers/**/*")
]

search_paths.flat_map do |pattern|
Dir.glob(pattern).select do |path|
File.file?(path) && SEARCHABLE_EXTENSIONS.any? { |ext| path.end_with?(ext) }
end
end
end

def extract_icons_from_content(content, used_icons)
content.scan(ICON_TAG_PATTERN) do |match|
icon_name = match[0]&.strip
icon_type = match[1]&.strip&.to_sym || :outline

next unless icon_name

# Normalize icon name: convert underscores to dashes
normalized_name = icon_name.tr("_", "-")
used_icons[icon_type] << normalized_name
end
end
end
end
Loading
Loading