fix(validate): false positives for warmup preloads and charset position#732
Conversation
- Allow `preload` + `fetchpriority="low"` for `as="script"` (the warmup pattern used by `useScript` to start fetching at low priority). - Skip `preload-async-defer-conflict` when the preload uses `fetchpriority="low"` for the same reason. - Run `charset-not-early` only on SSR (DOM order is already set after hydration), and sort by capo weight while filtering virtual tags (`templateParams`, `titleTemplate`) so they don't inflate the position count. - Pass tag references to several `report()` calls so consumers can surface the offending tag in error messages.
📝 WalkthroughWalkthroughRefined multiple validation rules in the Unhead validation plugin: preload-fetchpriority-conflict now exempts non-script resources; preload-async-defer-conflict ignores low-priority preloads; enhanced reporting by attaching HeadTag context to specific rules; improved missing-template-params detection; and restricted charset-not-early validation to SSR contexts with weight-based tag ordering. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Bundle Size Analysis
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/unhead/src/plugins/validate.ts (1)
556-568:⚠️ Potential issue | 🟡 MinorFix the remediation text for preload/script conflicts.
The warning now says to add
fetchpriority="low"to the script, but this branch only skips the rule when the preload link hasfetchpriority="low". Following the current message won't clear the warning.✏️ Suggested wording
- report('preload-async-defer-conflict', `Script "${tag.props.src}" is preloaded but has "${attr}" — preload escalates priority, defeating the purpose of ${attr}. Remove the preload or add fetchpriority="low" to the script.`, 'warn', preloadTag) + report('preload-async-defer-conflict', `Script "${tag.props.src}" is preloaded but has "${attr}" — preload escalates priority, defeating the purpose of ${attr}. Remove the preload or add fetchpriority="low" to the preload link.`, 'warn', preloadTag)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/unhead/src/plugins/validate.ts` around lines 556 - 568, The warning text in the preload/script conflict check (see preloadScriptHrefs map and the report call inside the loop that checks tag.tag === 'script' and tag.props.async/defer) incorrectly advises adding fetchpriority="low" to the script; update the remediation string so it tells the user to either remove the preload or add fetchpriority="low" to the preload link (not the script), keeping the rest of the message (script src, attr name, warn level, and preloadTag) unchanged.
🧹 Nitpick comments (2)
packages/unhead/test/unit/plugins/validate.test.ts (1)
1046-1061: Add a client-side regression for the new SSR guard.This case covers virtual-tag filtering, but it doesn't assert the other half of the fix:
charset-not-earlyshould be skipped whenhead.ssris false. A small clientcreateHeadtest here would keep that guard from regressing.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/unhead/test/unit/plugins/validate.test.ts` around lines 1046 - 1061, The test currently covers the SSR path using createValidationHead and renderSSRHead for the 'charset-not-early' rule but lacks a client-side (non-SSR) regression check; add a small test that uses createHead (or createValidationHead with head.ssr set to false) to assert that when head.ssr is false the 'charset-not-early' rule is skipped (i.e., rules.find(r => r.id === 'charset-not-early') returns falsy), ensuring the SSR guard preventing client-side validation regressions remains enforced—place the new test alongside the existing one and mirror the setup (title, templateParams, titleTemplate, meta charset) but ensure head.ssr is explicitly false or use createHead to simulate client behavior.packages/unhead/src/plugins/validate.ts (1)
503-507: Please add regression coverage for the newtagpayload.These
report(..., tag)calls change a devtools-facing contract, but the suite still only checks rule presence/source. A direct assertion thatrule.tagpoints at the offendingog:url,og:image, andtemplateParamstag would keep this from regressing.Also applies to: 588-590
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/unhead/src/plugins/validate.ts` around lines 503 - 507, Tests do not assert the new report(...) tag payload so regressions can reintroduce incorrect tag pointers; update the validation test suite to add assertions that the reported rule objects include a tag property pointing to the offending elements for the rules 'canonical-og-url-mismatch' and 'og-image-missing-dimensions' (and similarly for the rules around templateParams mentioned at lines ~588-590). Locate the tests that currently check rule presence/source and extend them to fetch the report for those rule ids and assert that report.tag === ogUrl (for canonical-og-url-mismatch) and report.tag === metaByKey.get('og:image') (for og-image-missing-dimensions), and add equivalent assertions for the templateParams rule so the devtools-facing contract is covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@packages/unhead/src/plugins/validate.ts`:
- Around line 556-568: The warning text in the preload/script conflict check
(see preloadScriptHrefs map and the report call inside the loop that checks
tag.tag === 'script' and tag.props.async/defer) incorrectly advises adding
fetchpriority="low" to the script; update the remediation string so it tells the
user to either remove the preload or add fetchpriority="low" to the preload link
(not the script), keeping the rest of the message (script src, attr name, warn
level, and preloadTag) unchanged.
---
Nitpick comments:
In `@packages/unhead/src/plugins/validate.ts`:
- Around line 503-507: Tests do not assert the new report(...) tag payload so
regressions can reintroduce incorrect tag pointers; update the validation test
suite to add assertions that the reported rule objects include a tag property
pointing to the offending elements for the rules 'canonical-og-url-mismatch' and
'og-image-missing-dimensions' (and similarly for the rules around templateParams
mentioned at lines ~588-590). Locate the tests that currently check rule
presence/source and extend them to fetch the report for those rule ids and
assert that report.tag === ogUrl (for canonical-og-url-mismatch) and report.tag
=== metaByKey.get('og:image') (for og-image-missing-dimensions), and add
equivalent assertions for the templateParams rule so the devtools-facing
contract is covered.
In `@packages/unhead/test/unit/plugins/validate.test.ts`:
- Around line 1046-1061: The test currently covers the SSR path using
createValidationHead and renderSSRHead for the 'charset-not-early' rule but
lacks a client-side (non-SSR) regression check; add a small test that uses
createHead (or createValidationHead with head.ssr set to false) to assert that
when head.ssr is false the 'charset-not-early' rule is skipped (i.e.,
rules.find(r => r.id === 'charset-not-early') returns falsy), ensuring the SSR
guard preventing client-side validation regressions remains enforced—place the
new test alongside the existing one and mirror the setup (title, templateParams,
titleTemplate, meta charset) but ensure head.ssr is explicitly false or use
createHead to simulate client behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e81dafb5-47a8-4a5a-9dd8-09a9260c5ab5
📒 Files selected for processing (2)
packages/unhead/src/plugins/validate.tspackages/unhead/test/unit/plugins/validate.test.ts
🔗 Linked issue
Related to #725
❓ Type of change
📚 Description
Fixes a few false positives in the recently-added performance validation rules and tightens the offending-tag context passed to
report().Warmup preloads (
useScript)useScriptships apreloadlink withas="script"andfetchpriority="low"to start fetching analytics-style scripts early at low priority. The new perf rules flagged this pattern twice:preload-fetchpriority-conflict-- now skipped whenas="script"(the warmup pattern is intentional).preload-async-defer-conflict-- now skipped when the preload itself hasfetchpriority="low".Charset position
charset-not-earlywas producing two kinds of false positive:head.ssr.templateParams,titleTemplate) toward the head position, pushing<meta charset>past the threshold even though they don't render. Now sorts by capo weight (_w/_p) and filters to actual head element tags before measuring.Better tag context in reports
canonical-og-url-mismatch,og-image-missing-dimensions, andmissing-template-params-pluginnow pass the offending tag toreport()so consumers (and devtools) can highlight it.Tests added for each new branch (preload+script warmup, preload+defer+low priority, virtual-tag charset position). 120/120 validate tests passing.
Summary by CodeRabbit