style_capsule 1.0.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1a5a88ff582594a22522f2b0349e54bfb235d42839a80b29a9118488a09f82df
4
+ data.tar.gz: 4bf81ae48a9cfe5ef9e0b7c5f8ae00abeb38dbdf7ea2dc930293036b1c375832
5
+ SHA512:
6
+ metadata.gz: a75e75f5c7d04616306d2ddaaea7a528eb2d4179071cb96ff6e360d854e940dddcfbfe271bf65fb3e6b4d5957961e5c116708f4017d5e9becd1ee3a76053bce3
7
+ data.tar.gz: b7981f706126585a59659ed0c6e3ec61c40a85767ac72c16a9908c6739a5956bcd2bb9c24f20593bc42402ecccfc5a747b042f85b13a07268dfe747555cb56c4
data/CHANGELOG.md ADDED
@@ -0,0 +1,29 @@
1
+ # CHANGELOG
2
+
3
+ ## 1.0.2 (2025-11-21)
4
+
5
+ - Fix default output directory for CSS files to app/assets/builds/capsules/
6
+
7
+ ## 1.0.1 (2025-11-21)
8
+
9
+ - Update ViewComponent dependency to version 4.0 and adjust compatibility in tests
10
+ - Enhance error handling in style_capsule Rake task for ViewComponent loading issues
11
+ - Remove Rails 6 support from Appraisals file and update Rails 7.2 dependencies
12
+ - Update Rails and ActiveSupport version requirements
13
+ - Add Codecov badge to README
14
+
15
+ ## 1.0.0 (2025-11-20)
16
+
17
+ - Initial stable release
18
+ - Attribute-based CSS scoping for Phlex components, ViewComponent, and ERB templates
19
+ - Component-scoped CSS encapsulation using `[data-capsule="..."]` selectors (combined selector format: `[data-capsule="..."].class`)
20
+ - Per-component-type scope IDs (shared across all instances)
21
+ - Automatic HTML wrapping with scoped elements
22
+ - CSS processor with support for regular selectors, pseudo-classes, `@media` queries, `@keyframes`, and component-scoped `:host` selectors
23
+ - Thread-safe stylesheet registry for head rendering with namespace support
24
+ - Multiple caching strategies: no caching, time-based, custom proc, and file-based caching
25
+ - File-based caching with Rails asset pipeline integration and Rake tasks
26
+ - Security features: path traversal protection, CSS size limits (1MB), scope ID validation, filename validation
27
+ - Ruby >= 3.0 requirement
28
+ - Comprehensive test suite with > 93% coverage
29
+
data/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,398 @@
1
+ # style_capsule
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)
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.
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
+ ## Installation
14
+
15
+ Add to your Gemfile:
16
+
17
+ ```ruby
18
+ gem "style_capsule"
19
+ ```
20
+
21
+ Then run `bundle install`.
22
+
23
+ ## Features
24
+
25
+ - **Attribute-based CSS scoping** (no class name renaming)
26
+ - **Phlex, ViewComponent, and ERB support** with automatic Rails integration
27
+ - **Per-component-type scope IDs** (shared across instances)
28
+ - **CSS Nesting support** (optional, more performant, requires modern browsers)
29
+ - **Stylesheet registry** with thread-safe head rendering, namespace support, and compatibility with Propshaft and other asset bundlers
30
+ - **Multiple cache strategies**: none, time-based, custom proc, and file-based (HTTP caching)
31
+ - **Security protections**: path traversal protection, input validation, size limits
32
+
33
+ ## Usage
34
+
35
+ ### Phlex Components
36
+
37
+ ```ruby
38
+ class MyComponent < ApplicationComponent
39
+ include StyleCapsule::Component
40
+
41
+ def component_styles
42
+ <<~CSS
43
+ .section { color: red; }
44
+ .heading:hover { opacity: 0.8; }
45
+ CSS
46
+ end
47
+
48
+ def view_template
49
+ div(class: "section") do
50
+ h2(class: "heading") { "Hello" }
51
+ end
52
+ end
53
+ end
54
+ ```
55
+
56
+ CSS is automatically scoped with `[data-capsule="..."]` attributes and content is wrapped in a scoped element.
57
+
58
+ ### ViewComponent
59
+
60
+ ```ruby
61
+ class MyComponent < ApplicationComponent
62
+ include StyleCapsule::ViewComponent
63
+
64
+ def component_styles
65
+ <<~CSS
66
+ .section { color: red; }
67
+ CSS
68
+ end
69
+
70
+ def call
71
+ content_tag :div, class: "section" do
72
+ "Hello"
73
+ end
74
+ end
75
+ end
76
+ ```
77
+
78
+ ### ERB Templates
79
+
80
+ ```erb
81
+ <%= style_capsule do %>
82
+ <style>
83
+ .section { color: red; }
84
+ </style>
85
+ <div class="section">Content</div>
86
+ <% end %>
87
+ ```
88
+
89
+ ## CSS Scoping Strategies
90
+
91
+ StyleCapsule supports two CSS scoping strategies:
92
+
93
+ 1. **Selector Patching (default)**: Adds `[data-capsule="..."]` prefix to each selector
94
+ - Better browser support (all modern browsers)
95
+ - Output: `[data-capsule="abc123"] .section { color: red; }`
96
+
97
+ 2. **CSS Nesting (optional)**: Wraps entire CSS in `[data-capsule="..."] { ... }`
98
+ - More performant (no CSS parsing needed)
99
+ - Requires CSS nesting support (Chrome 112+, Firefox 117+, Safari 16.5+)
100
+ - Output: `[data-capsule="abc123"] { .section { color: red; } }`
101
+
102
+ ### Configuration
103
+
104
+ **Per-component:**
105
+
106
+ ```ruby
107
+ class MyComponent < ApplicationComponent
108
+ include StyleCapsule::Component
109
+ css_scoping_strategy :nesting # Use CSS nesting
110
+ end
111
+ ```
112
+
113
+ **Global (in base component class):**
114
+
115
+ ```ruby
116
+ class ApplicationComponent < Phlex::HTML
117
+ include StyleCapsule::Component
118
+ css_scoping_strategy :nesting # Enable for all components
119
+ end
120
+ ```
121
+
122
+ **Note:** If you change the strategy and it doesn't take effect, clear the CSS cache:
123
+
124
+ ```ruby
125
+ MyComponent.clear_css_cache
126
+ ```
127
+
128
+ ## Stylesheet Registry
129
+
130
+ For better performance, register styles for head rendering instead of rendering `<style>` tags in the body:
131
+
132
+ ```ruby
133
+ class MyComponent < ApplicationComponent
134
+ include StyleCapsule::Component
135
+ stylesheet_registry namespace: :admin # Optional namespace
136
+
137
+ def component_styles
138
+ <<~CSS
139
+ .section { color: red; }
140
+ CSS
141
+ end
142
+ end
143
+ ```
144
+
145
+ With cache strategy:
146
+
147
+ ```ruby
148
+ class MyComponent < ApplicationComponent
149
+ include StyleCapsule::Component
150
+ stylesheet_registry namespace: :admin, cache_strategy: :time, cache_ttl: 1.hour
151
+
152
+ def component_styles
153
+ <<~CSS
154
+ .section { color: red; }
155
+ CSS
156
+ end
157
+ end
158
+ ```
159
+
160
+ Then in your layout:
161
+
162
+ ```erb
163
+ <head>
164
+ <%= stylesheet_registrymap_tags %>
165
+ <%= stylesheet_registrymap_tags(namespace: :admin) %>
166
+ </head>
167
+ ```
168
+
169
+ Or in Phlex (requires including `StyleCapsule::PhlexHelper`):
170
+
171
+ ```ruby
172
+ head do
173
+ stylesheet_registrymap_tags
174
+ end
175
+ ```
176
+
177
+ ### Registering Stylesheet Files
178
+
179
+ You can also register external stylesheet files (not inline CSS) for head rendering:
180
+
181
+ **In ERB:**
182
+
183
+ ```erb
184
+ <% register_stylesheet("stylesheets/user/order_select_component", "data-turbo-track": "reload") %>
185
+ <% register_stylesheet("stylesheets/admin/dashboard", namespace: :admin) %>
186
+ ```
187
+
188
+ **In Phlex (requires including `StyleCapsule::PhlexHelper`):**
189
+
190
+ ```ruby
191
+ def view_template
192
+ register_stylesheet("stylesheets/user/order_select_component", "data-turbo-track": "reload")
193
+ register_stylesheet("stylesheets/admin/dashboard", namespace: :admin)
194
+ div { "Content" }
195
+ end
196
+ ```
197
+
198
+ **In ViewComponent (requires including `StyleCapsule::ViewComponentHelper`):**
199
+
200
+ ```ruby
201
+ def call
202
+ register_stylesheet("stylesheets/user/order_select_component", "data-turbo-track": "reload")
203
+ register_stylesheet("stylesheets/admin/dashboard", namespace: :admin)
204
+ content_tag(:div, "Content")
205
+ end
206
+ ```
207
+
208
+ Registered files are rendered via `stylesheet_registrymap_tags` in your layout, just like inline CSS.
209
+
210
+ ## Caching Strategies
211
+
212
+ ### No Caching (Default)
213
+
214
+ ```ruby
215
+ class MyComponent < ApplicationComponent
216
+ include StyleCapsule::Component
217
+ stylesheet_registry # No cache strategy set (default: :none)
218
+ end
219
+ ```
220
+
221
+ ### Time-Based Caching
222
+
223
+ ```ruby
224
+ stylesheet_registry cache_strategy: :time, cache_ttl: 1.hour # Using ActiveSupport::Duration
225
+ # Or using integer seconds:
226
+ stylesheet_registry cache_strategy: :time, cache_ttl: 3600 # Cache for 1 hour
227
+ ```
228
+
229
+ ### Custom Proc Caching
230
+
231
+ ```ruby
232
+ stylesheet_registry cache_strategy: ->(css, capsule_id, namespace) {
233
+ cache_key = "css_#{capsule_id}_#{namespace}"
234
+ should_cache = css.length > 100
235
+ expires_at = Time.now + 1800
236
+ [cache_key, should_cache, expires_at]
237
+ }
238
+ ```
239
+
240
+ **Note:** `cache_strategy` accepts Symbol (`:time`), String (`"time"`), or Proc. Strings are automatically converted to symbols.
241
+
242
+ ### File-Based Caching (HTTP Caching)
243
+
244
+ Writes CSS to files for HTTP caching. **Requires class method `def self.component_styles`**:
245
+
246
+ ```ruby
247
+ class MyComponent < ApplicationComponent
248
+ include StyleCapsule::Component
249
+ stylesheet_registry cache_strategy: :file
250
+
251
+ # Must use class method for file caching
252
+ def self.component_styles
253
+ <<~CSS
254
+ .section { color: red; }
255
+ CSS
256
+ end
257
+ end
258
+ ```
259
+
260
+ **Configuration:**
261
+
262
+ ```ruby
263
+ # config/initializers/style_capsule.rb
264
+ StyleCapsule::CssFileWriter.configure(
265
+ output_dir: Rails.root.join("app/assets/builds/capsules"),
266
+ filename_pattern: ->(component_class, capsule_id) {
267
+ "capsule-#{capsule_id}.css"
268
+ }
269
+ )
270
+ ```
271
+
272
+ **Precompilation:**
273
+
274
+ ```bash
275
+ bin/rails style_capsule:build # Build CSS files
276
+ bin/rails style_capsule:clear # Clear generated files
277
+ ```
278
+
279
+ Files are automatically built during `bin/rails assets:precompile`.
280
+
281
+ **Compatibility:** The stylesheet registry works with Propshaft, Sprockets, and other Rails asset bundlers. Static file paths are collected in a process-wide manifest (similar to Propshaft's approach), while inline CSS is stored per-request.
282
+
283
+ ## Advanced Usage
284
+
285
+ ### Database-Stored CSS
286
+
287
+ For CSS stored in a database (e.g., user-generated styles, themes), use StyleCapsule's CSS processor directly:
288
+
289
+ ```ruby
290
+ # app/models/theme.rb
291
+ class Theme < ApplicationRecord
292
+ def generate_capsule_id
293
+ return capsule_id if capsule_id.present?
294
+ scope_key = "theme_#{id}_#{name}"
295
+ self.capsule_id = "a#{Digest::SHA1.hexdigest(scope_key)}"[0, 8]
296
+ save! if persisted?
297
+ capsule_id
298
+ end
299
+
300
+ def scoped_css
301
+ return scoped_css_cache if scoped_css_cache.present? &&
302
+ scoped_css_updated_at == updated_at
303
+
304
+ current_capsule_id = generate_capsule_id
305
+ scoped = StyleCapsule::CssProcessor.scope_selectors(css_content, current_capsule_id)
306
+
307
+ update_columns(
308
+ scoped_css_cache: scoped,
309
+ scoped_css_updated_at: updated_at,
310
+ capsule_id: current_capsule_id
311
+ )
312
+
313
+ scoped
314
+ end
315
+ end
316
+ ```
317
+
318
+ **Usage:**
319
+
320
+ ```erb
321
+ <div data-capsule="<%= theme.capsule_id %>">
322
+ <style><%= raw theme.scoped_css %></style>
323
+ <div class="header">Content</div>
324
+ </div>
325
+ ```
326
+
327
+ ## CSS Selector Support
328
+
329
+ - Regular selectors: `.section`, `#header`, `div.container`
330
+ - Pseudo-classes and pseudo-elements: `.button:hover`, `.item::before`
331
+ - Multiple selectors: `.a, .b, .c { color: red; }`
332
+ - Component-scoped selectors: `:host`, `:host(.active)`, `:host-context(.theme-dark)`
333
+ - Media queries: `@media (max-width: 768px) { ... }`
334
+
335
+ ## How It Works
336
+
337
+ 1. **Scope ID Generation**: Each component class gets a unique scope ID based on its class name (shared across all instances)
338
+ 2. **CSS Rewriting**: CSS selectors are rewritten to include `[data-capsule="..."]` attribute selectors
339
+ 3. **HTML Wrapping**: Component content is automatically wrapped in a scoped element
340
+ 4. **No Class Renaming**: Class names remain unchanged (unlike Shadow DOM)
341
+
342
+ ## Requirements
343
+
344
+ - Ruby >= 3.0
345
+ - Rails >= 7.0, < 9.0
346
+ - ActiveSupport >= 7.0, < 9.0
347
+
348
+ ## Development
349
+
350
+ ```bash
351
+ bundle install
352
+ bundle exec appraisal install
353
+
354
+ # Run tests
355
+ bundle exec rspec
356
+
357
+ # Run tests for all Rails versions
358
+ bundle exec appraisal rails72 rspec
359
+ bundle exec appraisal rails8ruby34 rspec
360
+
361
+ # Linting
362
+ bundle exec standardrb --fix
363
+ ```
364
+
365
+ ## Contributing
366
+
367
+ Bug reports and pull requests are welcome on GitHub at https://github.com/amkisko/style_capsule.rb
368
+
369
+ Contribution policy:
370
+ - New features are not necessarily added to the gem
371
+ - Pull requests should have test coverage and changelog entry
372
+
373
+ Review policy:
374
+ - Critical fixes: up to 2 calendar weeks
375
+ - Pull requests: up to 6 calendar months
376
+ - Issues: up to 1 calendar year
377
+
378
+ ## Publishing
379
+
380
+ ```sh
381
+ rm style_capsule-*.gem
382
+ gem build style_capsule.gemspec
383
+ gem push style_capsule-*.gem
384
+ ```
385
+
386
+ ## Security
387
+
388
+ StyleCapsule includes security protections:
389
+ - Path traversal protection
390
+ - Input validation
391
+ - Size limits (1MB per component)
392
+ - XSS prevention via Rails' HTML escaping
393
+
394
+ For detailed security information, see [SECURITY.md](SECURITY.md).
395
+
396
+ ## License
397
+
398
+ The gem is available as open source under the terms of the [MIT License](LICENSE.md).
data/SECURITY.md ADDED
@@ -0,0 +1,55 @@
1
+ # SECURITY
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ | ------- | ------------------ |
7
+ | 1.0.x | :white_check_mark: |
8
+ | < 1.0 | :x: |
9
+
10
+ ## Security Guidelines
11
+
12
+ StyleCapsule provides minimal security protections to assist with automated CSS scoping. The gem does not validate, sanitize, or secure CSS content itself—this is the application's responsibility.
13
+
14
+ ### What StyleCapsule Protects
15
+
16
+ - **Path Traversal**: Filenames are validated when writing CSS files to prevent directory traversal attacks
17
+ - **Input Size Limits**: CSS content is limited to 1MB per component to prevent resource exhaustion
18
+ - **Scope ID Validation**: Capsule IDs are validated to prevent injection into HTML attributes
19
+
20
+ ### What StyleCapsule Does NOT Control
21
+
22
+ **Developer Responsibility:**
23
+ - **CSS Content**: StyleCapsule does not validate or sanitize CSS content. Malicious CSS (e.g., data exfiltration via `@import`, CSS injection attacks) is not prevented by the gem
24
+ - **User Input**: Applications must validate and sanitize user-provided CSS before passing it to StyleCapsule
25
+ - **Developer Intent**: The gem trusts that developers provide safe CSS content from trusted sources
26
+
27
+ **Rails Framework:**
28
+ - HTML escaping is handled by Rails' built-in helpers (`content_tag`, etc.)
29
+ - Content Security Policy (CSP) must be configured at the application level
30
+ - File system permissions and access control are managed by the application
31
+
32
+ ### Security Best Practices
33
+
34
+ 1. **Validate User Input**: Never pass untrusted CSS content to StyleCapsule without validation
35
+ 2. **Use Content Security Policy**: Configure CSP headers to restrict inline styles and external resources
36
+ 3. **Sanitize User-Generated CSS**: If allowing user input, sanitize CSS before processing
37
+ 4. **Keep Dependencies Updated**: Use supported Ruby (>= 3.0) and Rails versions with security patches
38
+ 5. **Review Generated Files**: Periodically review files in `app/assets/builds/capsules/` if using file-based caching
39
+
40
+ ### Reporting a Vulnerability
41
+
42
+ If you discover a security vulnerability, please **do not** open a public issue. Instead:
43
+
44
+ 1. **Email**: contact@kiskolabs.com
45
+ 2. **Subject**: `[SECURITY] style_capsule vulnerability report`
46
+ 3. **Include**: Description, steps to reproduce, potential impact, and suggested fix (if any)
47
+
48
+ We will acknowledge receipt within 48 hours and provide an initial assessment within 7 days.
49
+
50
+ ### Security Updates
51
+
52
+ Security updates are released as patch versions (e.g., 1.0.1, 1.0.2) and announced via:
53
+ - GitHub Security Advisories
54
+ - RubyGems release notes
55
+ - CHANGELOG.md