Skip to content

Resolve declared component dependencies that never opted in - #2711

Draft
erikaxel wants to merge 1 commit into
ViewComponent:mainfrom
erikaxel:resolve-explicit-component-dependencies-without-opt-in
Draft

Resolve declared component dependencies that never opted in#2711
erikaxel wants to merge 1 commit into
ViewComponent:mainfrom
erikaxel:resolve-explicit-component-dependencies-without-opt-in

Conversation

@erikaxel

@erikaxel erikaxel commented Sep 3, 2026

Copy link
Copy Markdown

Problem

# Template Dependency: SomeComponent is the escape hatch for component renders static analysis can't see — a class held in a local variable, or reached through a helper module. The caching guide documents naming components this way.

CacheDigest.explicit_component_dependencies translates the declared class name into the synthetic path the component is digested under, but it resolves the name through constantize_component, which only accepts classes that already include ViewComponent::ExperimentallyCacheable. For anything else the raw constant name stays in the dependency list and is handed to the Digestor as a template path. Nothing resolves there, so the declaration silently does nothing and the only signal is a confusing log line naming a class where a path is expected:

Couldn't find template for digesting: ErbComponent

That defeats the point of the hatch: the dependencies you need it for are exactly the ones whose target you may not control or want to modify.

Fix

Resolve any ViewComponent::Base descendant in explicit_component_dependencies, and register it so Resolver can synthesize a template for it — being named by a declaration is itself the opt-in. component_for resolves through the same path, since the registry can now hold components that never included the module.

The declared component's template, Ruby class, sidecar files, and superclasses are digested, so editing any of them busts caches that depend on it. Rendering the declared component is still uncached; only components that include the module and declare cache_on cache their own output.

Constants that aren't components (and names that aren't constants at all) are left in the dependency list untouched, as before.

Tests

  • test_declared_components_resolve_without_opting_into_caching — a declaration naming a component with no __vc_cacheable? now resolves to its synthetic path.
  • test_cache_digest_changes_when_a_component_declared_without_opting_in_changes — end to end through the Digestor: editing ErbComponent's template changes the digest of a component that declares it. This fails on main.
  • test_declared_names_that_are_not_components_are_left_alone — replaces the old assertion that a non-opted-in component resolved to nothing, and covers a plain class and an unknown constant instead.

Full suite passes on the default Gemfile and on gemfiles/rails_7.1.gemfile (the two failures there are the pre-existing RenderingAllocationsTest entries with no baseline for the Ruby version I ran locally).

Not covered here

The guide also suggests # Template Dependency: for modules included into a component. A module still resolves to nothing, since it has no virtual_path and no template or sidecar files to hash. That needs a separate mechanism and seemed out of scope for this fix.

🤖 Generated with Claude Code

`# Template Dependency: SomeComponent` is the escape hatch for component
renders static analysis can't see -- a class held in a local variable, or
reached through a helper module. `explicit_component_dependencies`
translated the declared class name into the synthetic path the component
is digested under, but only for classes `constantize_component` accepts,
which means only ones that already include `ExperimentallyCacheable`.

For anything else the raw constant name stayed in the dependency list
and was handed to the Digestor as a template path. Nothing resolves
there, so the declaration silently did nothing and the only signal was
`Couldn't find template for digesting: SomeComponent` in the log.

That defeats the point of the hatch: the dependencies you need it for
are exactly the ones whose target you may not control or want to modify.

Resolve any `ViewComponent::Base` descendant instead, and register it so
the Resolver can synthesize a template for it -- being named by a
declaration is itself the opt-in. `component_for` resolves through the
same path, since the registry can now hold components that never
included the module.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@erikaxel
erikaxel force-pushed the resolve-explicit-component-dependencies-without-opt-in branch from 5d8ec5b to b085ece Compare September 3, 2026 11:53
@erikaxel
erikaxel marked this pull request as draft September 3, 2026 12:24
@erikaxel

erikaxel commented Sep 3, 2026

Copy link
Copy Markdown
Author

I have marked this as draft until we have considered #2714. Also, this overlaps quite a bit with #2713

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant