view_component 4.17.0 → 4.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 49aef67be789faf45059eeab902b02dcc50197a5b2da2482306966f809c3a2a3
4
- data.tar.gz: e8bab208c38f90f778eef18ba4f2f9a41593c88b4332870b537d11deaafcb647
3
+ metadata.gz: 2f3ee513a8505728d9295aa0a7c6ce8e228f256058c13f67591ac7697ab4eec1
4
+ data.tar.gz: 7b4a193c71baab6711b130dde0dbe2e84297db77d1e3ae1ac26805fe7272b796
5
5
  SHA512:
6
- metadata.gz: 786b0a2c41adef88d25b11755a567b9adc356830ea2d23e9eb16b20d2882c38cbee10c0c14a6b2e052363f200b5493bdca493fdda87c514a109c93fac8501cb6
7
- data.tar.gz: 7eb9c8c3234baf98fd725cdaf0d9ceae6a083f068bb043ed12b8db4162666370e4ad70f449adc5477c056dc72d12f6850090933579944480fd7618b2e3c0b51a
6
+ metadata.gz: b196d2a623e878c83325fe8ed684e0d8c938e9daa51668d1fd0665628162d29d6856748d76b65917fe3c6a3849486d77f1a8f50929d1207501fb5d66a2c9d37f
7
+ data.tar.gz: aecd6d15e2e16dac5557f9e9a082b191a9c36a604dbc9e4cd067aedeee0e478e7fd0d380267d2a623977c3b58f6666e9ead18ac8e9119a2c6216f350d14bb834
data/docs/CHANGELOG.md CHANGED
@@ -10,6 +10,12 @@ nav_order: 6
10
10
 
11
11
  ## main
12
12
 
13
+ ## 4.18.0
14
+
15
+ * Enable caching support for all components by default, and rename `ViewComponent::ExperimentallyCacheable` to `ViewComponent::Cacheable`. Including the module is no longer necessary. Breaking: remove existing `include ViewComponent::ExperimentallyCacheable` lines. Caching is no longer experimental.
16
+
17
+ *Erik Axel Nielsen, Joel Hawksley*
18
+
13
19
  ## 4.17.0
14
20
 
15
21
  * Only register the executor hook that watches component templates when Action View doesn't cache templates at boot. With template caching on, as in production, the hook did nothing but still read `ActionView::Base.cache_template_loading` on every request. That value lives in a class variable, which non-main Ractors can't read on Ruby 4.0, so under Rails' experimental `Rails.application.ractorize!` every request failed.
@@ -3,10 +3,12 @@
3
3
  require "action_view"
4
4
  require "action_view/base"
5
5
  require "view_component/collection"
6
+ require "view_component/cache_digest"
6
7
  require "view_component/compile_cache"
7
8
  require "view_component/compiler"
8
9
  require "view_component/config"
9
10
  require "view_component/errors"
11
+ require "view_component/cacheable"
10
12
  require "view_component/inline_template"
11
13
  require "view_component/preview"
12
14
  require "view_component/request_details"
@@ -31,6 +33,20 @@ end
31
33
  module ViewComponent
32
34
  class Base
33
35
  class << self
36
+ # A digest of the component's source, ancestors, and rendered dependencies.
37
+ #
38
+ # @param finder [ActionView::LookupContext, nil]
39
+ # @param format [Symbol]
40
+ #
41
+ # @return [String]
42
+ def cache_digest(finder: nil, format: :html)
43
+ ViewComponent::CacheDigest.digest(
44
+ self,
45
+ finder: finder || ViewComponent::CacheDigest.default_finder,
46
+ format: format
47
+ )
48
+ end
49
+
34
50
  delegate(*ViewComponent::Config.defaults.keys, to: :config)
35
51
 
36
52
  # Returns the current config.
@@ -106,6 +122,10 @@ module ViewComponent
106
122
  def render_in(view_context, **, &block)
107
123
  self.class.__vc_compile(raise_errors: true) unless self.class.__vc_compiled?
108
124
 
125
+ if self.class.__vc_cache_output && !@__vc_cache_bypassed
126
+ return __vc_render_cached(view_context, block)
127
+ end
128
+
109
129
  __vc_check_reused_instance!
110
130
  __vc_reset_render_state!
111
131
 
@@ -423,6 +443,21 @@ module ViewComponent
423
443
  @view_context.instance_variable_set(:@virtual_path, old_virtual_path)
424
444
  end
425
445
 
446
+ # Use the component's source digest for fragments inside its own template.
447
+ #
448
+ # @private
449
+ def digest_path_from_template(template)
450
+ component_path = self.class.virtual_path
451
+ return super unless component_path && template.virtual_path == component_path
452
+
453
+ digest = self.class.cache_digest(
454
+ finder: lookup_context,
455
+ format: template.format || lookup_context.formats.first || :html
456
+ )
457
+
458
+ digest.present? ? "#{component_path}:#{digest}" : component_path
459
+ end
460
+
426
461
  private
427
462
 
428
463
  attr_reader :view_context
@@ -668,6 +703,7 @@ module ViewComponent
668
703
  # owner is included in a prefix like `ApplicationComponent.inherited`.
669
704
  child.identifier = caller_locations(1, 10).reject { |l| l.base_label == "inherited" }[0].path
670
705
  child.virtual_path = child.name&.underscore
706
+ ViewComponent::CacheDigest.register(child)
671
707
 
672
708
  # Set collection parameter to the extended component
673
709
  child.with_collection_parameter(__vc_provided_collection_parameter)
@@ -707,6 +743,17 @@ module ViewComponent
707
743
  @__vc_compiler ||= Compiler.new(self)
708
744
  end
709
745
 
746
+ # Synthetic digest source computed at boot. Not inherited, since each
747
+ # component's source differs.
748
+ #
749
+ # @private
750
+ attr_accessor :__vc_cache_digest_source
751
+
752
+ # @private
753
+ def __vc_template_formats
754
+ __vc_compiler.template_formats
755
+ end
756
+
710
757
  # Set the parameter name used when rendering elements of a collection ([documentation](/guide/collections)):
711
758
  #
712
759
  # ```ruby
@@ -857,6 +904,8 @@ module ViewComponent
857
904
  attr_reader :__vc_provided_collection_parameter
858
905
  end
859
906
 
907
+ include ViewComponent::Cacheable
908
+
860
909
  ActiveSupport.run_load_hooks(:view_component, self)
861
910
  end
862
911
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/digestor"
4
+
5
+ module ViewComponent
6
+ module CacheDigest
7
+ # Lets `ActionView::Digestor` find the synthetic templates `Resolver` builds.
8
+ #
9
+ # Prepended to the Digestor's singleton class rather than appending
10
+ # `Resolver` to the application's view paths, so the resolver is only ever
11
+ # consulted while digesting. Code that walks `view_paths` and expects every
12
+ # entry to be a file system resolver is unaffected.
13
+ #
14
+ # @private
15
+ module DigestorLookup
16
+ # @private
17
+ def self.install!
18
+ digestor = ActionView::Digestor.singleton_class
19
+ return if digestor.include?(self)
20
+
21
+ digestor.prepend(self)
22
+ end
23
+
24
+ private
25
+
26
+ def find_template(finder, name, prefixes, partial, keys)
27
+ Resolver.instance.find_templates(name, prefixes.first, partial, {formats: finder.formats}).first || super
28
+ end
29
+ end
30
+ end
31
+ end
@@ -42,21 +42,11 @@ module ViewComponent
42
42
  end
43
43
  alias_method :==, :eql?
44
44
 
45
- private
46
-
47
- def build_template(component, virtual_path, details)
48
- ActionView::Template.new(
49
- source_for(component),
50
- "view_component cache digest for #{component.name}",
51
- ActionView::Template.handler_for_extension(:erb),
52
- locals: [],
53
- format: Array(details[:formats]).first || :html,
54
- virtual_path: virtual_path
55
- )
56
- end
57
-
58
45
  # The synthetic source. Every section exists to change this string when
59
46
  # something the component renders from changes.
47
+ #
48
+ # @param component [Class]
49
+ # @return [String]
60
50
  def source_for(component)
61
51
  parts = []
62
52
 
@@ -88,6 +78,19 @@ module ViewComponent
88
78
  parts.join("\n")
89
79
  end
90
80
 
81
+ private
82
+
83
+ def build_template(component, virtual_path, details)
84
+ ActionView::Template.new(
85
+ component.__vc_cache_digest_source || source_for(component),
86
+ "view_component cache digest for #{component.name}",
87
+ ActionView::Template.handler_for_extension(:erb),
88
+ locals: [],
89
+ format: Array(details[:formats]).first || :html,
90
+ virtual_path: virtual_path
91
+ )
92
+ end
93
+
91
94
  # The component and any component superclasses, nearest first. Including
92
95
  # ancestors means editing `ApplicationComponent` invalidates every
93
96
  # component that inherits from it.
@@ -165,8 +168,9 @@ module ViewComponent
165
168
  end
166
169
 
167
170
  # Built once at load time rather than memoized, so no class-level state
168
- # is written after boot. The resolver is stateless: it reads from disk on
169
- # every call so it can't go stale when a component changes.
171
+ # is written after boot. Unless sources were precomputed at boot, the
172
+ # resolver reads from disk on every call so it can't go stale when a
173
+ # component changes.
170
174
  INSTANCE = new
171
175
  end
172
176
  end
@@ -21,15 +21,15 @@ module ViewComponent
21
21
  # not just its template.
22
22
  #
23
23
  # This module fixes both, reusing Rails' own `ActionView::Digestor` rather than
24
- # reimplementing static analysis. Components opt in individually by including
25
- # `ViewComponent::ExperimentallyCacheable`; until at least one component does,
26
- # every hook here short-circuits.
24
+ # reimplementing static analysis. Every discovered component is tracked
25
+ # automatically, independently of output caching.
27
26
  #
28
27
  # @private
29
28
  module CacheDigest
30
29
  extend ActiveSupport::Autoload
31
30
 
32
31
  autoload :DependencyTracking
32
+ autoload :DigestorLookup
33
33
  autoload :Resolver
34
34
 
35
35
  # Prefix for the synthetic virtual paths components are digested under.
@@ -56,8 +56,7 @@ module ViewComponent
56
56
  # Rails' escape hatch for dependencies static analysis can't see.
57
57
  EXPLICIT_DEPENDENCY = /#\s*Template Dependency:\s*(\S+)/
58
58
  class << self
59
- # Virtual paths of components that have opted into caching, mapped to
60
- # their class names.
59
+ # Virtual paths of components, mapped to their class names.
61
60
  #
62
61
  # Class *names* rather than class objects so the registry survives
63
62
  # autoloader reloads without pinning stale constants in memory.
@@ -67,11 +66,6 @@ module ViewComponent
67
66
  @registry ||= {}
68
67
  end
69
68
 
70
- # @return [Boolean] whether any component has opted in.
71
- def enabled?
72
- !registry.empty?
73
- end
74
-
75
69
  # @private
76
70
  def register(component)
77
71
  return unless component.virtual_path && component.name
@@ -108,28 +102,22 @@ module ViewComponent
108
102
  constantize_component(name)
109
103
  end
110
104
 
111
- # Scan a template's source for renders of cacheable components.
112
- #
113
- # Called for every template Rails digests, so it exits early when the
114
- # feature is unused.
105
+ # Scan a template's source for renders of components.
115
106
  #
116
107
  # @return [Array<String>] synthetic virtual paths
117
108
  def dependencies_in(template)
118
- return [] unless enabled?
119
-
120
109
  component_paths_in(template.source)
121
110
  end
122
111
 
123
112
  # Scan arbitrary source (a template or a component's Ruby file) for
124
- # renders of cacheable components.
113
+ # renders of components.
125
114
  #
126
115
  # @return [Array<String>] synthetic virtual paths
127
116
  def component_paths_in(source)
128
117
  return [] unless source.is_a?(String) && source.include?("render")
129
118
 
130
119
  source.scan(RENDER_CALL).flatten.uniq.filter_map do |constant_name|
131
- component = constantize_component(constant_name)
132
- virtual_path_for(component) if component
120
+ registered_path_for(constantize_component(constant_name))
133
121
  end
134
122
  end
135
123
 
@@ -182,17 +170,19 @@ module ViewComponent
182
170
  next unless /\A(?:::)?[A-Z]/.match?(declared)
183
171
 
184
172
  component = constantize_component(declared)
185
- [declared, virtual_path_for(component)] if component
173
+ virtual_path = registered_path_for(component)
174
+ [declared, virtual_path] if virtual_path
186
175
  end
187
176
  end
188
177
 
189
178
  # Compute the digest of a component using Rails' digest tree.
190
179
  #
191
- # @param component [Class] a component that includes `ExperimentallyCacheable`
180
+ # @param component [Class] a component class
192
181
  # @param finder [ActionView::LookupContext]
193
182
  # @param format [Symbol]
194
183
  # @return [String]
195
184
  def digest(component, finder: default_finder, format: :html)
185
+ register(component)
196
186
  virtual_path = virtual_path_for(component)
197
187
  return "" unless virtual_path
198
188
 
@@ -210,24 +200,53 @@ module ViewComponent
210
200
 
211
201
  # Wire the tracker and resolver into Action View.
212
202
  #
213
- # Called each time a component includes `ExperimentallyCacheable`. Both
214
- # steps below are individually idempotent, so no "already installed" flag
215
- # is kept. Both hooks short-circuit while the registry is empty, so
216
- # applications that never opt in are unaffected.
203
+ # Installed during engine initialization. Both steps are idempotent.
217
204
  #
218
205
  # @private
219
206
  def install!
220
207
  DependencyTracking.install!
208
+ DigestorLookup.install!
209
+ end
221
210
 
222
- ActiveSupport.on_load(:action_controller_base) do
223
- resolver = ViewComponent::CacheDigest::Resolver.instance
211
+ # Compute component digests at boot, so requests read them from memory
212
+ # instead of reading and scanning source files.
213
+ #
214
+ # Only safe when templates are cached at boot: the precomputed sources are
215
+ # never refreshed.
216
+ #
217
+ # @param components [Array<Class>]
218
+ # @return [void]
219
+ # @private
220
+ def precompute!(components)
221
+ # Register everything first: a registration expires every memoized
222
+ # digest, so registering midway would discard digests already warmed.
223
+ components.each { |component| register(component) }
224
+
225
+ components.each do |component|
226
+ component.__vc_cache_digest_source = Resolver.instance.source_for(component).freeze
227
+ end
224
228
 
225
- append_view_path(resolver) unless view_paths.include?(resolver)
229
+ # Warms the same digest cache requests read, since a lookup context's
230
+ # cache is shared by every context with the same details.
231
+ components.each do |component|
232
+ component.__vc_template_formats.each do |format|
233
+ finder = ActionView::LookupContext.new(ActionController::Base.view_paths, formats: [format])
234
+ component.cache_digest(finder: finder, format: format)
235
+ end
226
236
  end
227
237
  end
228
238
 
229
239
  private
230
240
 
241
+ # Record the actual class name rather than trying to reverse a virtual
242
+ # path, which may contain acronyms or be overridden by the component.
243
+ def registered_path_for(component)
244
+ return unless component
245
+
246
+ register(component)
247
+ virtual_path_for(component)
248
+ end
249
+
231
250
  # Drop Action View's memoized template digests, leaving its resolver
232
251
  # caches alone: no template changed, only the set of dependencies the
233
252
  # Digestor can see.
@@ -235,15 +254,14 @@ module ViewComponent
235
254
  ActionView::LookupContext::DetailsKey.digest_caches.each(&:clear)
236
255
  end
237
256
 
238
- # Resolve a constant name to a component that opted into caching.
257
+ # Resolve a constant name to any ViewComponent.
239
258
  #
240
259
  # Returns nil for anything else, including constants that don't exist.
241
260
  # Autoloading here is safe: the template is about to render this constant
242
261
  # anyway.
243
262
  def constantize_component(constant_name)
244
263
  component = constant_name.safe_constantize
245
- return unless component.is_a?(Class)
246
- return unless component.respond_to?(:__vc_cacheable?) && component.__vc_cacheable?
264
+ return unless component.is_a?(Class) && component < ViewComponent::Base
247
265
 
248
266
  component
249
267
  end
@@ -3,26 +3,13 @@
3
3
  require "view_component/cache_digest"
4
4
 
5
5
  module ViewComponent
6
- # Experimental caching support for ViewComponents.
6
+ # Caching support for ViewComponents.
7
7
  #
8
- # **This API is experimental.** It may change or be removed in a non-major
9
- # release. Please share feedback in
10
- # https://github.com/ViewComponent/view_component/issues/234.
11
- #
12
- # Including this module does two things:
13
- #
14
- # 1. Registers the component with Rails' template digest tree, so a
15
- # `<% cache %>` block is invalidated when the component's template, Ruby
16
- # class, sidecar files, or child components change. This covers blocks
17
- # wrapping the component in a view and blocks inside the component's own
18
- # template.
19
- # 2. Enables the `cache_on` macro, which caches the component's own rendered
20
- # output.
8
+ # Provides the `cache_on` macro, which caches the component's own rendered
9
+ # output. Components don't cache their output until they call `cache_on`.
21
10
  #
22
11
  # ```ruby
23
12
  # class MessageComponent < ViewComponent::Base
24
- # include ViewComponent::ExperimentallyCacheable
25
- #
26
13
  # cache_on :message
27
14
  #
28
15
  # def initialize(message:)
@@ -30,7 +17,7 @@ module ViewComponent
30
17
  # end
31
18
  # end
32
19
  # ```
33
- module ExperimentallyCacheable
20
+ module Cacheable
34
21
  extend ActiveSupport::Concern
35
22
 
36
23
  # Stands in for `nil` in the cache key. Without it, `expand_cache_key`
@@ -38,11 +25,6 @@ module ViewComponent
38
25
  # that respect would share an entry.
39
26
  NIL_CACHE_VALUE = :__vc_nil
40
27
 
41
- included do
42
- ViewComponent::CacheDigest.install!
43
- ViewComponent::CacheDigest.register(self)
44
- end
45
-
46
28
  class_methods do
47
29
  # Declare the values that identify a rendering of this component.
48
30
  #
@@ -55,8 +37,7 @@ module ViewComponent
55
37
  # ```
56
38
  #
57
39
  # Calling `cache_on` opts the component into caching its own output.
58
- # Without it, including this module only registers the component with
59
- # Rails' digest tree.
40
+ # Components that don't call it aren't cached.
60
41
  #
61
42
  # Pass `if:` or `unless:` to cache only some renders. Both accept a method
62
43
  # name or a proc evaluated on the component:
@@ -119,11 +100,6 @@ module ViewComponent
119
100
  superclass.__vc_cache_unless if superclass.respond_to?(:__vc_cache_unless)
120
101
  end
121
102
 
122
- # @private
123
- def __vc_cacheable?
124
- true
125
- end
126
-
127
103
  # Whether this component caches its own rendered output.
128
104
  #
129
105
  # @return [Boolean]
@@ -131,67 +107,11 @@ module ViewComponent
131
107
  __vc_cache_on.any?
132
108
  end
133
109
 
134
- # A digest of everything this component renders from: its template, its
135
- # Ruby class, its sidecar files, its superclasses, and the components and
136
- # partials it renders.
137
- #
138
- # Computed with Rails' own `ActionView::Digestor`, so it's the same digest
139
- # used to invalidate `<% cache %>` blocks.
110
+ # `__vc_caches_output?`, stored when the component compiles so rendering
111
+ # reads a flag instead of computing it.
140
112
  #
141
- # Usable outside a request, where no view context exists:
142
- #
143
- # ```ruby
144
- # MessageComponent.cache_digest
145
- # ```
146
- #
147
- # @param finder [ActionView::LookupContext] Defaults to a lookup context
148
- # built from `ActionController::Base.view_paths`.
149
- # @param format [Symbol]
150
- # @return [String]
151
- def cache_digest(finder: nil, format: :html)
152
- ViewComponent::CacheDigest.digest(
153
- self,
154
- finder: finder || ViewComponent::CacheDigest.default_finder,
155
- format: format
156
- )
157
- end
158
-
159
113
  # @private
160
- def inherited(child)
161
- super
162
- ViewComponent::CacheDigest.register(child)
163
- end
164
- end
165
-
166
- # Renders the component, reading from and writing to the Rails cache when
167
- # `cache_on` has been declared.
168
- #
169
- # @private
170
- def render_in(view_context, **, &block)
171
- return super unless self.class.__vc_caches_output?
172
-
173
- # Content provided by the caller isn't part of the cache key, so caching
174
- # it would serve one caller's content to another. Raised whether or not
175
- # caching is currently enabled, so the conflict surfaces in development
176
- # and test rather than only in production.
177
- if block || __vc_content_set_by_with_content_defined? || __vc_slots_set_by_caller?
178
- raise ContentPassedToCachedComponentError.new(self.class.name)
179
- end
180
-
181
- return super unless __vc_cache_enabled?(view_context)
182
-
183
- store = Rails.cache
184
- key = cache_key(view_context)
185
-
186
- if (cached = store.read(key))
187
- # Safe to mark as HTML-safe: the cached string was produced by this same
188
- # rendering pipeline, which escapes output before it's written.
189
- return cached.html_safe
190
- end
191
-
192
- super.tap do |output|
193
- store.write(key, output.to_s)
194
- end
114
+ attr_accessor :__vc_cache_output
195
115
  end
196
116
 
197
117
  # The cache key for this rendering of the component.
@@ -226,34 +146,39 @@ module ViewComponent
226
146
  )
227
147
  end
228
148
 
229
- # The digest Rails mixes into the key of a `<% cache %>` block.
230
- #
231
- # `ActionView::Helpers::CacheHelper` digests the virtual path of whichever
232
- # template is rendering. Inside a component that path is the component's
233
- # own, which resolves to nothing: component templates aren't in the view
234
- # paths. The Digestor returns an empty digest, `CacheHelper` falls back to
235
- # the bare virtual path, and the fragment never invalidates.
236
- #
237
- # Substituting the digest the component already computes makes a `cache`
238
- # block in a component template behave like one in a view. Everything else
239
- # the component renders — a partial, say — keeps Rails' behavior.
240
- #
241
- # @private
242
- def digest_path_from_template(template)
243
- component_path = self.class.virtual_path
244
- return super unless component_path && template.virtual_path == component_path
245
-
246
- digest = self.class.cache_digest(
247
- finder: lookup_context,
248
- format: template.format || __vc_cache_format(lookup_context)
249
- )
149
+ private
150
+
151
+ # Renders through the Rails cache. Called by `Base#render_in` for
152
+ # components that declare `cache_on`.
153
+ def __vc_render_cached(view_context, content_block)
154
+ # Content provided by the caller isn't part of the cache key, so caching
155
+ # it would serve one caller's content to another. Raised whether or not
156
+ # caching is currently enabled, so the conflict surfaces in development
157
+ # and test rather than only in production.
158
+ if content_block || __vc_content_set_by_with_content_defined? || __vc_slots_set_by_caller?
159
+ raise ContentPassedToCachedComponentError.new(self.class.name)
160
+ end
161
+
162
+ return __vc_render_uncached(view_context) unless __vc_cache_enabled?(view_context)
163
+
164
+ store = Rails.cache
165
+ key = cache_key(view_context)
166
+
167
+ if (cached = store.read(key))
168
+ # Safe to mark as HTML-safe: the cached string was produced by this same
169
+ # rendering pipeline, which escapes output before it's written.
170
+ return cached.html_safe
171
+ end
250
172
 
251
- # An empty digest means the component couldn't be resolved. Falling back
252
- # to the bare virtual path matches what `CacheHelper` does with one.
253
- digest.present? ? "#{component_path}:#{digest}" : component_path
173
+ __vc_render_uncached(view_context).tap do |output|
174
+ store.write(key, output.to_s)
175
+ end
254
176
  end
255
177
 
256
- private
178
+ def __vc_render_uncached(view_context)
179
+ @__vc_cache_bypassed = true
180
+ render_in(view_context)
181
+ end
257
182
 
258
183
  # Slots set by the caller via `with_*`. Checked before rendering, so slots
259
184
  # a component fills in for itself with a `default_*` method — which resolve
@@ -50,6 +50,8 @@ module ViewComponent
50
50
 
51
51
  define_render_template_for
52
52
 
53
+ @component.__vc_cache_output = @component.__vc_caches_output?
54
+
53
55
  @component.__vc_register_default_slots
54
56
  @component.__vc_build_i18n_backend
55
57
 
@@ -76,6 +78,14 @@ module ViewComponent
76
78
  filtered_templates
77
79
  end
78
80
 
81
+ # Formats the component has templates for. Inline templates and `#call`
82
+ # methods have no format of their own and render as HTML.
83
+ #
84
+ # @return [Array<Symbol>]
85
+ def template_formats
86
+ gather_templates.map { |template| template.format || :html }.uniq
87
+ end
88
+
79
89
  private
80
90
 
81
91
  attr_reader :templates
@@ -3,11 +3,16 @@
3
3
  require "rails"
4
4
  require "view_component/config"
5
5
  require "view_component/deprecation"
6
+ require "view_component/cache_digest"
6
7
 
7
8
  module ViewComponent
8
9
  class Engine < Rails::Engine # :nodoc:
9
10
  config.view_component = ViewComponent::Config.current
10
11
 
12
+ initializer "view_component.cache_digest" do
13
+ ViewComponent::CacheDigest.install!
14
+ end
15
+
11
16
  initializer "view_component.set_configs" do |app|
12
17
  options = app.config.view_component
13
18
 
@@ -89,10 +94,11 @@ module ViewComponent
89
94
  ActiveSupport.on_load(:after_initialize) do
90
95
  if Rails.application.config.eager_load
91
96
  ActiveSupport::Notifications.instrument("compile.view_component") do
92
- ViewComponent::Base.descendants.each do |component|
93
- next if component.anonymous?
97
+ components = ViewComponent::Base.descendants.reject(&:anonymous?)
98
+ components.each(&:__vc_compile)
94
99
 
95
- component.__vc_compile
100
+ if ActionView::Base.cache_template_loading
101
+ ViewComponent::CacheDigest.precompute!(components.select(&:__vc_compiled?))
96
102
  end
97
103
  end
98
104
  end
@@ -3,7 +3,7 @@
3
3
  module ViewComponent
4
4
  module VERSION
5
5
  MAJOR = 4
6
- MINOR = 17
6
+ MINOR = 18
7
7
  PATCH = 0
8
8
  PRE = nil
9
9
 
@@ -9,11 +9,11 @@ module ViewComponent
9
9
 
10
10
  autoload :Base
11
11
  autoload :CacheDigest
12
+ autoload :Cacheable
12
13
  autoload :Compiler
13
14
  autoload :CompileCache
14
15
  autoload :Config
15
16
  autoload :Deprecation
16
- autoload :ExperimentallyCacheable
17
17
  autoload :InlineTemplate
18
18
  autoload :Instrumentation
19
19
  autoload :Preview
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: view_component
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.17.0
4
+ version: 4.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ViewComponent Team
@@ -93,7 +93,9 @@ files:
93
93
  - lib/view_component/base.rb
94
94
  - lib/view_component/cache_digest.rb
95
95
  - lib/view_component/cache_digest/dependency_tracking.rb
96
+ - lib/view_component/cache_digest/digestor_lookup.rb
96
97
  - lib/view_component/cache_digest/resolver.rb
98
+ - lib/view_component/cacheable.rb
97
99
  - lib/view_component/collection.rb
98
100
  - lib/view_component/compile_cache.rb
99
101
  - lib/view_component/compiler.rb
@@ -102,7 +104,6 @@ files:
102
104
  - lib/view_component/deprecation.rb
103
105
  - lib/view_component/engine.rb
104
106
  - lib/view_component/errors.rb
105
- - lib/view_component/experimentally_cacheable.rb
106
107
  - lib/view_component/inline_template.rb
107
108
  - lib/view_component/instrumentation.rb
108
109
  - lib/view_component/preview.rb