feat(view): transitive modulepreload and css resolution for vite pipeline#2133
Merged
feat(view): transitive modulepreload and css resolution for vite pipeline#2133
Conversation
…line
Closes the "asset-pipeline maturity" gap called out in the Wheels 4.0
feature audit. Adds:
- `$viteResolveAssets(entrypoint)` — private resolver that walks the
Vite manifest once, following `imports` transitively with a visited
set for dedup + cycle safety. Returns `{scripts, styles, preloads}`.
- `vitePreloadTag(entrypoint, head=true)` — new view helper that emits
`<link rel="modulepreload">` for a Vite entrypoint and its transitive
chunk imports. Useful for Turbo Drive hover-preload patterns in Wheels
4.0 apps using the Hotwire package.
- `viteScriptTag()` and `viteStyleTag()` now use the resolver so that:
- transitive chunk CSS is included in stylesheet output (previously
only the entry's own CSS was emitted)
- modulepreload links for each transitive import chunk are emitted
into <head> via a new `$viteHtmlHead()` wrapper (brings production-
performance parity with Rails/Laravel Vite integrations)
- `viteStrictManifest` setting (default `true`) — missing manifest
entries now throw `Wheels.ViteAssetNotFound` in production regardless
of `showErrorInformation`. Apps that need the 3.x silent behavior can
`set(viteStrictManifest=false)`. Consistent with Wheels 4.0's
deny-by-default security posture: deploy-time misconfigurations are
loud instead of quiet 404s.
- `$viteHtmlHead()` — thin wrapper around `$htmlhead` that also writes
to a request-scoped capture buffer when one is present, giving tests
a way to assert what's been emitted to <head>. No production
behavior change.
Tests extend viteSpec with:
- 7 new tests for `$viteResolveAssets` (leaf, CSS, transitive imports,
diamond dedup, cycle termination, strict + silent miss paths)
- 2 new tests on `viteScriptTag` (transitive CSS, modulepreload via
capture buffer)
- 1 new test on `viteStyleTag` (transitive CSS)
- 4 new tests for `vitePreloadTag` (dev mode, head=false inline, head=
true via $viteHtmlHead, strict miss)
- 2 new strict-mode regression tests on `viteAsset`
Suite: 34 vite specs passing on Lucee 7 + SQLite (up from 18). Full core
suite: 3063 pass / 0 fail. The 20 remaining errors are unrelated to this
change (Playwright JARs absent on this machine).
Spec: docs/superpowers/specs/2026-04-16-vite-pipeline-maturity-design.md
Plan: docs/superpowers/plans/2026-04-16-vite-pipeline-maturity.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the asset-pipeline maturity gap called out in the Wheels 4.0 feature audit. Brings production-perf parity with Rails' and Laravel's Vite integrations.
New public API
vitePreloadTag(entrypoint, head=true)— emits<link rel="modulepreload">for a Vite entrypoint and its transitive chunk imports. Useful for Turbo Drive hover-preload patterns (Turbo shipped with Wheels 4.0 via the Hotwire package).Changed behavior
viteScriptTag()andviteStyleTag()now walk the manifest'simportstree transitively:<link rel="stylesheet">(previously only the entry's own CSS).<link rel="modulepreload">is emitted into<head>for every transitive chunk, so the browser can start fetching shared code before the entry script parses.viteStrictManifestsetting (new, defaulttrue) — missing manifest entries now throwWheels.ViteAssetNotFoundin production regardless ofshowErrorInformation. Consistent with Wheels 4.0's deny-by-default security posture. Setfalseto restore 3.x silent behavior.Internal refactor (Approach D, the "C lite")
Extracted a private
$viteResolveAssets(entrypoint)resolver insidevendor/wheels/view/vite.cfcthat walks the manifest once and returns{scripts, styles, preloads}. Existing helpers and the newvitePreloadTag()all become thin callers. Recursive walker uses a visited struct for cycle safety and diamond-dependency dedup. Also extracted$viteHtmlHead(text)as a testable wrapper around$htmlhead.Test plan
showErrorInformation; opt-out viaset(viteStrictManifest=false)preserves 3.x silent path.vitePreloadTagcovered for dev mode, inlinehead=false,head=truevia$viteHtmlHeadcapture buffer, and strict-miss throw.graphqlclientruntime issue; CI will exercise the full engine matrix.Docs
docs/superpowers/specs/2026-04-16-vite-pipeline-maturity-design.mddocs/superpowers/plans/2026-04-16-vite-pipeline-maturity.md[Unreleased] > Added > Views.docs/src/introduction/upgrading-to-4.0.mdgains an 11th breaking-change entry with detect / fix / opt-out guidance.🤖 Generated with Claude Code