style_capsule 1.3.0 → 2.0.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 +34 -1
- data/README.md +92 -105
- data/docs/non_rails_support.md +85 -0
- data/lib/style_capsule/asset_path.rb +39 -0
- data/lib/style_capsule/class_registry.rb +3 -2
- data/lib/style_capsule/component.rb +13 -254
- data/lib/style_capsule/component_builder.rb +4 -1
- data/lib/style_capsule/component_class_methods.rb +295 -0
- data/lib/style_capsule/css_file_writer.rb +62 -25
- data/lib/style_capsule/css_processor.rb +4 -6
- data/lib/style_capsule/head_injection_middleware.rb +72 -0
- data/lib/style_capsule/helper.rb +17 -27
- data/lib/style_capsule/helper_scope_cache.rb +72 -0
- data/lib/style_capsule/instrumentation.rb +15 -15
- data/lib/style_capsule/phlex_helper.rb +19 -16
- data/lib/style_capsule/railtie.rb +11 -0
- data/lib/style_capsule/standalone_helper.rb +12 -23
- data/lib/style_capsule/stylesheet_registry.rb +244 -64
- data/lib/style_capsule/version.rb +1 -1
- data/lib/style_capsule/view_component.rb +13 -256
- data/lib/style_capsule/view_component_helper.rb +16 -9
- data/lib/style_capsule.rb +14 -5
- data/lib/tasks/style_capsule.rake +15 -2
- data/sig/style_capsule.rbs +43 -44
- metadata +84 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a44279976aff6cedb8ddc1a8bc4c84382378d73a11ebb7a17850626ecd612745
|
|
4
|
+
data.tar.gz: fee43b83895392f778b7c8b3600dbb5e75e52d660efc76d7939e762ef594e94d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a82489272c3ff1e9ac782e2129dc8133a2037101a9719f6c1a38404cec71dae33dab6f64ae93ff6926cd57bcd2456412f87dc61e0c61d04996b226a66466f411
|
|
7
|
+
data.tar.gz: 6c50c563aba3098121e4d9f2693572c6b66cbf6304fff198cafd337ac33da190af06a8469a2a6b59108c1392e2c4b844d13d0dd1cf56025faf5640bbbb1f5bdc
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 2.0.0 (2026-07-03)
|
|
6
|
+
|
|
7
|
+
- BREAKING: split `StylesheetRegistry.register` into request-scoped `register` and boot-time `register_eager`
|
|
8
|
+
- Replace boot-time `StylesheetRegistry.register(...)` with `register_eager(...)`; keep `register` for render-time / `register_stylesheet` paths
|
|
9
|
+
- BREAKING: enable `StyleCapsule::HeadInjectionMiddleware` by default (`config.style_capsule.head_injection_middleware`); set to `false` to disable
|
|
10
|
+
- Fix render-time `register_stylesheet` missing from `<head>` on the same request when layouts render head before body
|
|
11
|
+
- Add `HeadInjectionMiddleware` to inject pending stylesheet tags before `</head>` after the response body is rendered
|
|
12
|
+
- Skips chunked responses and only buffers when pending request-scoped stylesheets exist; accepts any 2xx HTML response
|
|
13
|
+
- Dedupe eager manifest and request-scoped file paths by logical path when rendering `<head>` (request registration wins on option conflicts)
|
|
14
|
+
- Bound class-level component CSS cache (`MAX_CSS_CACHE_ENTRIES` = 256)
|
|
15
|
+
- Single-pass `<style>` extraction in ERB / standalone helpers (`StringScanner`)
|
|
16
|
+
- Reject parent path segments (`..`) in `AssetPath.validate_logical_path!`
|
|
17
|
+
- Add `StyleCapsule::StylesheetRegistry.inject_pending_head_stylesheets` for manual/testing use
|
|
18
|
+
- Extract shared Phlex / ViewComponent class DSL into `StyleCapsule::ComponentClassMethods`
|
|
19
|
+
- Fix class-level `scope_css` cache keys to include a CSS fingerprint (instance methods returning different styles per render)
|
|
20
|
+
- Fix ERB / standalone `Helper#scope_css` thread cache keys to include a CSS fingerprint
|
|
21
|
+
- Add `tag:` option to `StandaloneHelper#style_capsule` (aligned with the Rails helper)
|
|
22
|
+
- Fix `PhlexHelper#stylesheet_registry_tags` to always return a string when `safe` is unavailable
|
|
23
|
+
- Pass `component_class:` into `CssProcessor` from `ViewComponent` for consistent instrumentation
|
|
24
|
+
- Defer `input_size` in `Instrumentation.instrument_css_processing` until instrumentation runs
|
|
25
|
+
- Documentation: add [docs/non_rails_support.md](docs/non_rails_support.md); document late head injection, `register_eager`, streaming / Live caveats
|
|
26
|
+
- RBS: `StylesheetRegistry` no longer declared as a subclass of `ActiveSupport::CurrentAttributes` only
|
|
27
|
+
|
|
28
|
+
## 1.4.0 (2025-11-26)
|
|
29
|
+
|
|
30
|
+
- Added unified `style_capsule` class method for configuring all StyleCapsule settings (namespace, cache strategy, CSS scoping, head rendering) in a single call
|
|
31
|
+
- Added automatic namespace fallback in `register_stylesheet` helper methods - when namespace is not specified, uses the component's configured namespace from `style_capsule`
|
|
32
|
+
- Removed deprecated `head_rendering!` method (use `style_capsule` instead)
|
|
33
|
+
- Removed deprecated `stylesheet_registrymap_tags` alias (use `stylesheet_registry_tags` instead)
|
|
34
|
+
- Refactored namespace configuration to use instance variables instead of constants for better inheritance behavior
|
|
35
|
+
|
|
3
36
|
## 1.3.0 (2025-11-26)
|
|
4
37
|
|
|
5
38
|
- Added comprehensive instrumentation via `StyleCapsule::Instrumentation` using ActiveSupport::Notifications
|
|
@@ -29,7 +62,7 @@
|
|
|
29
62
|
- Rails integration remains fully supported via Railtie
|
|
30
63
|
- Added `StyleCapsule::StandaloneHelper` for non-Rails frameworks
|
|
31
64
|
- `StylesheetRegistry` now works without `ActiveSupport::CurrentAttributes` using thread-local storage fallback
|
|
32
|
-
- Renamed `stylesheet_registrymap_tags` to `stylesheet_registry_tags` (
|
|
65
|
+
- Renamed `stylesheet_registrymap_tags` to `stylesheet_registry_tags` (deprecated alias removed in 1.4.0)
|
|
33
66
|
- Extracted CSS building logic from Rake tasks into `StyleCapsule::ComponentBuilder`
|
|
34
67
|
- Fixed XSS vulnerability in `escape_html_attr` by using `CGI.escapeHTML` for proper HTML entity escaping
|
|
35
68
|
- Optimized ActiveSupport require to avoid exception handling overhead in Rails apps
|
data/README.md
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
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
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
|
-
Sponsored by [Kisko Labs](https://www.kiskolabs.com).
|
|
8
|
-
|
|
9
|
-
<a href="https://www.kiskolabs.com">
|
|
10
|
-
<img src="kisko.svg" width="200" alt="Sponsored by Kisko Labs" />
|
|
11
|
-
</a>
|
|
12
|
-
|
|
13
7
|
## Installation
|
|
14
8
|
|
|
15
9
|
Add to your Gemfile:
|
|
@@ -88,6 +82,17 @@ end
|
|
|
88
82
|
<% end %>
|
|
89
83
|
```
|
|
90
84
|
|
|
85
|
+
**With custom wrapper tag:**
|
|
86
|
+
|
|
87
|
+
```erb
|
|
88
|
+
<%= style_capsule(tag: :section) do %>
|
|
89
|
+
<style>
|
|
90
|
+
.section { color: red; }
|
|
91
|
+
</style>
|
|
92
|
+
<div class="section">Content</div>
|
|
93
|
+
<% end %>
|
|
94
|
+
```
|
|
95
|
+
|
|
91
96
|
## CSS Scoping Strategies
|
|
92
97
|
|
|
93
98
|
StyleCapsule supports two CSS scoping strategies:
|
|
@@ -103,12 +108,21 @@ StyleCapsule supports two CSS scoping strategies:
|
|
|
103
108
|
|
|
104
109
|
### Configuration
|
|
105
110
|
|
|
106
|
-
**Per-component:**
|
|
111
|
+
**Per-component (using `style_capsule` - recommended):**
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
class MyComponent < ApplicationComponent
|
|
115
|
+
include StyleCapsule::Component
|
|
116
|
+
style_capsule scoping_strategy: :nesting # Use CSS nesting
|
|
117
|
+
end
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**With custom wrapper tag:**
|
|
107
121
|
|
|
108
122
|
```ruby
|
|
109
123
|
class MyComponent < ApplicationComponent
|
|
110
124
|
include StyleCapsule::Component
|
|
111
|
-
|
|
125
|
+
style_capsule tag: :section # Use <section> instead of <div> for wrapper
|
|
112
126
|
end
|
|
113
127
|
```
|
|
114
128
|
|
|
@@ -117,7 +131,7 @@ end
|
|
|
117
131
|
```ruby
|
|
118
132
|
class ApplicationComponent < Phlex::HTML
|
|
119
133
|
include StyleCapsule::Component
|
|
120
|
-
|
|
134
|
+
style_capsule scoping_strategy: :nesting # Enable for all components
|
|
121
135
|
end
|
|
122
136
|
```
|
|
123
137
|
|
|
@@ -129,12 +143,12 @@ MyComponent.clear_css_cache
|
|
|
129
143
|
|
|
130
144
|
## Stylesheet Registry
|
|
131
145
|
|
|
132
|
-
For better performance, register styles for head rendering instead of rendering `<style>` tags in the body:
|
|
146
|
+
For better performance, register styles for head rendering instead of rendering `<style>` tags in the body. Use the unified `style_capsule` method to configure all settings:
|
|
133
147
|
|
|
134
148
|
```ruby
|
|
135
149
|
class MyComponent < ApplicationComponent
|
|
136
150
|
include StyleCapsule::Component
|
|
137
|
-
|
|
151
|
+
style_capsule namespace: :admin # Configure namespace and enable head rendering
|
|
138
152
|
|
|
139
153
|
def component_styles
|
|
140
154
|
<<~CSS
|
|
@@ -144,12 +158,17 @@ class MyComponent < ApplicationComponent
|
|
|
144
158
|
end
|
|
145
159
|
```
|
|
146
160
|
|
|
147
|
-
With cache strategy:
|
|
161
|
+
With cache strategy and CSS scoping:
|
|
148
162
|
|
|
149
163
|
```ruby
|
|
150
164
|
class MyComponent < ApplicationComponent
|
|
151
165
|
include StyleCapsule::Component
|
|
152
|
-
|
|
166
|
+
style_capsule(
|
|
167
|
+
namespace: :admin,
|
|
168
|
+
cache_strategy: :time,
|
|
169
|
+
cache_ttl: 1.hour,
|
|
170
|
+
scoping_strategy: :nesting
|
|
171
|
+
)
|
|
153
172
|
|
|
154
173
|
def component_styles
|
|
155
174
|
<<~CSS
|
|
@@ -159,11 +178,10 @@ class MyComponent < ApplicationComponent
|
|
|
159
178
|
end
|
|
160
179
|
```
|
|
161
180
|
|
|
162
|
-
Then in your layout:
|
|
181
|
+
Then in your layout (render only the namespace you need):
|
|
163
182
|
|
|
164
183
|
```erb
|
|
165
184
|
<head>
|
|
166
|
-
<%= stylesheet_registry_tags %>
|
|
167
185
|
<%= stylesheet_registry_tags(namespace: :admin) %>
|
|
168
186
|
</head>
|
|
169
187
|
```
|
|
@@ -172,42 +190,73 @@ Or in Phlex (requires including `StyleCapsule::PhlexHelper`):
|
|
|
172
190
|
|
|
173
191
|
```ruby
|
|
174
192
|
head do
|
|
175
|
-
stylesheet_registry_tags
|
|
193
|
+
stylesheet_registry_tags(namespace: :admin)
|
|
176
194
|
end
|
|
177
195
|
```
|
|
178
196
|
|
|
197
|
+
**Namespace Isolation:** Using namespaces prevents stylesheet leakage between different application contexts. For example, login pages can use `namespace: :login`, ActiveAdmin can use `namespace: :active_admin`, and user components can use `namespace: :user`. Each namespace is rendered separately, improving caching efficiency and preventing style conflicts.
|
|
198
|
+
|
|
179
199
|
### Registering Stylesheet Files
|
|
180
200
|
|
|
181
|
-
You can also register external stylesheet files (not inline CSS) for head rendering:
|
|
201
|
+
You can also register external stylesheet files (not inline CSS) for head rendering. When a component has a configured namespace via `style_capsule`, you don't need to specify it every time:
|
|
182
202
|
|
|
183
203
|
**In ERB:**
|
|
184
204
|
|
|
185
205
|
```erb
|
|
186
|
-
<% register_stylesheet("stylesheets/user/
|
|
206
|
+
<% register_stylesheet("stylesheets/user/my_component", "data-turbo-track": "reload") %>
|
|
187
207
|
<% register_stylesheet("stylesheets/admin/dashboard", namespace: :admin) %>
|
|
188
208
|
```
|
|
189
209
|
|
|
190
210
|
**In Phlex (requires including `StyleCapsule::PhlexHelper`):**
|
|
191
211
|
|
|
192
212
|
```ruby
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
213
|
+
class UserComponent < ApplicationComponent
|
|
214
|
+
include StyleCapsule::Component
|
|
215
|
+
include StyleCapsule::PhlexHelper
|
|
216
|
+
style_capsule namespace: :user # Set default namespace
|
|
217
|
+
|
|
218
|
+
def view_template
|
|
219
|
+
# Namespace automatically uses :user from style_capsule
|
|
220
|
+
register_stylesheet("stylesheets/user/my_component", "data-turbo-track": "reload")
|
|
221
|
+
# Can still override namespace if needed
|
|
222
|
+
register_stylesheet("stylesheets/shared/common", namespace: :shared)
|
|
223
|
+
div { "Content" }
|
|
224
|
+
end
|
|
197
225
|
end
|
|
198
226
|
```
|
|
199
227
|
|
|
200
228
|
**In ViewComponent (requires including `StyleCapsule::ViewComponentHelper`):**
|
|
201
229
|
|
|
202
230
|
```ruby
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
231
|
+
class UserComponent < ApplicationComponent
|
|
232
|
+
include StyleCapsule::ViewComponent
|
|
233
|
+
include StyleCapsule::ViewComponentHelper
|
|
234
|
+
style_capsule namespace: :user # Set default namespace
|
|
235
|
+
|
|
236
|
+
def call
|
|
237
|
+
# Namespace automatically uses :user from style_capsule
|
|
238
|
+
register_stylesheet("stylesheets/user/my_component", "data-turbo-track": "reload")
|
|
239
|
+
content_tag(:div, "Content")
|
|
240
|
+
end
|
|
207
241
|
end
|
|
208
242
|
```
|
|
209
243
|
|
|
210
|
-
Registered files are rendered via `stylesheet_registry_tags` in your layout, just like inline CSS.
|
|
244
|
+
Registered files are rendered via `stylesheet_registry_tags` in your layout, just like inline CSS. The namespace is automatically used from the component's `style_capsule` configuration when not explicitly specified.
|
|
245
|
+
|
|
246
|
+
**Boot-time file paths:** register static paths once with `StyleCapsule::StylesheetRegistry.register_eager(...)` (initializers, class load). Use `register` / `register_stylesheet` during rendering for request-scoped paths.
|
|
247
|
+
|
|
248
|
+
### Late head injection (Rails)
|
|
249
|
+
|
|
250
|
+
Layouts usually call `stylesheet_registry_tags` in `<head>` before the body renders. Components that call `register_stylesheet` later in the same response would miss that call without help.
|
|
251
|
+
|
|
252
|
+
By default, `StyleCapsule::HeadInjectionMiddleware` appends pending request-scoped stylesheet tags immediately before `</head>` after the response body is built. Disable when you buffer or stream the body yourself:
|
|
253
|
+
|
|
254
|
+
```ruby
|
|
255
|
+
# config/application.rb
|
|
256
|
+
config.style_capsule.head_injection_middleware = false
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
The middleware skips chunked responses (`Transfer-Encoding: chunked`) and does not buffer the body when no pending request-scoped stylesheets remain. For ActionController::Live, SSE, or other streaming HTML, disable it and inject manually with `StyleCapsule::StylesheetRegistry.inject_pending_head_stylesheets` if needed.
|
|
211
260
|
|
|
212
261
|
## Caching Strategies
|
|
213
262
|
|
|
@@ -216,22 +265,22 @@ Registered files are rendered via `stylesheet_registry_tags` in your layout, jus
|
|
|
216
265
|
```ruby
|
|
217
266
|
class MyComponent < ApplicationComponent
|
|
218
267
|
include StyleCapsule::Component
|
|
219
|
-
|
|
268
|
+
style_capsule # No cache strategy set (default: :none)
|
|
220
269
|
end
|
|
221
270
|
```
|
|
222
271
|
|
|
223
272
|
### Time-Based Caching
|
|
224
273
|
|
|
225
274
|
```ruby
|
|
226
|
-
|
|
275
|
+
style_capsule cache_strategy: :time, cache_ttl: 1.hour # Using ActiveSupport::Duration
|
|
227
276
|
# Or using integer seconds:
|
|
228
|
-
|
|
277
|
+
style_capsule cache_strategy: :time, cache_ttl: 3600 # Cache for 1 hour
|
|
229
278
|
```
|
|
230
279
|
|
|
231
280
|
### Custom Proc Caching
|
|
232
281
|
|
|
233
282
|
```ruby
|
|
234
|
-
|
|
283
|
+
style_capsule cache_strategy: ->(css, capsule_id, namespace) {
|
|
235
284
|
cache_key = "css_#{capsule_id}_#{namespace}"
|
|
236
285
|
should_cache = css.length > 100
|
|
237
286
|
expires_at = Time.now + 1800
|
|
@@ -248,7 +297,7 @@ Writes CSS to files for HTTP caching. **Requires class method `def self.componen
|
|
|
248
297
|
```ruby
|
|
249
298
|
class MyComponent < ApplicationComponent
|
|
250
299
|
include StyleCapsule::Component
|
|
251
|
-
|
|
300
|
+
style_capsule cache_strategy: :file
|
|
252
301
|
|
|
253
302
|
# Must use class method for file caching
|
|
254
303
|
def self.component_styles
|
|
@@ -411,79 +460,9 @@ end
|
|
|
411
460
|
|
|
412
461
|
## Non-Rails Support
|
|
413
462
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
### Standalone Usage
|
|
463
|
+
The core library is framework-agnostic. Without Rails, use `StyleCapsule::CssProcessor` directly, `StyleCapsule::Component` with Phlex, or `StyleCapsule::StandaloneHelper` for ERB-style templates. The stylesheet registry falls back to thread-local storage when `ActiveSupport::CurrentAttributes` is not loaded.
|
|
417
464
|
|
|
418
|
-
|
|
419
|
-
require 'style_capsule'
|
|
420
|
-
|
|
421
|
-
# Direct CSS processing
|
|
422
|
-
css = ".section { color: red; }"
|
|
423
|
-
capsule_id = "abc123"
|
|
424
|
-
scoped = StyleCapsule::CssProcessor.scope_selectors(css, capsule_id)
|
|
425
|
-
# => "[data-capsule=\"abc123\"] .section { color: red; }"
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
### Phlex Without Rails
|
|
429
|
-
|
|
430
|
-
```ruby
|
|
431
|
-
require 'phlex'
|
|
432
|
-
require 'style_capsule'
|
|
433
|
-
|
|
434
|
-
class MyComponent < Phlex::HTML
|
|
435
|
-
include StyleCapsule::Component
|
|
436
|
-
|
|
437
|
-
def component_styles
|
|
438
|
-
<<~CSS
|
|
439
|
-
.section { color: red; }
|
|
440
|
-
CSS
|
|
441
|
-
end
|
|
442
|
-
|
|
443
|
-
def view_template
|
|
444
|
-
div(class: "section") { "Hello" }
|
|
445
|
-
end
|
|
446
|
-
end
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
### Sinatra
|
|
450
|
-
|
|
451
|
-
```ruby
|
|
452
|
-
require 'sinatra'
|
|
453
|
-
require 'style_capsule'
|
|
454
|
-
|
|
455
|
-
class MyApp < Sinatra::Base
|
|
456
|
-
helpers StyleCapsule::StandaloneHelper
|
|
457
|
-
|
|
458
|
-
get '/' do
|
|
459
|
-
erb :index
|
|
460
|
-
end
|
|
461
|
-
end
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
```erb
|
|
465
|
-
<!-- views/index.erb -->
|
|
466
|
-
<%= style_capsule do %>
|
|
467
|
-
<style>
|
|
468
|
-
.section { color: red; }
|
|
469
|
-
</style>
|
|
470
|
-
<div class="section">Content</div>
|
|
471
|
-
<% end %>
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
### Stylesheet Registry Without Rails
|
|
475
|
-
|
|
476
|
-
The stylesheet registry automatically uses thread-local storage when ActiveSupport is not available:
|
|
477
|
-
|
|
478
|
-
```ruby
|
|
479
|
-
require 'style_capsule'
|
|
480
|
-
|
|
481
|
-
# Works without Rails
|
|
482
|
-
StyleCapsule::StylesheetRegistry.register_inline(".test { color: red; }", namespace: :test)
|
|
483
|
-
stylesheets = StyleCapsule::StylesheetRegistry.request_inline_stylesheets
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
For more details, see [docs/non_rails_support.md](docs/non_rails_support.md).
|
|
465
|
+
For setup examples, API notes, and the relationship between `style_capsule` and `stylesheet_registry`, see **[docs/non_rails_support.md](docs/non_rails_support.md)**.
|
|
487
466
|
|
|
488
467
|
## How It Works
|
|
489
468
|
|
|
@@ -543,3 +522,11 @@ For detailed security information, see [SECURITY.md](SECURITY.md).
|
|
|
543
522
|
## License
|
|
544
523
|
|
|
545
524
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
525
|
+
|
|
526
|
+
## Sponsors
|
|
527
|
+
|
|
528
|
+
Sponsored by [Kisko Labs](https://www.kiskolabs.com).
|
|
529
|
+
|
|
530
|
+
<a href="https://www.kiskolabs.com">
|
|
531
|
+
<img src="kisko.svg" width="200" alt="Sponsored by Kisko Labs" />
|
|
532
|
+
</a>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Non-Rails usage
|
|
2
|
+
|
|
3
|
+
StyleCapsule works without Rails. Core APIs are framework-agnostic; optional pieces integrate with Rails when `railties` and `activesupport` are present.
|
|
4
|
+
|
|
5
|
+
## Direct CSS processing
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
require "style_capsule"
|
|
9
|
+
|
|
10
|
+
css = ".section { color: red; }"
|
|
11
|
+
capsule_id = "abc123"
|
|
12
|
+
scoped = StyleCapsule::CssProcessor.scope_selectors(css, capsule_id)
|
|
13
|
+
# => "[data-capsule=\"abc123\"] .section { color: red; }"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Phlex (`StyleCapsule::Component`)
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
require "phlex"
|
|
20
|
+
require "style_capsule"
|
|
21
|
+
|
|
22
|
+
class MyComponent < Phlex::HTML
|
|
23
|
+
include StyleCapsule::Component
|
|
24
|
+
|
|
25
|
+
def component_styles
|
|
26
|
+
<<~CSS
|
|
27
|
+
.section { color: red; }
|
|
28
|
+
CSS
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def view_template
|
|
32
|
+
div(class: "section") { "Hello" }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Sinatra and `StyleCapsule::StandaloneHelper`
|
|
38
|
+
|
|
39
|
+
Use `StyleCapsule::StandaloneHelper` for ERB (or similar) without ActionView. It mirrors the Rails `Helper` API, including `style_capsule(tag: :section)` for a custom wrapper element.
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
require "sinatra"
|
|
43
|
+
require "style_capsule"
|
|
44
|
+
|
|
45
|
+
class MyApp < Sinatra::Base
|
|
46
|
+
helpers StyleCapsule::StandaloneHelper
|
|
47
|
+
|
|
48
|
+
get "/" do
|
|
49
|
+
erb :index
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```erb
|
|
55
|
+
<!-- views/index.erb -->
|
|
56
|
+
<%= style_capsule do %>
|
|
57
|
+
<style>
|
|
58
|
+
.section { color: red; }
|
|
59
|
+
</style>
|
|
60
|
+
<div class="section">Content</div>
|
|
61
|
+
<% end %>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Stylesheet registry without Rails
|
|
65
|
+
|
|
66
|
+
When `ActiveSupport::CurrentAttributes` is not loaded, `StyleCapsule::StylesheetRegistry` uses thread-local storage for request-scoped inline CSS and render-time file paths. Use `register_eager` for process-wide file paths that should appear on every request (boot or class load). Use `register` during rendering when a component calls `register_stylesheet`.
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
require "style_capsule"
|
|
70
|
+
|
|
71
|
+
# Boot-time / static file (process-wide manifest)
|
|
72
|
+
StyleCapsule::StylesheetRegistry.register_eager("stylesheets/main", namespace: :default)
|
|
73
|
+
|
|
74
|
+
# Render-time registration (same request only)
|
|
75
|
+
StyleCapsule::StylesheetRegistry.register("stylesheets/page", namespace: :default)
|
|
76
|
+
|
|
77
|
+
# Inline CSS (request-scoped)
|
|
78
|
+
StyleCapsule::StylesheetRegistry.register_inline(".test { color: red; }", namespace: :test)
|
|
79
|
+
stylesheets = StyleCapsule::StylesheetRegistry.request_inline_stylesheets
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Configuration entry points
|
|
83
|
+
|
|
84
|
+
- **`style_capsule`** — Preferred class-level DSL for namespace, cache strategy, CSS scoping (`:selector_patching` or `:nesting`), head rendering, and wrapper `tag:`.
|
|
85
|
+
- **`stylesheet_registry`** — Older alias that enables head rendering and cache options; new code should prefer `style_capsule` for a single configuration surface.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StyleCapsule
|
|
4
|
+
# Validates logical asset paths for stylesheet registration (blocks injection / absurd paths).
|
|
5
|
+
module AssetPath
|
|
6
|
+
MAX_PATH_LENGTH = 1024
|
|
7
|
+
|
|
8
|
+
# @param path [String] Logical path (e.g. "stylesheets/admin/foo" or "builds/capsules/my_component")
|
|
9
|
+
# @return [String] Stripped path
|
|
10
|
+
# @raise [ArgumentError] If path is invalid
|
|
11
|
+
def self.validate_logical_path!(path)
|
|
12
|
+
unless path.is_a?(String)
|
|
13
|
+
raise ArgumentError, "stylesheet path must be a String (got #{path.class})"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
s = path.strip
|
|
17
|
+
validate_non_empty_path!(s)
|
|
18
|
+
validate_path_segments!(s, path)
|
|
19
|
+
s
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.validate_non_empty_path!(stripped_path)
|
|
23
|
+
raise ArgumentError, "stylesheet path cannot be empty" if stripped_path.empty?
|
|
24
|
+
raise ArgumentError, "invalid stylesheet path (no leading slash / absolute URL path): #{stripped_path.inspect}" if stripped_path.start_with?("/")
|
|
25
|
+
raise ArgumentError, "invalid stylesheet path (max #{MAX_PATH_LENGTH} characters, got #{stripped_path.length})" if stripped_path.length > MAX_PATH_LENGTH
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.validate_path_segments!(stripped_path, original_path)
|
|
29
|
+
if stripped_path.split("/").any? { |segment| segment == ".." }
|
|
30
|
+
raise ArgumentError, "invalid stylesheet path (parent segments not allowed): #{original_path.inspect}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
if /["<>|\0\\]/.match?(stripped_path)
|
|
34
|
+
raise ArgumentError, "invalid stylesheet path (disallowed characters): #{original_path.inspect}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
private_class_method :validate_non_empty_path!, :validate_path_segments!
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -57,11 +57,12 @@ module StyleCapsule
|
|
|
57
57
|
|
|
58
58
|
# Iterate over all registered classes
|
|
59
59
|
#
|
|
60
|
+
# Prunes invalid entries on each call (linear in registry size). The set of StyleCapsule
|
|
61
|
+
# components is typically small, so this stays cheap compared to ObjectSpace scans.
|
|
62
|
+
#
|
|
60
63
|
# @yield [Class] Each registered class
|
|
61
64
|
# @return [void]
|
|
62
65
|
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
66
|
@classes.delete_if do |klass|
|
|
66
67
|
# Check if class still exists and is valid
|
|
67
68
|
klass.name.nil? || klass.singleton_class?
|