style_capsule 1.4.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 +25 -0
- data/README.md +46 -79
- 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 -316
- 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 -23
- 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 +4 -9
- data/lib/style_capsule/railtie.rb +11 -0
- data/lib/style_capsule/standalone_helper.rb +12 -19
- 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 -318
- data/lib/style_capsule/view_component_helper.rb +2 -2
- data/lib/style_capsule.rb +9 -4
- data/lib/tasks/style_capsule.rake +15 -2
- data/sig/style_capsule.rbs +43 -39
- metadata +84 -9
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "digest/sha1"
|
|
4
|
+
require_relative "component_class_methods"
|
|
4
5
|
# ActiveSupport string extensions are conditionally required in lib/style_capsule.rb
|
|
5
6
|
|
|
6
7
|
module StyleCapsule
|
|
@@ -95,314 +96,7 @@ module StyleCapsule
|
|
|
95
96
|
end
|
|
96
97
|
|
|
97
98
|
module ClassMethods
|
|
98
|
-
|
|
99
|
-
def css_cache
|
|
100
|
-
@css_cache ||= {}
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# Clear the CSS cache for this component class
|
|
104
|
-
#
|
|
105
|
-
# Useful for testing or when you want to force CSS reprocessing.
|
|
106
|
-
# In development, this is automatically called when classes are reloaded.
|
|
107
|
-
#
|
|
108
|
-
# @example
|
|
109
|
-
# MyComponent.clear_css_cache
|
|
110
|
-
def clear_css_cache
|
|
111
|
-
@css_cache = {}
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# Set or get a custom capsule ID for this component class (useful for testing)
|
|
115
|
-
#
|
|
116
|
-
# @param capsule_id [String, nil] The custom capsule ID to use (nil to get current value)
|
|
117
|
-
# @return [String, nil] The current capsule ID if no argument provided
|
|
118
|
-
# @example Setting a custom capsule ID
|
|
119
|
-
# class MyComponent < ApplicationComponent
|
|
120
|
-
# include StyleCapsule::ViewComponent
|
|
121
|
-
# capsule_id "test-capsule-123"
|
|
122
|
-
# end
|
|
123
|
-
# @example Getting the current capsule ID
|
|
124
|
-
# MyComponent.capsule_id # => "test-capsule-123" or nil
|
|
125
|
-
def capsule_id(capsule_id = nil)
|
|
126
|
-
if capsule_id.nil?
|
|
127
|
-
@custom_capsule_id if defined?(@custom_capsule_id)
|
|
128
|
-
else
|
|
129
|
-
@custom_capsule_id = capsule_id.to_s
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
# Configure stylesheet registry for head rendering
|
|
134
|
-
#
|
|
135
|
-
# Enables head rendering and configures namespace and cache strategy in a single call.
|
|
136
|
-
# All parameters are optional - calling without arguments enables head rendering with defaults.
|
|
137
|
-
#
|
|
138
|
-
# @param namespace [Symbol, String, nil] Namespace identifier (nil/blank uses default)
|
|
139
|
-
# @param cache_strategy [Symbol, String, Proc, nil] Cache strategy: :none (default), :time, :proc, :file
|
|
140
|
-
# - Symbol or String: :none, :time, :proc, :file (or "none", "time", "proc", "file")
|
|
141
|
-
# - Proc: Custom cache proc (automatically uses :proc strategy)
|
|
142
|
-
# Proc receives: (css_content, capsule_id, namespace) and should return [cache_key, should_cache, expires_at]
|
|
143
|
-
# @param cache_ttl [Integer, ActiveSupport::Duration, nil] Time-to-live in seconds (for :time strategy). Supports ActiveSupport::Duration (e.g., 1.hour, 30.minutes)
|
|
144
|
-
# @param cache_proc [Proc, nil] Custom cache proc (for :proc strategy, ignored if cache_strategy is a Proc)
|
|
145
|
-
# Proc receives: (css_content, capsule_id, namespace) and should return [cache_key, should_cache, expires_at]
|
|
146
|
-
# @return [void]
|
|
147
|
-
# @example Basic usage (enables head rendering with defaults)
|
|
148
|
-
# class MyComponent < ApplicationComponent
|
|
149
|
-
# include StyleCapsule::ViewComponent
|
|
150
|
-
# stylesheet_registry
|
|
151
|
-
# end
|
|
152
|
-
# @example With namespace
|
|
153
|
-
# class AdminComponent < ApplicationComponent
|
|
154
|
-
# include StyleCapsule::ViewComponent
|
|
155
|
-
# stylesheet_registry namespace: :admin
|
|
156
|
-
# end
|
|
157
|
-
# @example With time-based caching
|
|
158
|
-
# class MyComponent < ApplicationComponent
|
|
159
|
-
# include StyleCapsule::ViewComponent
|
|
160
|
-
# stylesheet_registry cache_strategy: :time, cache_ttl: 1.hour
|
|
161
|
-
# end
|
|
162
|
-
# @example With custom proc caching
|
|
163
|
-
# class MyComponent < ApplicationComponent
|
|
164
|
-
# include StyleCapsule::ViewComponent
|
|
165
|
-
# stylesheet_registry cache_strategy: :proc, cache_proc: ->(css, capsule_id, ns) {
|
|
166
|
-
# cache_key = "css_#{capsule_id}_#{ns}"
|
|
167
|
-
# should_cache = css.length > 100
|
|
168
|
-
# expires_at = Time.now + 1800
|
|
169
|
-
# [cache_key, should_cache, expires_at]
|
|
170
|
-
# }
|
|
171
|
-
# end
|
|
172
|
-
# @example File-based caching (requires class method component_styles)
|
|
173
|
-
# class MyComponent < ApplicationComponent
|
|
174
|
-
# include StyleCapsule::ViewComponent
|
|
175
|
-
# stylesheet_registry cache_strategy: :file
|
|
176
|
-
#
|
|
177
|
-
# def self.component_styles # Must be class method for file caching
|
|
178
|
-
# <<~CSS
|
|
179
|
-
# .section { color: red; }
|
|
180
|
-
# CSS
|
|
181
|
-
# end
|
|
182
|
-
# end
|
|
183
|
-
# @example All options combined
|
|
184
|
-
# class MyComponent < ApplicationComponent
|
|
185
|
-
# include StyleCapsule::ViewComponent
|
|
186
|
-
# stylesheet_registry namespace: :admin, cache_strategy: :time, cache_ttl: 1.hour
|
|
187
|
-
# end
|
|
188
|
-
def stylesheet_registry(namespace: nil, cache_strategy: :none, cache_ttl: nil, cache_proc: nil)
|
|
189
|
-
@head_rendering = true
|
|
190
|
-
@stylesheet_namespace = namespace unless namespace.nil?
|
|
191
|
-
|
|
192
|
-
# Normalize cache_strategy: convert strings to symbols, handle Proc
|
|
193
|
-
normalized_strategy, normalized_proc = normalize_cache_strategy(cache_strategy, cache_proc)
|
|
194
|
-
@inline_cache_strategy = normalized_strategy
|
|
195
|
-
@inline_cache_ttl = cache_ttl
|
|
196
|
-
@inline_cache_proc = normalized_proc
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
private
|
|
200
|
-
|
|
201
|
-
# Normalize cache_strategy to handle Symbol, String, and Proc
|
|
202
|
-
#
|
|
203
|
-
# @param cache_strategy [Symbol, String, Proc, nil] Cache strategy
|
|
204
|
-
# @param cache_proc [Proc, nil] Optional cache proc (ignored if cache_strategy is a Proc)
|
|
205
|
-
# @return [Array<Symbol, Proc|nil>] Normalized strategy and proc
|
|
206
|
-
def normalize_cache_strategy(cache_strategy, cache_proc)
|
|
207
|
-
case cache_strategy
|
|
208
|
-
when Proc
|
|
209
|
-
# If cache_strategy is a Proc, use it as the proc and set strategy to :proc
|
|
210
|
-
[:proc, cache_strategy]
|
|
211
|
-
when String
|
|
212
|
-
# Convert string to symbol
|
|
213
|
-
normalized = cache_strategy.to_sym
|
|
214
|
-
unless [:none, :time, :proc, :file].include?(normalized)
|
|
215
|
-
raise ArgumentError, "cache_strategy must be :none, :time, :proc, or :file (got: #{cache_strategy.inspect})"
|
|
216
|
-
end
|
|
217
|
-
[normalized, cache_proc]
|
|
218
|
-
when Symbol
|
|
219
|
-
unless [:none, :time, :proc, :file].include?(cache_strategy)
|
|
220
|
-
raise ArgumentError, "cache_strategy must be :none, :time, :proc, or :file (got: #{cache_strategy.inspect})"
|
|
221
|
-
end
|
|
222
|
-
[cache_strategy, cache_proc]
|
|
223
|
-
when nil
|
|
224
|
-
[:none, nil]
|
|
225
|
-
else
|
|
226
|
-
raise ArgumentError, "cache_strategy must be a Symbol, String, or Proc (got: #{cache_strategy.class})"
|
|
227
|
-
end
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
# Check if component uses head rendering (checks instance variable, then parent class, defaults to false)
|
|
231
|
-
#
|
|
232
|
-
# @return [Boolean] Whether head rendering is enabled (default: false)
|
|
233
|
-
def head_rendering?
|
|
234
|
-
if defined?(@head_rendering)
|
|
235
|
-
@head_rendering
|
|
236
|
-
elsif superclass.respond_to?(:head_rendering?, true)
|
|
237
|
-
superclass.head_rendering?
|
|
238
|
-
else
|
|
239
|
-
false
|
|
240
|
-
end
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
public :head_rendering?
|
|
244
|
-
|
|
245
|
-
# Get the namespace for stylesheet registry (checks instance variable, then parent class, defaults to nil)
|
|
246
|
-
#
|
|
247
|
-
# @return [Symbol, String, nil] The namespace identifier (default: nil)
|
|
248
|
-
def stylesheet_namespace
|
|
249
|
-
if defined?(@stylesheet_namespace) && @stylesheet_namespace
|
|
250
|
-
@stylesheet_namespace
|
|
251
|
-
elsif superclass.respond_to?(:stylesheet_namespace, true)
|
|
252
|
-
superclass.stylesheet_namespace
|
|
253
|
-
end
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
# Configure StyleCapsule settings
|
|
257
|
-
#
|
|
258
|
-
# All settings support class inheritance - child classes inherit settings from parent classes
|
|
259
|
-
# and can override them by calling style_capsule again with different values.
|
|
260
|
-
#
|
|
261
|
-
# @param namespace [Symbol, String, nil] Default namespace for stylesheets
|
|
262
|
-
# @param cache_strategy [Symbol, String, Proc, nil] Cache strategy: :none (default), :time, :proc, :file
|
|
263
|
-
# @param cache_ttl [Integer, ActiveSupport::Duration, nil] Time-to-live in seconds (for :time strategy)
|
|
264
|
-
# @param cache_proc [Proc, nil] Custom cache proc (for :proc strategy)
|
|
265
|
-
# @param scoping_strategy [Symbol, nil] CSS scoping strategy: :selector_patching (default) or :nesting
|
|
266
|
-
# @param head_rendering [Boolean, nil] Enable head rendering (default: true if any option is set, false otherwise)
|
|
267
|
-
# @return [void]
|
|
268
|
-
# @example Basic usage with namespace
|
|
269
|
-
# class AdminComponent < ApplicationComponent
|
|
270
|
-
# include StyleCapsule::ViewComponent
|
|
271
|
-
# style_capsule namespace: :admin
|
|
272
|
-
#
|
|
273
|
-
# def call
|
|
274
|
-
# register_stylesheet("stylesheets/admin/dashboard") # Uses :admin namespace automatically
|
|
275
|
-
# content_tag(:div, "Content")
|
|
276
|
-
# end
|
|
277
|
-
# end
|
|
278
|
-
# @example With all options
|
|
279
|
-
# class MyComponent < ApplicationComponent
|
|
280
|
-
# include StyleCapsule::ViewComponent
|
|
281
|
-
# style_capsule(
|
|
282
|
-
# namespace: :user,
|
|
283
|
-
# cache_strategy: :time,
|
|
284
|
-
# cache_ttl: 1.hour,
|
|
285
|
-
# scoping_strategy: :nesting
|
|
286
|
-
# )
|
|
287
|
-
# end
|
|
288
|
-
# @example Inheritance - child class inherits parent settings
|
|
289
|
-
# class BaseComponent < ApplicationComponent
|
|
290
|
-
# include StyleCapsule::ViewComponent
|
|
291
|
-
# style_capsule namespace: :admin, cache_strategy: :time, cache_ttl: 1.hour
|
|
292
|
-
# end
|
|
293
|
-
#
|
|
294
|
-
# class ChildComponent < BaseComponent
|
|
295
|
-
# # Inherits namespace: :admin, cache_strategy: :time, cache_ttl: 1.hour
|
|
296
|
-
# # Can override specific settings:
|
|
297
|
-
# style_capsule namespace: :user # Overrides namespace, keeps cache settings
|
|
298
|
-
# end
|
|
299
|
-
def style_capsule(namespace: nil, cache_strategy: nil, cache_ttl: nil, cache_proc: nil, scoping_strategy: nil, head_rendering: nil)
|
|
300
|
-
# Set namespace (stored in instance variable, but getter checks parent class for inheritance)
|
|
301
|
-
if namespace
|
|
302
|
-
@stylesheet_namespace = namespace
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
# Configure cache strategy if provided
|
|
306
|
-
if cache_strategy || cache_ttl || cache_proc
|
|
307
|
-
normalized_strategy, normalized_proc = normalize_cache_strategy(cache_strategy || :none, cache_proc)
|
|
308
|
-
@inline_cache_strategy = normalized_strategy
|
|
309
|
-
# Explicitly set cache_ttl (even if nil) to override parent's value when cache settings are changed
|
|
310
|
-
@inline_cache_ttl = cache_ttl
|
|
311
|
-
@inline_cache_proc = normalized_proc
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
# Configure CSS scoping strategy if provided
|
|
315
|
-
if scoping_strategy
|
|
316
|
-
unless [:selector_patching, :nesting].include?(scoping_strategy)
|
|
317
|
-
raise ArgumentError, "scoping_strategy must be :selector_patching or :nesting (got: #{scoping_strategy.inspect})"
|
|
318
|
-
end
|
|
319
|
-
@css_scoping_strategy = scoping_strategy
|
|
320
|
-
end
|
|
321
|
-
|
|
322
|
-
# Enable head rendering if explicitly set or if any option is provided (except scoping_strategy)
|
|
323
|
-
if head_rendering.nil?
|
|
324
|
-
@head_rendering = true if namespace || cache_strategy || cache_ttl || cache_proc
|
|
325
|
-
else
|
|
326
|
-
@head_rendering = head_rendering
|
|
327
|
-
end
|
|
328
|
-
end
|
|
329
|
-
|
|
330
|
-
# Get the custom scope ID if set (alias for capsule_id getter)
|
|
331
|
-
def custom_capsule_id
|
|
332
|
-
@custom_capsule_id if defined?(@custom_capsule_id)
|
|
333
|
-
end
|
|
334
|
-
|
|
335
|
-
# Get inline cache strategy (checks instance variable, then parent class, defaults to nil)
|
|
336
|
-
#
|
|
337
|
-
# @return [Symbol, nil] The cache strategy (default: nil)
|
|
338
|
-
def inline_cache_strategy
|
|
339
|
-
if defined?(@inline_cache_strategy) && @inline_cache_strategy
|
|
340
|
-
@inline_cache_strategy
|
|
341
|
-
elsif superclass.respond_to?(:inline_cache_strategy, true)
|
|
342
|
-
superclass.inline_cache_strategy
|
|
343
|
-
end
|
|
344
|
-
end
|
|
345
|
-
|
|
346
|
-
# Get inline cache TTL (checks instance variable, then parent class, defaults to nil)
|
|
347
|
-
#
|
|
348
|
-
# @return [Integer, ActiveSupport::Duration, nil] The cache TTL (default: nil)
|
|
349
|
-
def inline_cache_ttl
|
|
350
|
-
if defined?(@inline_cache_ttl)
|
|
351
|
-
@inline_cache_ttl
|
|
352
|
-
elsif superclass.respond_to?(:inline_cache_ttl, true)
|
|
353
|
-
superclass.inline_cache_ttl
|
|
354
|
-
end
|
|
355
|
-
end
|
|
356
|
-
|
|
357
|
-
# Get inline cache proc (checks instance variable, then parent class, defaults to nil)
|
|
358
|
-
#
|
|
359
|
-
# @return [Proc, nil] The cache proc (default: nil)
|
|
360
|
-
def inline_cache_proc
|
|
361
|
-
if defined?(@inline_cache_proc)
|
|
362
|
-
@inline_cache_proc
|
|
363
|
-
elsif superclass.respond_to?(:inline_cache_proc, true)
|
|
364
|
-
superclass.inline_cache_proc
|
|
365
|
-
end
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
public :head_rendering?, :stylesheet_namespace, :style_capsule, :custom_capsule_id, :inline_cache_strategy, :inline_cache_ttl, :inline_cache_proc
|
|
369
|
-
|
|
370
|
-
# Get CSS scoping strategy (checks instance variable, then parent class, defaults to :selector_patching)
|
|
371
|
-
#
|
|
372
|
-
# @return [Symbol] The current scoping strategy (default: :selector_patching)
|
|
373
|
-
def css_scoping_strategy
|
|
374
|
-
if defined?(@css_scoping_strategy) && @css_scoping_strategy
|
|
375
|
-
@css_scoping_strategy
|
|
376
|
-
elsif superclass.respond_to?(:css_scoping_strategy, true)
|
|
377
|
-
superclass.css_scoping_strategy
|
|
378
|
-
else
|
|
379
|
-
:selector_patching
|
|
380
|
-
end
|
|
381
|
-
end
|
|
382
|
-
|
|
383
|
-
public :css_scoping_strategy
|
|
384
|
-
|
|
385
|
-
# Set or get options for stylesheet_link_tag when using file-based caching
|
|
386
|
-
#
|
|
387
|
-
# @param options [Hash, nil] Options to pass to stylesheet_link_tag (e.g., "data-turbo-track": "reload", omit to get current value)
|
|
388
|
-
# @return [Hash, nil] The current stylesheet link options if no argument provided
|
|
389
|
-
# @example Setting stylesheet link options
|
|
390
|
-
# class MyComponent < ApplicationComponent
|
|
391
|
-
# include StyleCapsule::ViewComponent
|
|
392
|
-
# stylesheet_registry cache_strategy: :file
|
|
393
|
-
# stylesheet_link_options "data-turbo-track": "reload"
|
|
394
|
-
# end
|
|
395
|
-
# @example Getting the current options
|
|
396
|
-
# MyComponent.stylesheet_link_options # => {"data-turbo-track" => "reload"} or nil
|
|
397
|
-
def stylesheet_link_options(options = nil)
|
|
398
|
-
if options.nil?
|
|
399
|
-
@stylesheet_link_options if defined?(@stylesheet_link_options)
|
|
400
|
-
else
|
|
401
|
-
@stylesheet_link_options = options
|
|
402
|
-
end
|
|
403
|
-
end
|
|
404
|
-
|
|
405
|
-
public :stylesheet_link_options
|
|
99
|
+
include StyleCapsule::ComponentClassMethods
|
|
406
100
|
end
|
|
407
101
|
|
|
408
102
|
# Module that wraps call to add scoped wrapper
|
|
@@ -415,8 +109,11 @@ module StyleCapsule
|
|
|
415
109
|
# Get content from original call method
|
|
416
110
|
content_html = super
|
|
417
111
|
|
|
112
|
+
# Get wrapper tag
|
|
113
|
+
tag = self.class.wrapper_tag
|
|
114
|
+
|
|
418
115
|
# Wrap content in scoped element
|
|
419
|
-
scoped_wrapper = helpers.content_tag(
|
|
116
|
+
scoped_wrapper = helpers.content_tag(tag, content_html.html_safe, data: {capsule: component_capsule})
|
|
420
117
|
|
|
421
118
|
# Combine styles and wrapped content
|
|
422
119
|
(styles_html + scoped_wrapper).html_safe
|
|
@@ -450,6 +147,7 @@ module StyleCapsule
|
|
|
450
147
|
# File caching is only allowed for class method component_styles.
|
|
451
148
|
#
|
|
452
149
|
# @return [String] HTML string with style tag or empty string
|
|
150
|
+
# rubocop:disable Metrics/AbcSize -- coordinates head vs body rendering, caching, and registry
|
|
453
151
|
def render_capsule_styles
|
|
454
152
|
css_content = component_styles_content
|
|
455
153
|
return "".html_safe if css_content.nil? || css_content.to_s.strip.empty?
|
|
@@ -495,6 +193,7 @@ module StyleCapsule
|
|
|
495
193
|
helpers.content_tag(:style, scoped_css.html_safe, type: "text/css")
|
|
496
194
|
end
|
|
497
195
|
end
|
|
196
|
+
# rubocop:enable Metrics/AbcSize
|
|
498
197
|
|
|
499
198
|
# Check if component should use head rendering
|
|
500
199
|
#
|
|
@@ -506,11 +205,10 @@ module StyleCapsule
|
|
|
506
205
|
|
|
507
206
|
# Scope CSS and return scoped CSS with attribute selectors
|
|
508
207
|
def scope_css(css_content)
|
|
509
|
-
# Use class-level cache to avoid reprocessing same CSS
|
|
510
|
-
# Include capsule_id and scoping strategy in cache key
|
|
511
208
|
capsule_id = component_capsule
|
|
512
209
|
scoping_strategy = self.class.css_scoping_strategy
|
|
513
|
-
|
|
210
|
+
css_fingerprint = Digest::SHA1.hexdigest(css_content.to_s)
|
|
211
|
+
cache_key = "#{self.class.name}:#{capsule_id}:#{scoping_strategy}:#{css_fingerprint}"
|
|
514
212
|
|
|
515
213
|
if self.class.css_cache.key?(cache_key)
|
|
516
214
|
return self.class.css_cache[cache_key]
|
|
@@ -519,15 +217,12 @@ module StyleCapsule
|
|
|
519
217
|
# Use the configured scoping strategy
|
|
520
218
|
scoped_css = case scoping_strategy
|
|
521
219
|
when :nesting
|
|
522
|
-
CssProcessor.scope_with_nesting(css_content, capsule_id)
|
|
220
|
+
CssProcessor.scope_with_nesting(css_content, capsule_id, component_class: self.class)
|
|
523
221
|
else # :selector_patching (default)
|
|
524
|
-
CssProcessor.scope_selectors(css_content, capsule_id)
|
|
222
|
+
CssProcessor.scope_selectors(css_content, capsule_id, component_class: self.class)
|
|
525
223
|
end
|
|
526
224
|
|
|
527
|
-
|
|
528
|
-
self.class.css_cache[cache_key] = scoped_css
|
|
529
|
-
|
|
530
|
-
scoped_css
|
|
225
|
+
self.class.store_css_cache(cache_key, scoped_css)
|
|
531
226
|
end
|
|
532
227
|
|
|
533
228
|
# Generate a unique capsule ID based on component class name (per-component-type)
|
|
@@ -15,7 +15,7 @@ module StyleCapsule
|
|
|
15
15
|
#
|
|
16
16
|
# Usage in ViewComponent components:
|
|
17
17
|
# class MyComponent < ApplicationComponent
|
|
18
|
-
#
|
|
18
|
+
# style_capsule namespace: :user # Set default namespace for register_stylesheet
|
|
19
19
|
#
|
|
20
20
|
# def call
|
|
21
21
|
# register_stylesheet("stylesheets/user/my_component") # Uses :user namespace automatically
|
|
@@ -32,7 +32,7 @@ module StyleCapsule
|
|
|
32
32
|
# content_tag(:div, "Content")
|
|
33
33
|
# end
|
|
34
34
|
#
|
|
35
|
-
# If the component has a default namespace set via
|
|
35
|
+
# If the component has a default namespace set via style_capsule or stylesheet_registry,
|
|
36
36
|
# it will be used automatically when namespace is not explicitly provided.
|
|
37
37
|
#
|
|
38
38
|
# @param file_path [String] Path to stylesheet (relative to app/assets/stylesheets)
|
data/lib/style_capsule.rb
CHANGED
|
@@ -72,9 +72,12 @@ end
|
|
|
72
72
|
# <%= stylesheet_registry_tags(namespace: :admin) %>
|
|
73
73
|
#
|
|
74
74
|
# @example Namespace Support
|
|
75
|
-
# #
|
|
76
|
-
# StyleCapsule::StylesheetRegistry.
|
|
77
|
-
# StyleCapsule::StylesheetRegistry.
|
|
75
|
+
# # Eager registrations (boot / class load; process-wide manifest)
|
|
76
|
+
# StyleCapsule::StylesheetRegistry.register_eager('stylesheets/admin/dashboard', namespace: :admin)
|
|
77
|
+
# StyleCapsule::StylesheetRegistry.register_eager('stylesheets/user/profile', namespace: :user)
|
|
78
|
+
#
|
|
79
|
+
# # Render-time registrations (request-scoped; also picked up by HeadInjectionMiddleware)
|
|
80
|
+
# StyleCapsule::StylesheetRegistry.register('stylesheets/page', namespace: :user)
|
|
78
81
|
#
|
|
79
82
|
# # Render all namespaces (default)
|
|
80
83
|
# <%= stylesheet_registry_tags %>
|
|
@@ -85,7 +88,7 @@ end
|
|
|
85
88
|
# @example File-Based Caching (HTTP Caching)
|
|
86
89
|
# class MyComponent < ApplicationComponent
|
|
87
90
|
# include StyleCapsule::Component
|
|
88
|
-
#
|
|
91
|
+
# style_capsule cache_strategy: :file # Writes CSS to files for HTTP caching
|
|
89
92
|
# end
|
|
90
93
|
#
|
|
91
94
|
# # CSS files are written to app/assets/builds/capsules/
|
|
@@ -94,6 +97,8 @@ end
|
|
|
94
97
|
module StyleCapsule
|
|
95
98
|
require_relative "style_capsule/version"
|
|
96
99
|
require_relative "style_capsule/instrumentation"
|
|
100
|
+
require_relative "style_capsule/asset_path"
|
|
101
|
+
require_relative "style_capsule/helper_scope_cache"
|
|
97
102
|
require_relative "style_capsule/css_processor"
|
|
98
103
|
require_relative "style_capsule/css_file_writer"
|
|
99
104
|
require_relative "style_capsule/stylesheet_registry"
|
|
@@ -16,7 +16,20 @@ namespace :style_capsule do
|
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
# Hook into Rails asset precompilation (similar to Tailwind CSS)
|
|
19
|
+
# Hook into Rails asset precompilation (similar to Tailwind CSS).
|
|
20
|
+
# Set `config.style_capsule.run_on_precompile = false` to skip.
|
|
21
|
+
task "style_capsule:precompile_hook" => :environment do
|
|
22
|
+
cfg = Rails.application.config
|
|
23
|
+
run = if cfg.respond_to?(:style_capsule) && cfg.style_capsule
|
|
24
|
+
cfg.style_capsule.run_on_precompile != false
|
|
25
|
+
else
|
|
26
|
+
true
|
|
27
|
+
end
|
|
28
|
+
next unless run
|
|
29
|
+
|
|
30
|
+
Rake::Task["style_capsule:build"].invoke
|
|
31
|
+
end
|
|
32
|
+
|
|
20
33
|
if defined?(Rails)
|
|
21
|
-
Rake::Task["assets:precompile"].enhance(["style_capsule:
|
|
34
|
+
Rake::Task["assets:precompile"].enhance(["style_capsule:precompile_hook"]) if Rake::Task.task_defined?("assets:precompile")
|
|
22
35
|
end
|
data/sig/style_capsule.rbs
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
module StyleCapsule
|
|
2
2
|
VERSION: String
|
|
3
3
|
|
|
4
|
+
module AssetPath
|
|
5
|
+
def self.validate_logical_path!: (String path) -> String
|
|
6
|
+
end
|
|
7
|
+
|
|
4
8
|
module CssProcessor
|
|
5
|
-
def self.scope_selectors: (String css_string, String capsule_id) -> String
|
|
6
|
-
def self.scope_with_nesting: (String css_string, String capsule_id) -> String
|
|
9
|
+
def self.scope_selectors: (String css_string, String capsule_id, ?component_class: Class | String | nil) -> String
|
|
10
|
+
def self.scope_with_nesting: (String css_string, String capsule_id, ?component_class: Class | String | nil) -> String
|
|
7
11
|
def self.strip_comments: (String css) -> String
|
|
8
12
|
end
|
|
9
13
|
|
|
@@ -17,43 +21,59 @@ module StyleCapsule
|
|
|
17
21
|
def self.enabled?: () -> bool
|
|
18
22
|
end
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
# At runtime inherits Object when ActiveSupport::CurrentAttributes is unavailable.
|
|
25
|
+
class StylesheetRegistry
|
|
21
26
|
DEFAULT_NAMESPACE: Symbol
|
|
22
27
|
|
|
23
28
|
def self.normalize_namespace: (Symbol | String | nil namespace) -> Symbol
|
|
24
29
|
def self.register: (String file_path, ?namespace: Symbol | String | nil, **Hash[untyped, untyped] options) -> void
|
|
30
|
+
def self.register_eager: (String file_path, ?namespace: Symbol | String | nil, **Hash[untyped, untyped] options) -> void
|
|
25
31
|
def self.register_inline: (String css_content, ?namespace: Symbol | String | nil, ?capsule_id: String | nil, ?cache_key: String | nil, ?cache_strategy: Symbol, ?cache_ttl: Integer | ActiveSupport::Duration | nil, ?cache_proc: Proc | nil, ?component_class: Class | nil, ?stylesheet_link_options: Hash[untyped, untyped] | nil) -> void
|
|
26
32
|
def self.cached_inline: (String cache_key, cache_strategy: Symbol, ?cache_ttl: Integer | ActiveSupport::Duration | nil, ?cache_proc: Proc | nil, ?css_content: String | nil, ?capsule_id: String | nil, ?namespace: Symbol | nil) -> String | nil
|
|
27
33
|
def self.cache_inline_css: (String cache_key, String css_content, cache_strategy: Symbol, ?cache_ttl: Integer | ActiveSupport::Duration | nil, ?cache_proc: Proc | nil, ?capsule_id: String | nil, ?namespace: Symbol | nil) -> void
|
|
28
34
|
def self.clear_inline_cache: (?String cache_key) -> void
|
|
29
35
|
def self.cleanup_expired_cache: () -> Integer
|
|
30
|
-
def self.manifest_files: () -> Hash[Symbol,
|
|
36
|
+
def self.manifest_files: () -> Hash[Symbol, Array[Hash[Symbol, untyped]]]
|
|
31
37
|
def self.request_inline_stylesheets: () -> Hash[Symbol, Array[Hash[Symbol, untyped]]]
|
|
38
|
+
def self.request_stylesheet_files: () -> Hash[Symbol, Hash[String, Hash[Symbol, untyped]]]
|
|
32
39
|
def self.stylesheets_for: (?namespace: Symbol | String | nil) -> Array[Hash[Symbol, untyped]]
|
|
33
40
|
def self.clear: (?namespace: Symbol | String | nil) -> void
|
|
34
41
|
def self.clear_manifest: (?namespace: Symbol | String | nil) -> void
|
|
35
42
|
def self.render_head_stylesheets: (?ActionView::Base | nil view_context, ?namespace: Symbol | String | nil) -> String
|
|
43
|
+
def self.inject_pending_head_stylesheets: (String html, ?ActionView::Base | nil view_context) -> String
|
|
44
|
+
def self.pending_head_stylesheets?: () -> bool
|
|
36
45
|
def self.any?: (?namespace: Symbol | String | nil) -> bool
|
|
37
46
|
end
|
|
38
47
|
|
|
48
|
+
class HeadInjectionMiddleware
|
|
49
|
+
def initialize: (untyped app) -> void
|
|
50
|
+
def call: (Hash[Symbol, untyped] env) -> [Integer, Hash[String, String], untyped]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
module ComponentClassMethods
|
|
54
|
+
MAX_CSS_CACHE_ENTRIES: Integer
|
|
55
|
+
def css_cache: () -> Hash[String, String]
|
|
56
|
+
def store_css_cache: (String cache_key, String scoped_css) -> String
|
|
57
|
+
def clear_css_cache: () -> void
|
|
58
|
+
def capsule_id: () -> String | nil
|
|
59
|
+
def capsule_id: (String capsule_id) -> String
|
|
60
|
+
def stylesheet_registry: (?namespace: Symbol | String | nil, ?cache_strategy: Symbol, ?cache_ttl: Integer | ActiveSupport::Duration | nil, ?cache_proc: Proc | nil) -> void
|
|
61
|
+
def style_capsule: (?namespace: Symbol | String | nil, ?cache_strategy: Symbol | String | Proc | nil, ?cache_ttl: Integer | ActiveSupport::Duration | nil, ?cache_proc: Proc | nil, ?scoping_strategy: Symbol | nil, ?head_rendering: bool | nil, ?tag: Symbol | String | nil) -> void
|
|
62
|
+
def head_rendering?: () -> bool
|
|
63
|
+
def stylesheet_namespace: () -> Symbol | String | nil
|
|
64
|
+
def custom_capsule_id: () -> String | nil
|
|
65
|
+
def inline_cache_strategy: () -> Symbol | nil
|
|
66
|
+
def inline_cache_strategy: (Symbol strategy, ?ttl: Integer | nil, ?cache_proc: Proc | nil) -> Symbol
|
|
67
|
+
def inline_cache_ttl: () -> Integer | ActiveSupport::Duration | nil
|
|
68
|
+
def inline_cache_proc: () -> Proc | nil
|
|
69
|
+
def stylesheet_link_options: () -> Hash[untyped, untyped] | nil
|
|
70
|
+
def stylesheet_link_options: (Hash[untyped, untyped] options) -> Hash[untyped, untyped]
|
|
71
|
+
def css_scoping_strategy: () -> Symbol
|
|
72
|
+
end
|
|
73
|
+
|
|
39
74
|
module Component
|
|
40
75
|
module ClassMethods
|
|
41
|
-
|
|
42
|
-
def clear_css_cache: () -> void
|
|
43
|
-
def capsule_id: () -> String | nil
|
|
44
|
-
def capsule_id: (String capsule_id) -> String
|
|
45
|
-
def stylesheet_registry: (?namespace: Symbol | String | nil, ?cache_strategy: Symbol, ?cache_ttl: Integer | ActiveSupport::Duration | nil, ?cache_proc: Proc | nil) -> void
|
|
46
|
-
def style_capsule: (?namespace: Symbol | String | nil, ?cache_strategy: Symbol | String | Proc | nil, ?cache_ttl: Integer | ActiveSupport::Duration | nil, ?cache_proc: Proc | nil, ?scoping_strategy: Symbol | nil, ?head_rendering: bool | nil) -> void
|
|
47
|
-
def head_rendering?: () -> bool
|
|
48
|
-
def stylesheet_namespace: () -> Symbol | String | nil
|
|
49
|
-
def custom_capsule_id: () -> String | nil
|
|
50
|
-
def inline_cache_strategy: () -> Symbol | nil
|
|
51
|
-
def inline_cache_strategy: (Symbol strategy, ?ttl: Integer | nil, ?cache_proc: Proc | nil) -> Symbol
|
|
52
|
-
def inline_cache_ttl: () -> Integer | ActiveSupport::Duration | nil
|
|
53
|
-
def inline_cache_proc: () -> Proc | nil
|
|
54
|
-
def stylesheet_link_options: () -> Hash[untyped, untyped] | nil
|
|
55
|
-
def stylesheet_link_options: (Hash[untyped, untyped] options) -> Hash[untyped, untyped]
|
|
56
|
-
def css_scoping_strategy: () -> Symbol
|
|
76
|
+
include ComponentClassMethods
|
|
57
77
|
end
|
|
58
78
|
|
|
59
79
|
def component_capsule: () -> String
|
|
@@ -62,22 +82,7 @@ module StyleCapsule
|
|
|
62
82
|
|
|
63
83
|
module ViewComponent
|
|
64
84
|
module ClassMethods
|
|
65
|
-
|
|
66
|
-
def clear_css_cache: () -> void
|
|
67
|
-
def capsule_id: () -> String | nil
|
|
68
|
-
def capsule_id: (String capsule_id) -> String
|
|
69
|
-
def stylesheet_registry: (?namespace: Symbol | String | nil, ?cache_strategy: Symbol, ?cache_ttl: Integer | ActiveSupport::Duration | nil, ?cache_proc: Proc | nil) -> void
|
|
70
|
-
def style_capsule: (?namespace: Symbol | String | nil, ?cache_strategy: Symbol | String | Proc | nil, ?cache_ttl: Integer | ActiveSupport::Duration | nil, ?cache_proc: Proc | nil, ?scoping_strategy: Symbol | nil, ?head_rendering: bool | nil) -> void
|
|
71
|
-
def head_rendering?: () -> bool
|
|
72
|
-
def stylesheet_namespace: () -> Symbol | String | nil
|
|
73
|
-
def custom_capsule_id: () -> String | nil
|
|
74
|
-
def inline_cache_strategy: () -> Symbol | nil
|
|
75
|
-
def inline_cache_strategy: (Symbol strategy, ?ttl: Integer | nil, ?cache_proc: Proc | nil) -> Symbol
|
|
76
|
-
def inline_cache_ttl: () -> Integer | ActiveSupport::Duration | nil
|
|
77
|
-
def inline_cache_proc: () -> Proc | nil
|
|
78
|
-
def stylesheet_link_options: () -> Hash[untyped, untyped] | nil
|
|
79
|
-
def stylesheet_link_options: (Hash[untyped, untyped] options) -> Hash[untyped, untyped]
|
|
80
|
-
def css_scoping_strategy: () -> Symbol
|
|
85
|
+
include ComponentClassMethods
|
|
81
86
|
end
|
|
82
87
|
|
|
83
88
|
def component_capsule: () -> String
|
|
@@ -92,17 +97,16 @@ module StyleCapsule
|
|
|
92
97
|
module Helper
|
|
93
98
|
def generate_capsule_id: (String css_content) -> String
|
|
94
99
|
def scope_css: (String css_content, String capsule_id) -> String
|
|
95
|
-
def style_capsule: (?String css_content, ?capsule_id: String | nil) { () -> String } -> String
|
|
100
|
+
def style_capsule: (?String css_content, ?capsule_id: String | nil, ?tag: Symbol | String) { () -> String } -> String
|
|
96
101
|
def register_stylesheet: (String file_path, ?namespace: Symbol | String | nil, **Hash[untyped, untyped] options) -> void
|
|
97
102
|
def stylesheet_registry_tags: (?namespace: Symbol | String | nil) -> String
|
|
98
103
|
end
|
|
99
104
|
|
|
100
105
|
module PhlexHelper
|
|
101
106
|
def register_stylesheet: (String file_path, ?namespace: Symbol | String | nil, **Hash[untyped, untyped] options) -> void
|
|
102
|
-
def stylesheet_registry_tags: (?namespace: Symbol | String | nil) ->
|
|
107
|
+
def stylesheet_registry_tags: (?namespace: Symbol | String | nil) -> String
|
|
103
108
|
end
|
|
104
109
|
|
|
105
110
|
class Railtie < Rails::Railtie
|
|
106
111
|
end
|
|
107
112
|
end
|
|
108
|
-
|