view_component 4.20.0 → 4.21.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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/docs/CHANGELOG.md +94 -0
  3. data/lib/generators/view_component/abstract_generator.rb +24 -4
  4. data/lib/generators/view_component/component/component_generator.rb +8 -6
  5. data/lib/generators/view_component/component/templates/component.rb.tt +1 -1
  6. data/lib/generators/view_component/erb/erb_generator.rb +2 -2
  7. data/lib/generators/view_component/haml/haml_generator.rb +1 -1
  8. data/lib/generators/view_component/locale/locale_generator.rb +2 -6
  9. data/lib/generators/view_component/preview/preview_generator.rb +9 -6
  10. data/lib/generators/view_component/preview/templates/component_preview.rb.tt +2 -2
  11. data/lib/generators/view_component/rspec/rspec_generator.rb +1 -1
  12. data/lib/generators/view_component/rspec/templates/component_spec.rb.tt +1 -1
  13. data/lib/generators/view_component/slim/slim_generator.rb +1 -1
  14. data/lib/generators/view_component/stimulus/stimulus_generator.rb +3 -7
  15. data/lib/generators/view_component/test_unit/templates/component_test.rb.tt +2 -2
  16. data/lib/generators/view_component/test_unit/test_unit_generator.rb +8 -7
  17. data/lib/view_component/base.rb +106 -98
  18. data/lib/view_component/cache_digest/dependency_tracking.rb +6 -6
  19. data/lib/view_component/cache_digest/digestor_lookup.rb +3 -1
  20. data/lib/view_component/cache_digest/resolver.rb +59 -38
  21. data/lib/view_component/cache_digest.rb +161 -53
  22. data/lib/view_component/cacheable.rb +16 -18
  23. data/lib/view_component/collection.rb +4 -9
  24. data/lib/view_component/compile_cache.rb +44 -5
  25. data/lib/view_component/compiler.rb +50 -2
  26. data/lib/view_component/config.rb +114 -8
  27. data/lib/view_component/engine.rb +1 -2
  28. data/lib/view_component/errors.rb +16 -0
  29. data/lib/view_component/slot.rb +12 -0
  30. data/lib/view_component/slotable.rb +28 -4
  31. data/lib/view_component/template.rb +16 -3
  32. data/lib/view_component/test_helpers.rb +38 -16
  33. data/lib/view_component/translatable.rb +34 -0
  34. data/lib/view_component/version.rb +1 -1
  35. metadata +1 -1
@@ -32,6 +32,9 @@ end
32
32
 
33
33
  module ViewComponent
34
34
  class Base
35
+ EMPTY_OUTPUT = "".html_safe.freeze
36
+ private_constant :EMPTY_OUTPUT
37
+
35
38
  class << self
36
39
  # A digest of the component's source, ancestors, and rendered dependencies.
37
40
  #
@@ -42,7 +45,7 @@ module ViewComponent
42
45
  def cache_digest(finder: nil, format: :html)
43
46
  ViewComponent::CacheDigest.digest(
44
47
  self,
45
- finder: finder || ViewComponent::CacheDigest.default_finder,
48
+ finder: finder,
46
49
  format: format
47
50
  )
48
51
  end
@@ -121,86 +124,14 @@ module ViewComponent
121
124
  # @return [String]
122
125
  def render_in(view_context, **, &block)
123
126
  self.class.__vc_compile(raise_errors: true) unless self.class.__vc_compiled?
124
-
125
- if self.class.__vc_cache_output && !@__vc_cache_bypassed
126
- return __vc_render_cached(view_context, block)
127
- end
128
-
129
127
  __vc_check_reused_instance!
130
- __vc_reset_render_state!
131
-
132
- @view_context = view_context
133
- @old_virtual_path = view_context.instance_variable_get(:@virtual_path)
134
- self.__vc_original_view_context = view_context
135
-
136
- @output_buffer = view_context.output_buffer
137
-
138
- @lookup_context = view_context.lookup_context
139
-
140
- # For content_for
141
- @view_flow = view_context.view_flow
142
-
143
- # For i18n
144
- @virtual_path ||= virtual_path
145
-
146
- # Describes the inferred request constraints (locales, formats, variants)
147
- @__vc_requested_details = @lookup_context.vc_requested_details
148
-
149
- # For caching, such as #cache_if
150
- @current_template = nil unless defined?(@current_template)
151
- old_current_template = @current_template
152
128
 
153
- if block && defined?(@__vc_content_set_by_with_content)
154
- raise DuplicateContentError.new(self.class.name)
155
- end
156
-
157
- @__vc_content_evaluated = false
158
- remove_instance_variable(:@__vc_content) if defined?(@__vc_content)
159
- @__vc_render_in_block = block
160
- @view_context.instance_variable_set(:@virtual_path, virtual_path)
161
-
162
- before_render
163
-
164
- if render?
165
- value = nil
166
-
167
- @output_buffer.with_buffer do
168
- inner_rendered_template = nil
169
- around_rendered_template = around_render do
170
- inner_rendered_template = render_template_for(@__vc_requested_details).to_s
171
- end
172
-
173
- # If `around_render` returned the same object the block yielded, the inner
174
- # template's escaping is authoritative and we can trust the result. If the
175
- # user replaced/wrapped the value, re-check HTML safety to prevent
176
- # bypassing the escaping applied to normal `#call` return values
177
- # (GHSA-97jw-64cj-jc58).
178
- rendered_template = if around_rendered_template.equal?(inner_rendered_template)
179
- around_rendered_template
180
- else
181
- __vc_safe_around_render_output(around_rendered_template)
182
- end
183
-
184
- # Avoid allocating new string when output_preamble and output_postamble are blank
185
- value = if output_preamble.blank? && output_postamble.blank?
186
- rendered_template
187
- else
188
- __vc_safe_output_preamble + rendered_template + __vc_safe_output_postamble
189
- end
190
- end
191
-
192
- if ActionView::Base.annotate_rendered_view_with_filenames && current_template.inline_call? && request&.format == :html
193
- identifier = defined?(Rails.root) ? self.class.identifier.sub("#{Rails.root}/", "") : self.class.identifier
194
- value = "<!-- BEGIN #{identifier} -->".html_safe + value + "<!-- END #{identifier} -->".html_safe
195
- end
196
-
197
- value
129
+ if self.class.__vc_cache_output
130
+ __vc_render_cached(view_context, block)
198
131
  else
199
- "".html_safe
132
+ __vc_render_uncached(view_context, &block)
200
133
  end
201
134
  ensure
202
- view_context.instance_variable_set(:@virtual_path, @old_virtual_path)
203
- @current_template = old_current_template
204
135
  @__vc_rendered = true
205
136
  end
206
137
 
@@ -232,14 +163,21 @@ module ViewComponent
232
163
  # When rendering the parent inside an .erb template, use `#render_parent` instead.
233
164
  def render_parent_to_string
234
165
  @__vc_parent_render_level ||= 0 # ensure a good starting value
166
+ old_current_template = @current_template
235
167
 
236
168
  begin
237
169
  target_render = self.class.instance_variable_get(:@__vc_ancestor_calls)[@__vc_parent_render_level]
238
170
  @__vc_parent_render_level += 1
239
171
 
172
+ unless target_render.owner.__vc_compiled?
173
+ target_render.owner.__vc_compile(raise_errors: true)
174
+ target_render = target_render.owner.instance_method(:render_template_for)
175
+ end
176
+
240
177
  target_render.bind_call(self, @__vc_requested_details)
241
178
  ensure
242
179
  @__vc_parent_render_level -= 1
180
+ @current_template = old_current_template
243
181
  end
244
182
  end
245
183
 
@@ -247,14 +185,14 @@ module ViewComponent
247
185
  #
248
186
  # @return [String]
249
187
  def output_preamble
250
- @@default_output_preamble ||= "".html_safe
188
+ EMPTY_OUTPUT
251
189
  end
252
190
 
253
191
  # Optional content to be returned after the rendered template.
254
192
  #
255
193
  # @return [String]
256
194
  def output_postamble
257
- @@default_output_postamble ||= "".html_safe
195
+ EMPTY_OUTPUT
258
196
  end
259
197
 
260
198
  # Called before rendering the component. Override to perform operations that
@@ -289,7 +227,7 @@ module ViewComponent
289
227
  #
290
228
  # @private
291
229
  def render(options = {}, args = {}, &block)
292
- if options.respond_to?(:set_original_view_context)
230
+ if options.is_a?(ViewComponent::Base) || options.respond_to?(:set_original_view_context)
293
231
  options.set_original_view_context(__vc_original_view_context)
294
232
 
295
233
  # We assume options is a component, so there's no need to evaluate the
@@ -448,20 +386,99 @@ module ViewComponent
448
386
  # @private
449
387
  def digest_path_from_template(template)
450
388
  component_path = self.class.virtual_path
451
- return super unless component_path && template.virtual_path == component_path
389
+ return super unless component_path && template.is_a?(ViewComponent::Template)
452
390
 
453
391
  digest = self.class.cache_digest(
454
392
  finder: lookup_context,
455
393
  format: template.format || lookup_context.formats.first || :html
456
394
  )
457
395
 
458
- digest.present? ? "#{component_path}:#{digest}" : component_path
396
+ digest.present? ? "#{template.virtual_path}:#{digest}" : template.virtual_path
459
397
  end
460
398
 
461
399
  private
462
400
 
463
401
  attr_reader :view_context
464
402
 
403
+ def __vc_render_uncached(view_context, &block)
404
+ old_virtual_path = view_context.instance_variable_get(:@virtual_path)
405
+ __vc_reset_render_state!
406
+
407
+ @view_context = view_context
408
+ @old_virtual_path = old_virtual_path
409
+ self.__vc_original_view_context = view_context
410
+
411
+ @output_buffer = view_context.output_buffer
412
+
413
+ @lookup_context = view_context.lookup_context
414
+
415
+ # For content_for
416
+ @view_flow = view_context.view_flow
417
+
418
+ # For i18n
419
+ component_virtual_path = virtual_path
420
+ @virtual_path ||= component_virtual_path
421
+
422
+ # Describes the inferred request constraints (locales, formats, variants)
423
+ @__vc_requested_details = @lookup_context.vc_requested_details
424
+
425
+ # For caching, such as #cache_if
426
+ @current_template = nil unless defined?(@current_template)
427
+ old_current_template = @current_template
428
+
429
+ if block && defined?(@__vc_content_set_by_with_content)
430
+ raise DuplicateContentError.new(self.class.name)
431
+ end
432
+
433
+ @__vc_content_evaluated = false
434
+ remove_instance_variable(:@__vc_content) if defined?(@__vc_content)
435
+ @__vc_render_in_block = block
436
+ @view_context.instance_variable_set(:@virtual_path, component_virtual_path)
437
+
438
+ before_render
439
+
440
+ if render?
441
+ value = nil
442
+
443
+ @output_buffer.with_buffer do
444
+ inner_rendered_template = nil
445
+ around_rendered_template = around_render do
446
+ inner_rendered_template = render_template_for(@__vc_requested_details).to_s
447
+ end
448
+
449
+ # If `around_render` returned the same object the block yielded, the inner
450
+ # template's escaping is authoritative and we can trust the result. If the
451
+ # user replaced/wrapped the value, re-check HTML safety to prevent
452
+ # bypassing the escaping applied to normal `#call` return values
453
+ # (GHSA-97jw-64cj-jc58).
454
+ rendered_template = if around_rendered_template.equal?(inner_rendered_template)
455
+ around_rendered_template
456
+ else
457
+ __vc_safe_around_render_output(around_rendered_template)
458
+ end
459
+
460
+ # Avoid allocating new string when output_preamble and output_postamble are blank
461
+ value = if output_preamble.blank? && output_postamble.blank?
462
+ rendered_template
463
+ else
464
+ __vc_safe_output_preamble + rendered_template + __vc_safe_output_postamble
465
+ end
466
+ end
467
+
468
+ if ActionView::Base.annotate_rendered_view_with_filenames && current_template.inline_call? && request&.format == :html
469
+ identifier = defined?(Rails.root) ? self.class.identifier.sub("#{Rails.root}/", "") : self.class.identifier
470
+ value = "<!-- BEGIN #{identifier} -->".html_safe + value + "<!-- END #{identifier} -->".html_safe
471
+ end
472
+
473
+ value
474
+ else
475
+ "".html_safe
476
+ end
477
+ ensure
478
+ view_context.instance_variable_set(:@virtual_path, old_virtual_path)
479
+ @current_template = old_current_template
480
+ end
481
+
465
482
  def __vc_render_in_block_provided?
466
483
  defined?(@view_context) && @view_context && @__vc_render_in_block
467
484
  end
@@ -471,15 +488,12 @@ module ViewComponent
471
488
  end
472
489
 
473
490
  def __vc_maybe_escape_html(text)
491
+ return text if text.html_safe?
474
492
  return text if @current_template && !@current_template.html?
475
493
  return text if text.blank?
476
494
 
477
- if text.html_safe?
478
- text
479
- else
480
- yield
481
- html_escape(text)
482
- end
495
+ yield
496
+ html_escape(text)
483
497
  end
484
498
 
485
499
  def __vc_safe_output_preamble
@@ -508,16 +522,10 @@ module ViewComponent
508
522
  # slot setters or `with_content`); reuse of an instance that has slot or
509
523
  # `with_content` state is guarded against separately by
510
524
  # `__vc_check_reused_instance!`.
511
- RENDER_STATE_IVARS = %i[
512
- @__vc_controller
513
- @__vc_helpers
514
- @__vc_request
515
- ].freeze
516
-
517
525
  def __vc_reset_render_state!
518
- RENDER_STATE_IVARS.each do |ivar|
519
- remove_instance_variable(ivar) if instance_variable_defined?(ivar)
520
- end
526
+ remove_instance_variable(:@__vc_controller) if defined?(@__vc_controller)
527
+ remove_instance_variable(:@__vc_helpers) if defined?(@__vc_helpers)
528
+ remove_instance_variable(:@__vc_request) if defined?(@__vc_request)
521
529
  end
522
530
 
523
531
  # Raises when a component instance is rendered more than once.
@@ -733,7 +741,7 @@ module ViewComponent
733
741
 
734
742
  # @private
735
743
  def __vc_compiled?
736
- __vc_compiler.compiled?
744
+ CompileCache.compiled?(self)
737
745
  end
738
746
 
739
747
  # Hook called by the compiler after a component is compiled.
@@ -915,7 +923,7 @@ module ViewComponent
915
923
  end
916
924
 
917
925
  def __vc_files_ending_with(extensions, pattern)
918
- paths = Dir[pattern]
926
+ paths = CompileCache.sidecar_files_matching(pattern)
919
927
  extensions.flat_map { |extension| paths.select { |path| path.end_with?(extension) } }
920
928
  end
921
929
 
@@ -26,15 +26,15 @@ module ViewComponent
26
26
  dependencies = dependencies - [declared] + [virtual_path]
27
27
  end
28
28
 
29
- # Swap the partials Action View wrongly infers from component renders,
30
- # such as `news/new` from `render ChildComponent.new`, for the
31
- # components themselves.
32
- return dependencies unless CacheDigest::CONSTANT_RENDER.match?(source)
29
+ # Synthetic templates already declare every dependency found in their sources.
30
+ return dependencies if name.start_with?("#{CacheDigest::VIRTUAL_PATH_PREFIX}/")
31
+ return dependencies unless CacheDigest.template_dependencies?(source, template.handler)
33
32
 
34
33
  component_paths, inferred_partials =
35
- CacheDigest.component_renders_in(CacheDigest.compiled_source(template), name)
34
+ CacheDigest.template_renders_in(CacheDigest.compiled_source(template), name, view_paths: view_paths)
35
+ declared = source.scan(CacheDigest::EXPLICIT_DEPENDENCY).flatten
36
36
 
37
- (dependencies - inferred_partials + component_paths).uniq
37
+ (dependencies - (inferred_partials - declared) + component_paths).uniq
38
38
  end
39
39
 
40
40
  # @private
@@ -24,7 +24,9 @@ module ViewComponent
24
24
  private
25
25
 
26
26
  def find_template(finder, name, prefixes, partial, keys)
27
- Resolver.instance.find_templates(name, prefixes.first, partial, {formats: finder.formats}).first || super
27
+ Resolver.instance.find_templates(
28
+ name, prefixes.first, partial, {formats: finder.formats}, view_paths: finder.view_paths
29
+ ).first || super
28
30
  end
29
31
  end
30
32
  end
@@ -18,6 +18,10 @@ module ViewComponent
18
18
  # Extensions whose contents are hashed into the synthetic source.
19
19
  SIDECAR_EXTENSIONS = %w[yml yaml].freeze
20
20
 
21
+ RELATIVE_PARTIAL_PREFIX = "view_component/relative_partial/"
22
+ RELATIVE_DEPENDENCY = /<%# Relative Template Dependency: (\S+) %>/
23
+ private_constant :RELATIVE_PARTIAL_PREFIX, :RELATIVE_DEPENDENCY
24
+
21
25
  # Where ViewComponent's own modules, such as the ones it includes into
22
26
  # every component, are defined.
23
27
  VIEW_COMPONENT_LIB = "#{::File.expand_path("..", __dir__)}/".freeze
@@ -28,12 +32,12 @@ module ViewComponent
28
32
  end
29
33
  end
30
34
 
31
- def find_templates(name, prefix, partial, details, locals = [])
35
+ def find_templates(name, prefix, partial, details, locals = [], view_paths: ActionController::Base.view_paths)
32
36
  virtual_path = [prefix.presence, name].compact.join("/")
33
37
  component = CacheDigest.component_for(virtual_path)
34
38
  return [] unless component
35
39
 
36
- [build_template(component, virtual_path, details)]
40
+ [build_template(component, virtual_path, details, view_paths)]
37
41
  end
38
42
 
39
43
  def to_s
@@ -74,7 +78,11 @@ module ViewComponent
74
78
  # the template sources, where they'd also infer partials from component
75
79
  # renders.
76
80
  dependencies(component, templates).each do |dependency|
77
- parts << "<%# Template Dependency: #{dependency} %>"
81
+ parts << if dependency.start_with?(RELATIVE_PARTIAL_PREFIX)
82
+ "<%# Relative Template Dependency: #{dependency.delete_prefix(RELATIVE_PARTIAL_PREFIX)} %>"
83
+ else
84
+ "<%# Template Dependency: #{dependency} %>"
85
+ end
78
86
  end
79
87
 
80
88
  # Content hashes of the templates, including inline ones.
@@ -87,9 +95,10 @@ module ViewComponent
87
95
 
88
96
  private
89
97
 
90
- def build_template(component, virtual_path, details)
98
+ def build_template(component, virtual_path, details, view_paths)
99
+ source = component.__vc_cache_digest_source || source_for(component)
91
100
  ActionView::Template.new(
92
- component.__vc_cache_digest_source || source_for(component),
101
+ resolve_relative_dependencies(source, view_paths),
93
102
  "view_component cache digest for #{component.name}",
94
103
  ActionView::Template.handler_for_extension(:erb),
95
104
  locals: [],
@@ -98,15 +107,32 @@ module ViewComponent
98
107
  )
99
108
  end
100
109
 
110
+ # A relative render uses the calling controller's prefixes. Track all
111
+ # matching partials so precomputed sources work for every controller.
112
+ def resolve_relative_dependencies(source, view_paths)
113
+ return source unless source.include?("Relative Template Dependency:")
114
+
115
+ paths = view_paths.flat_map(&:all_template_paths).uniq
116
+ source.gsub(RELATIVE_DEPENDENCY) do
117
+ name = Regexp.last_match(1)
118
+ matches = paths.filter_map do |path|
119
+ path.to_s if path.partial? && ::File.fnmatch?(name.delete_prefix("_"), path.name)
120
+ end.sort
121
+ matches = [name] if matches.empty?
122
+ matches.map { |path| "<%# Template Dependency: #{path} %>" }.join("\n")
123
+ end
124
+ end
125
+
101
126
  # The component and any component superclasses, nearest first. Including
102
127
  # ancestors means editing `ApplicationComponent` invalidates every
103
128
  # component that inherits from it.
104
129
  def component_ancestors(component)
105
- component.ancestors.select do |ancestor|
106
- ancestor.is_a?(Class) &&
107
- ancestor <= ViewComponent::Base &&
108
- ancestor != ViewComponent::Base
130
+ ancestors = []
131
+ until component == ViewComponent::Base
132
+ ancestors << component
133
+ component = component.superclass
109
134
  end
135
+ ancestors
110
136
  end
111
137
 
112
138
  # Modules the component and its component superclasses include, such as
@@ -119,7 +145,11 @@ module ViewComponent
119
145
  component.ancestors.take_while { |ancestor| ancestor != ViewComponent::Base }.filter_map { |ancestor|
120
146
  next if ancestor.is_a?(Class) || ancestor.name.nil?
121
147
 
122
- path, _line = Object.const_source_location(ancestor.name)
148
+ begin
149
+ path, _line = Object.const_source_location(ancestor.name)
150
+ rescue NameError
151
+ next
152
+ end
123
153
  path unless path.nil? || path.start_with?(VIEW_COMPONENT_LIB)
124
154
  }.uniq
125
155
  end
@@ -134,26 +164,20 @@ module ViewComponent
134
164
  .compact.uniq.select { |path| ::File.exist?(path) }
135
165
  end
136
166
 
137
- def template_files(component)
138
- component_ancestors(component)
139
- .flat_map { |ancestor| ancestor.__vc_sidecar_files(ActionView::Template::Handlers.extensions) }
140
- .uniq
141
- .select { |path| ::File.exist?(path) }
142
- end
143
-
144
167
  # Sidecar template files plus inline templates, which live in the Ruby
145
168
  # file and so are invisible to Action View's trackers.
146
169
  #
147
- # @return [Array<Array(String, String)>] pairs of source and extension
170
+ # @return [Array<Array(String, String, Class)>] source, extension, and defining component
148
171
  def template_sources(component)
149
- sources = template_files(component).map { |path| [::File.read(path), ::File.extname(path).delete_prefix(".")] }
150
-
151
- component_ancestors(component).each do |ancestor|
172
+ component_ancestors(component).flat_map do |ancestor|
173
+ sources = ancestor.__vc_sidecar_files(ActionView::Template::Handlers.extensions).filter_map do |path|
174
+ [CompileCache.read_file(path), ::File.extname(path).delete_prefix("."), ancestor] if ::File.exist?(path)
175
+ end
152
176
  inline_template = ancestor.__vc_inline_template
153
- sources << [inline_template.source, inline_template.language.to_s] if inline_template
154
- end
177
+ sources << [inline_template.source, inline_template.language.to_s, ancestor] if inline_template
155
178
 
156
- sources.uniq
179
+ sources
180
+ end.uniq
157
181
  end
158
182
 
159
183
  def explicit_dependencies(sources)
@@ -173,42 +197,39 @@ module ViewComponent
173
197
  # Everything but the component and its superclasses, which a component
174
198
  # references in its own source but are digested as part of it already.
175
199
  def dependencies(component, templates)
176
- virtual_path = CacheDigest.virtual_path_for(component)
200
+ virtual_path = "#{RELATIVE_PARTIAL_PREFIX}template"
177
201
  ruby = ruby_sources(component)
178
202
  own_paths = component_ancestors(component).filter_map { |ancestor| CacheDigest.virtual_path_for(ancestor) }
179
203
 
180
204
  dependencies = explicit_dependencies(ruby + templates.map(&:first)) |
181
205
  ruby.flat_map { |source| ruby_dependencies(source, virtual_path) } |
182
206
  CacheDigest.slot_renders_of(component) |
183
- templates.flat_map { |source, extension| template_dependencies(component, source, extension, virtual_path) }
207
+ templates.flat_map { |source, extension, owner| template_dependencies(owner, source, extension, virtual_path) }
184
208
 
185
209
  dependencies - own_paths
186
210
  end
187
211
 
188
212
  # Components and partials rendered from Ruby, such as a `#call` method.
189
- # Partials are narrowed to paths that appear in the source, discarding
190
- # the speculative `things/thing` Action View infers from dynamic renders
191
- # like `render @thing`, which would resolve to nothing.
192
213
  def ruby_dependencies(source, virtual_path)
193
- components, partials = CacheDigest.renders_in(source, virtual_path)
194
-
195
- components | partials.select { |path|
196
- source.include?(path) || source.include?(path.sub(%r{[^/]+\z}, '_\0'))
197
- }
214
+ CacheDigest.renders_in(source, virtual_path, view_paths: ActionController::Base.view_paths).flatten
198
215
  end
199
216
 
200
217
  def template_dependencies(component, source, extension, virtual_path)
201
- return [] unless source.include?("render")
218
+ handler = ActionView::Template.handler_for_extension(extension)
219
+ return [] unless CacheDigest.template_dependencies?(source, handler)
202
220
 
203
221
  template = ActionView::Template.new(
204
222
  source,
205
223
  "view_component cache digest for #{component.name}",
206
- ActionView::Template.handler_for_extension(extension),
224
+ handler,
207
225
  locals: [],
208
226
  format: nil,
209
227
  virtual_path: virtual_path
210
228
  )
211
- CacheDigest.renders_in(CacheDigest.compiled_source(template), virtual_path).flatten
229
+ CacheDigest.renders_in(
230
+ CacheDigest.compiled_source(template), virtual_path,
231
+ view_paths: ActionController::Base.view_paths, nesting: [component]
232
+ ).flatten
212
233
  end
213
234
 
214
235
  # Components loaded from encrypted files, as some commercial gems ship
@@ -216,7 +237,7 @@ module ViewComponent
216
237
  # hashed by `file_digest`.
217
238
  def ruby_sources(component)
218
239
  ruby_files(component).filter_map { |path|
219
- source = ::File.read(path)
240
+ source = CompileCache.read_file(path)
220
241
  source if source.valid_encoding?
221
242
  }
222
243
  end