style_capsule 1.0.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a5a88ff582594a22522f2b0349e54bfb235d42839a80b29a9118488a09f82df
4
- data.tar.gz: 4bf81ae48a9cfe5ef9e0b7c5f8ae00abeb38dbdf7ea2dc930293036b1c375832
3
+ metadata.gz: d1fcbe6c03f9675593d6dca7b3d7d70de2b7d471d2d938993b59877614332ff4
4
+ data.tar.gz: 6aa5feb7ff25ef755084e7754f328e4340a0d4dc620e1ea8298badbb0387f254
5
5
  SHA512:
6
- metadata.gz: a75e75f5c7d04616306d2ddaaea7a528eb2d4179071cb96ff6e360d854e940dddcfbfe271bf65fb3e6b4d5957961e5c116708f4017d5e9becd1ee3a76053bce3
7
- data.tar.gz: b7981f706126585a59659ed0c6e3ec61c40a85767ac72c16a9908c6739a5956bcd2bb9c24f20593bc42402ecccfc5a747b042f85b13a07268dfe747555cb56c4
6
+ metadata.gz: f06af1e47282aa0220b5ff28e5e0a197a8d76eaa9f3c00639958ca1f9d672c7ad1dd4f672b9de83cc7c521af6a49b9c9f9c9bd0ce476575211b1fc7a7aeb1da2
7
+ data.tar.gz: 3844dd9da6608b1eff6e495e19b92e3f90d4b41df323118f5fa7c6216603d3b33620c80df8aeb13c2e2506e133e3ab6e424c3dfd76feb16dbbc68b37fac72636
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.2.0 (2025-11-24)
4
+
5
+ - Added `StyleCapsule::ClassRegistry` for Rails-friendly class tracking without ObjectSpace iteration
6
+ - Fixed development environment bug where `ObjectSpace.each_object(Class)` could trigger errors with gems that override `Class#name` (e.g., Faker)
7
+ - Improved `ComponentBuilder` to use ClassRegistry first, with ObjectSpace fallback for compatibility
8
+ - Classes are now automatically registered when including `StyleCapsule::Component` or `StyleCapsule::ViewComponent`
9
+ - Better performance in development mode by tracking only relevant classes instead of iterating all classes
10
+ - Enhanced error handling for classes that cause issues during iteration
11
+
12
+ ## 1.1.0 (2025-11-21)
13
+
14
+ - Made Rails dependencies optional: `railties` and `activesupport` moved to development dependencies
15
+ - Core functionality now works without Rails (Sinatra, Hanami, plain Ruby, etc.)
16
+ - Rails integration remains fully supported via Railtie
17
+ - Added `StyleCapsule::StandaloneHelper` for non-Rails frameworks
18
+ - `StylesheetRegistry` now works without `ActiveSupport::CurrentAttributes` using thread-local storage fallback
19
+ - Renamed `stylesheet_registrymap_tags` to `stylesheet_registry_tags` (old name kept as deprecated alias)
20
+ - Extracted CSS building logic from Rake tasks into `StyleCapsule::ComponentBuilder`
21
+ - Fixed XSS vulnerability in `escape_html_attr` by using `CGI.escapeHTML` for proper HTML entity escaping
22
+ - Optimized ActiveSupport require to avoid exception handling overhead in Rails apps
23
+
3
24
  ## 1.0.2 (2025-11-21)
4
25
 
5
26
  - Fix default output directory for CSS files to app/assets/builds/capsules/
@@ -26,4 +47,3 @@
26
47
  - Security features: path traversal protection, CSS size limits (1MB), scope ID validation, filename validation
27
48
  - Ruby >= 3.0 requirement
28
49
  - Comprehensive test suite with > 93% coverage
29
-
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # style_capsule
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/style_capsule.svg?v=1.0.1)](https://badge.fury.io/rb/style_capsule) [![Test Status](https://github.com/amkisko/style_capsule.rb/actions/workflows/test.yml/badge.svg)](https://github.com/amkisko/style_capsule.rb/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/amkisko/style_capsule.rb/graph/badge.svg?token=2U6NXJOVVM)](https://codecov.io/gh/amkisko/style_capsule.rb)
3
+ [![Gem Version](https://badge.fury.io/rb/style_capsule.svg?v=1.2.0)](https://badge.fury.io/rb/style_capsule) [![Test Status](https://github.com/amkisko/style_capsule.rb/actions/workflows/test.yml/badge.svg)](https://github.com/amkisko/style_capsule.rb/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/amkisko/style_capsule.rb/graph/badge.svg?token=2U6NXJOVVM)](https://codecov.io/gh/amkisko/style_capsule.rb)
4
4
 
5
- CSS scoping extension for Rails components. Provides attribute-based style encapsulation for Phlex, ViewComponent, and ERB templates to prevent style leakage between components. Includes configurable caching strategies for optimal performance.
5
+ CSS scoping extension for Ruby components. Provides attribute-based style encapsulation for Phlex, ViewComponent, and ERB templates to prevent style leakage between components. Works with Rails and can be used standalone in other Ruby frameworks (Sinatra, Hanami, etc.) or plain Ruby scripts. Includes configurable caching strategies for optimal performance.
6
6
 
7
7
  Sponsored by [Kisko Labs](https://www.kiskolabs.com).
8
8
 
@@ -161,8 +161,8 @@ Then in your layout:
161
161
 
162
162
  ```erb
163
163
  <head>
164
- <%= stylesheet_registrymap_tags %>
165
- <%= stylesheet_registrymap_tags(namespace: :admin) %>
164
+ <%= stylesheet_registry_tags %>
165
+ <%= stylesheet_registry_tags(namespace: :admin) %>
166
166
  </head>
167
167
  ```
168
168
 
@@ -170,7 +170,7 @@ Or in Phlex (requires including `StyleCapsule::PhlexHelper`):
170
170
 
171
171
  ```ruby
172
172
  head do
173
- stylesheet_registrymap_tags
173
+ stylesheet_registry_tags
174
174
  end
175
175
  ```
176
176
 
@@ -205,7 +205,7 @@ def call
205
205
  end
206
206
  ```
207
207
 
208
- Registered files are rendered via `stylesheet_registrymap_tags` in your layout, just like inline CSS.
208
+ Registered files are rendered via `stylesheet_registry_tags` in your layout, just like inline CSS.
209
209
 
210
210
  ## Caching Strategies
211
211
 
@@ -332,6 +332,90 @@ end
332
332
  - Component-scoped selectors: `:host`, `:host(.active)`, `:host-context(.theme-dark)`
333
333
  - Media queries: `@media (max-width: 768px) { ... }`
334
334
 
335
+ ## Requirements
336
+
337
+ - Ruby >= 3.0
338
+ - Rails >= 6.0, < 9.0 (optional, for Rails integration)
339
+ - ActiveSupport >= 6.0, < 9.0 (optional, for Rails integration)
340
+
341
+ **Note**: The gem can be used without Rails! See [Non-Rails Support](#non-rails-support) below.
342
+
343
+ ## Non-Rails Support
344
+
345
+ StyleCapsule can be used without Rails! The core functionality is framework-agnostic.
346
+
347
+ ### Standalone Usage
348
+
349
+ ```ruby
350
+ require 'style_capsule'
351
+
352
+ # Direct CSS processing
353
+ css = ".section { color: red; }"
354
+ capsule_id = "abc123"
355
+ scoped = StyleCapsule::CssProcessor.scope_selectors(css, capsule_id)
356
+ # => "[data-capsule=\"abc123\"] .section { color: red; }"
357
+ ```
358
+
359
+ ### Phlex Without Rails
360
+
361
+ ```ruby
362
+ require 'phlex'
363
+ require 'style_capsule'
364
+
365
+ class MyComponent < Phlex::HTML
366
+ include StyleCapsule::Component
367
+
368
+ def component_styles
369
+ <<~CSS
370
+ .section { color: red; }
371
+ CSS
372
+ end
373
+
374
+ def view_template
375
+ div(class: "section") { "Hello" }
376
+ end
377
+ end
378
+ ```
379
+
380
+ ### Sinatra
381
+
382
+ ```ruby
383
+ require 'sinatra'
384
+ require 'style_capsule'
385
+
386
+ class MyApp < Sinatra::Base
387
+ helpers StyleCapsule::StandaloneHelper
388
+
389
+ get '/' do
390
+ erb :index
391
+ end
392
+ end
393
+ ```
394
+
395
+ ```erb
396
+ <!-- views/index.erb -->
397
+ <%= style_capsule do %>
398
+ <style>
399
+ .section { color: red; }
400
+ </style>
401
+ <div class="section">Content</div>
402
+ <% end %>
403
+ ```
404
+
405
+ ### Stylesheet Registry Without Rails
406
+
407
+ The stylesheet registry automatically uses thread-local storage when ActiveSupport is not available:
408
+
409
+ ```ruby
410
+ require 'style_capsule'
411
+
412
+ # Works without Rails
413
+ StyleCapsule::StylesheetRegistry.register_inline(".test { color: red; }", namespace: :test)
414
+ stylesheets = StyleCapsule::StylesheetRegistry.request_inline_stylesheets
415
+ ```
416
+
417
+ For more details, see [docs/non_rails_support.md](docs/non_rails_support.md).
418
+
335
419
  ## How It Works
336
420
 
337
421
  1. **Scope ID Generation**: Each component class gets a unique scope ID based on its class name (shared across all instances)
@@ -339,12 +423,6 @@ end
339
423
  3. **HTML Wrapping**: Component content is automatically wrapped in a scoped element
340
424
  4. **No Class Renaming**: Class names remain unchanged (unlike Shadow DOM)
341
425
 
342
- ## Requirements
343
-
344
- - Ruby >= 3.0
345
- - Rails >= 7.0, < 9.0
346
- - ActiveSupport >= 7.0, < 9.0
347
-
348
426
  ## Development
349
427
 
350
428
  ```bash
@@ -395,4 +473,4 @@ For detailed security information, see [SECURITY.md](SECURITY.md).
395
473
 
396
474
  ## License
397
475
 
398
- The gem is available as open source under the terms of the [MIT License](LICENSE.md).
476
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StyleCapsule
4
+ # Registry for tracking classes that include StyleCapsule modules
5
+ #
6
+ # This provides a Rails-friendly way to track classes without using ObjectSpace,
7
+ # which can be problematic with certain gems (e.g., Faker) that override Class#name.
8
+ #
9
+ # Classes are automatically registered when they include StyleCapsule::Component
10
+ # or StyleCapsule::ViewComponent.
11
+ #
12
+ # @example
13
+ # # Classes are automatically registered when they include modules
14
+ # class MyComponent < ApplicationComponent
15
+ # include StyleCapsule::Component # Automatically registered
16
+ # end
17
+ #
18
+ # # Iterate over registered classes
19
+ # StyleCapsule::ClassRegistry.each do |klass|
20
+ # klass.clear_css_cache if klass.respond_to?(:clear_css_cache)
21
+ # end
22
+ #
23
+ # # Clear registry (useful in development when classes are reloaded)
24
+ # StyleCapsule::ClassRegistry.clear
25
+ class ClassRegistry
26
+ # Use Set for O(1) lookups instead of O(n) with Array#include?
27
+ @classes = Set.new
28
+
29
+ class << self
30
+ # Register a class that includes a StyleCapsule module
31
+ #
32
+ # @param klass [Class] The class to register
33
+ # @return [void]
34
+ def register(klass)
35
+ return if klass.nil?
36
+ return if klass.singleton_class?
37
+
38
+ # Only register classes with names (skip anonymous classes)
39
+ begin
40
+ name = klass.name
41
+ return if name.nil? || name.to_s.strip.empty?
42
+ rescue
43
+ # Skip classes that cause errors when calling name (e.g., ArgumentError, NoMethodError, NameError)
44
+ return
45
+ end
46
+
47
+ @classes.add(klass)
48
+ end
49
+
50
+ # Remove a class from the registry
51
+ #
52
+ # @param klass [Class] The class to unregister
53
+ # @return [void]
54
+ def unregister(klass)
55
+ @classes.delete(klass)
56
+ end
57
+
58
+ # Iterate over all registered classes
59
+ #
60
+ # @yield [Class] Each registered class
61
+ # @return [void]
62
+ def each(&block)
63
+ # Filter out classes that no longer exist or have been unloaded
64
+ # Use delete_if for Set (equivalent to reject! for Array)
65
+ @classes.delete_if do |klass|
66
+ # Check if class still exists and is valid
67
+ klass.name.nil? || klass.singleton_class?
68
+ rescue
69
+ # Class has been unloaded or causes errors - remove from registry
70
+ true
71
+ end
72
+
73
+ @classes.each(&block)
74
+ end
75
+
76
+ # Get all registered classes
77
+ #
78
+ # @return [Array<Class>] Array of registered classes
79
+ def all
80
+ # Filter out invalid classes
81
+ each.to_a
82
+ end
83
+
84
+ # Clear the registry
85
+ #
86
+ # Useful in development when classes are reloaded.
87
+ #
88
+ # @return [void]
89
+ def clear
90
+ @classes.clear
91
+ end
92
+
93
+ # Get the number of registered classes
94
+ #
95
+ # @return [Integer]
96
+ def count
97
+ each.count
98
+ end
99
+ end
100
+ end
101
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "digest/sha1"
4
- require "active_support/core_ext/string"
4
+ # ActiveSupport string extensions are conditionally required in lib/style_capsule.rb
5
5
 
6
6
  module StyleCapsule
7
7
  # Phlex component concern for encapsulated CSS
@@ -89,6 +89,9 @@ module StyleCapsule
89
89
 
90
90
  # Use prepend to wrap view_template method
91
91
  base.prepend(ViewTemplateWrapper)
92
+
93
+ # Register class for Rails-friendly tracking
94
+ ClassRegistry.register(base)
92
95
  end
93
96
 
94
97
  module ClassMethods
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StyleCapsule
4
+ # Builds CSS files from StyleCapsule components
5
+ #
6
+ # This class extracts the logic from the rake task so it can be tested independently.
7
+ # The rake task delegates to this class.
8
+ class ComponentBuilder
9
+ class << self
10
+ # Check if Phlex is available
11
+ # This method can be stubbed in tests to test fallback paths
12
+ def phlex_available?
13
+ !!defined?(Phlex::HTML)
14
+ end
15
+
16
+ # Check if ViewComponent is available
17
+ # This method can be stubbed in tests to test fallback paths
18
+ def view_component_available?
19
+ !!defined?(ViewComponent::Base)
20
+ end
21
+
22
+ # Find all Phlex components that use StyleCapsule
23
+ #
24
+ # Uses ClassRegistry to find registered components (Rails-friendly, avoids expensive ObjectSpace scanning).
25
+ # Classes are automatically registered when they include StyleCapsule::Component.
26
+ #
27
+ # @return [Array<Class>] Array of component classes
28
+ def find_phlex_components
29
+ return [] unless phlex_available?
30
+
31
+ components = []
32
+
33
+ # Use the registry (Rails-friendly, avoids expensive ObjectSpace scanning)
34
+ ClassRegistry.each do |klass|
35
+ if klass < Phlex::HTML && klass.included_modules.include?(StyleCapsule::Component)
36
+ components << klass
37
+ end
38
+ rescue
39
+ # Skip classes that cause errors (inheritance checks, etc.)
40
+ next
41
+ end
42
+
43
+ components
44
+ end
45
+
46
+ # Find all ViewComponent components that use StyleCapsule
47
+ #
48
+ # Uses ClassRegistry to find registered components (Rails-friendly, avoids expensive ObjectSpace scanning).
49
+ # Classes are automatically registered when they include StyleCapsule::ViewComponent.
50
+ #
51
+ # @return [Array<Class>] Array of component classes
52
+ def find_view_components
53
+ return [] unless view_component_available?
54
+
55
+ components = []
56
+
57
+ begin
58
+ # Use the registry (Rails-friendly, avoids expensive ObjectSpace scanning)
59
+ ClassRegistry.each do |klass|
60
+ if klass < ViewComponent::Base && klass.included_modules.include?(StyleCapsule::ViewComponent)
61
+ components << klass
62
+ end
63
+ rescue
64
+ # Skip classes that cause errors (inheritance checks, etc.)
65
+ next
66
+ end
67
+ rescue
68
+ # ViewComponent may have loading issues (e.g., version compatibility)
69
+ # Silently skip ViewComponent components if there's an error
70
+ # This allows the rake task to continue with Phlex components
71
+ end
72
+
73
+ components
74
+ end
75
+
76
+ # Collect all component classes that use StyleCapsule
77
+ #
78
+ # @return [Array<Class>] Array of component classes
79
+ def collect_components
80
+ find_phlex_components + find_view_components
81
+ end
82
+
83
+ # Build CSS file for a single component
84
+ #
85
+ # @param component_class [Class] Component class to build
86
+ # @param output_proc [Proc, nil] Optional proc to call with output messages
87
+ # @return [String, nil] Generated file path or nil if skipped
88
+ def build_component(component_class, output_proc: nil)
89
+ return nil unless component_class.inline_cache_strategy == :file
90
+ # Check for class method component_styles (required for file caching)
91
+ return nil unless component_class.respond_to?(:component_styles, false)
92
+
93
+ begin
94
+ # Use class method component_styles for file caching
95
+ css_content = component_class.component_styles
96
+ return nil if css_content.nil? || css_content.to_s.strip.empty?
97
+
98
+ # Create a temporary instance to get capsule
99
+ # Some components might require arguments, so we catch errors
100
+ instance = component_class.new
101
+ capsule_id = instance.component_capsule
102
+ scoped_css = instance.send(:scope_css, css_content)
103
+
104
+ # Write CSS file
105
+ file_path = CssFileWriter.write_css(
106
+ css_content: scoped_css,
107
+ component_class: component_class,
108
+ capsule_id: capsule_id
109
+ )
110
+
111
+ output_proc&.call("Generated: #{file_path}") if file_path
112
+ file_path
113
+ rescue ArgumentError, NoMethodError => e
114
+ # Component requires arguments or has dependencies - skip it
115
+ output_proc&.call("Skipped #{component_class.name}: #{e.message}")
116
+ nil
117
+ end
118
+ end
119
+
120
+ # Build CSS files for all components
121
+ #
122
+ # @param output_proc [Proc, nil] Optional proc to call with output messages
123
+ # @return [Integer] Number of files generated
124
+ def build_all(output_proc: nil)
125
+ require "style_capsule/css_file_writer"
126
+
127
+ # Ensure output directory exists
128
+ CssFileWriter.ensure_output_directory
129
+
130
+ # Collect all component classes that use StyleCapsule
131
+ component_classes = collect_components
132
+
133
+ # Generate CSS files for each component
134
+ generated_count = 0
135
+ component_classes.each do |component_class|
136
+ file_path = build_component(component_class, output_proc: output_proc)
137
+ generated_count += 1 if file_path
138
+ end
139
+
140
+ output_proc&.call("StyleCapsule CSS files built successfully")
141
+ generated_count
142
+ end
143
+ end
144
+ end
145
+ end
@@ -52,7 +52,7 @@ module StyleCapsule
52
52
 
53
53
  @output_dir = if output_dir
54
54
  output_dir.is_a?(Pathname) ? output_dir : Pathname.new(output_dir.to_s)
55
- elsif defined?(Rails) && Rails.root
55
+ elsif rails_available?
56
56
  Rails.root.join(DEFAULT_OUTPUT_DIR)
57
57
  else
58
58
  Pathname.new(DEFAULT_OUTPUT_DIR)
@@ -154,11 +154,17 @@ module StyleCapsule
154
154
  @enabled != false
155
155
  end
156
156
 
157
+ # Check if Rails is available
158
+ # This method can be stubbed in tests to test fallback paths
159
+ def rails_available?
160
+ defined?(Rails) && Rails.respond_to?(:root) && Rails.root
161
+ end
162
+
157
163
  private
158
164
 
159
165
  # Get output directory (with default)
160
166
  def output_directory
161
- @output_dir ||= if defined?(Rails) && Rails.root
167
+ @output_dir ||= if rails_available?
162
168
  Rails.root.join(DEFAULT_OUTPUT_DIR)
163
169
  else
164
170
  Pathname.new(DEFAULT_OUTPUT_DIR)
@@ -206,7 +212,7 @@ module StyleCapsule
206
212
 
207
213
  # Get Rails assets root (app/assets)
208
214
  def rails_assets_root
209
- if defined?(Rails) && Rails.root
215
+ if rails_available?
210
216
  Rails.root.join("app/assets")
211
217
  else
212
218
  Pathname.new("app/assets")
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "digest/sha1"
4
- require "active_support/core_ext/string"
4
+ # ActiveSupport string extensions are conditionally required in lib/style_capsule.rb
5
5
 
6
6
  module StyleCapsule
7
7
  # ERB Helper module for use in Rails views
@@ -148,16 +148,20 @@ module StyleCapsule
148
148
  StyleCapsule::StylesheetRegistry.register(file_path, namespace: namespace, **options)
149
149
  end
150
150
 
151
- # Render StyleCapsule registered stylesheets (similar to javascript_importmap_tags)
151
+ # Render StyleCapsule registered stylesheets
152
152
  #
153
153
  # Usage in ERB:
154
- # <%= stylesheet_registrymap_tags %>
155
- # <%= stylesheet_registrymap_tags(namespace: :admin) %>
154
+ # <%= stylesheet_registry_tags %>
155
+ # <%= stylesheet_registry_tags(namespace: :admin) %>
156
156
  #
157
157
  # @param namespace [Symbol, String, nil] Optional namespace to render (nil/blank renders all)
158
158
  # @return [String] HTML-safe string with stylesheet tags
159
- def stylesheet_registrymap_tags(namespace: nil)
159
+ def stylesheet_registry_tags(namespace: nil)
160
160
  StyleCapsule::StylesheetRegistry.render_head_stylesheets(self, namespace: namespace)
161
161
  end
162
+
163
+ # @deprecated Use {#stylesheet_registry_tags} instead.
164
+ # This method name will be removed in a future version.
165
+ alias_method :stylesheet_registrymap_tags, :stylesheet_registry_tags
162
166
  end
163
167
  end
@@ -36,17 +36,17 @@ module StyleCapsule
36
36
  StyleCapsule::StylesheetRegistry.register(file_path, namespace: namespace, **options)
37
37
  end
38
38
 
39
- # Render StyleCapsule registered stylesheets (similar to javascript_importmap_tags)
39
+ # Render StyleCapsule registered stylesheets
40
40
  #
41
41
  # Usage in Phlex layouts:
42
42
  # head do
43
- # stylesheet_registrymap_tags
44
- # stylesheet_registrymap_tags(namespace: :admin)
43
+ # stylesheet_registry_tags
44
+ # stylesheet_registry_tags(namespace: :admin)
45
45
  # end
46
46
  #
47
47
  # @param namespace [Symbol, String, nil] Optional namespace to render (nil/blank renders all)
48
48
  # @return [void] Renders stylesheet tags via raw
49
- def stylesheet_registrymap_tags(namespace: nil)
49
+ def stylesheet_registry_tags(namespace: nil)
50
50
  output = StyleCapsule::StylesheetRegistry.render_head_stylesheets(view_context, namespace: namespace)
51
51
  # Phlex's raw() requires the object to be marked as safe
52
52
  # Use Phlex's safe() if available, otherwise fall back to html_safe for test doubles
@@ -62,5 +62,9 @@ module StyleCapsule
62
62
  # Always return the output string for testing/compatibility
63
63
  output_string
64
64
  end
65
+
66
+ # @deprecated Use {#stylesheet_registry_tags} instead.
67
+ # This method name will be removed in a future version.
68
+ alias_method :stylesheet_registrymap_tags, :stylesheet_registry_tags
65
69
  end
66
70
  end
@@ -31,24 +31,16 @@ module StyleCapsule
31
31
  # This prevents memory leaks from stale cache entries during code reloading
32
32
  if Rails.env.development?
33
33
  config.to_prepare do
34
- # Clear CSS caches for all classes that include StyleCapsule modules
35
- # Skip singleton classes and handle errors gracefully
36
- ObjectSpace.each_object(Class) do |klass|
37
- # Skip singleton classes and classes without names (they can cause errors)
38
- # Singleton classes don't have proper names and can't be safely checked
39
- next if klass.name.blank?
40
- next if klass.singleton_class?
41
-
42
- # Use method_defined? instead of respond_to? to avoid triggering delegation
43
- # that might cause errors with singleton classes or other edge cases
44
- begin
45
- if klass.method_defined?(:clear_css_cache, false) || klass.private_method_defined?(:clear_css_cache)
46
- klass.clear_css_cache
47
- end
48
- rescue
49
- # Skip classes that cause errors (singleton classes, delegation issues, etc.)
50
- next
34
+ # Use Rails-friendly class registry instead of ObjectSpace iteration
35
+ # This avoids issues with gems that override Class#name (e.g., Faker)
36
+ StyleCapsule::ClassRegistry.each do |klass|
37
+ # Clear CSS cache if the class has this method
38
+ if klass.method_defined?(:clear_css_cache, false) || klass.private_method_defined?(:clear_css_cache)
39
+ klass.clear_css_cache
51
40
  end
41
+ rescue
42
+ # Skip classes that cause errors (unloaded classes, etc.)
43
+ next
52
44
  end
53
45
 
54
46
  # Clear stylesheet manifest to allow re-registration during code reload