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 +4 -4
- data/CHANGELOG.md +21 -1
- data/README.md +91 -13
- data/lib/style_capsule/class_registry.rb +101 -0
- data/lib/style_capsule/component.rb +4 -1
- data/lib/style_capsule/component_builder.rb +145 -0
- data/lib/style_capsule/css_file_writer.rb +9 -3
- data/lib/style_capsule/helper.rb +9 -5
- data/lib/style_capsule/phlex_helper.rb +8 -4
- data/lib/style_capsule/railtie.rb +9 -17
- data/lib/style_capsule/standalone_helper.rb +196 -0
- data/lib/style_capsule/stylesheet_registry.rb +114 -24
- data/lib/style_capsule/version.rb +1 -1
- data/lib/style_capsule/view_component.rb +4 -1
- data/lib/style_capsule/view_component_helper.rb +8 -4
- data/lib/style_capsule.rb +17 -5
- data/lib/tasks/style_capsule.rake +2 -69
- data/sig/style_capsule.rbs +6 -3
- metadata +10 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d1fcbe6c03f9675593d6dca7b3d7d70de2b7d471d2d938993b59877614332ff4
|
|
4
|
+
data.tar.gz: 6aa5feb7ff25ef755084e7754f328e4340a0d4dc620e1ea8298badbb0387f254
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
[](https://badge.fury.io/rb/style_capsule) [](https://github.com/amkisko/style_capsule.rb/actions/workflows/test.yml) [](https://codecov.io/gh/amkisko/style_capsule.rb)
|
|
4
4
|
|
|
5
|
-
CSS scoping extension for
|
|
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
|
-
<%=
|
|
165
|
-
<%=
|
|
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
|
-
|
|
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 `
|
|
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](
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
215
|
+
if rails_available?
|
|
210
216
|
Rails.root.join("app/assets")
|
|
211
217
|
else
|
|
212
218
|
Pathname.new("app/assets")
|
data/lib/style_capsule/helper.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "digest/sha1"
|
|
4
|
-
|
|
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
|
|
151
|
+
# Render StyleCapsule registered stylesheets
|
|
152
152
|
#
|
|
153
153
|
# Usage in ERB:
|
|
154
|
-
# <%=
|
|
155
|
-
# <%=
|
|
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
|
|
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
|
|
39
|
+
# Render StyleCapsule registered stylesheets
|
|
40
40
|
#
|
|
41
41
|
# Usage in Phlex layouts:
|
|
42
42
|
# head do
|
|
43
|
-
#
|
|
44
|
-
#
|
|
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
|
|
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
|
-
#
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
|
|
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
|