You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: better type narrowing escape hatches for custom rel/type (#735)
* feat(unhead): add defineLink and defineScript helpers for custom rel/type
The `satisfies GenericLink` / `satisfies GenericScript` pattern the v3
docstrings and migration guide recommended did not actually type-check
against useHead, because `GenericLink` / `GenericScript` are intentionally
excluded from the `Link` / `Script` unions to prevent silent absorption
of known `rel` / `type` values.
Introduce `defineLink` and `defineScript` helpers that keep known-value
strictness (e.g. `rel: 'preload'` still requires `as`, `type: 'module'`
still requires `src` or inline content) while accepting custom values
via `GenericLink` / `GenericScript`. Update docstrings, migration guide,
release notes, and the useHead API reference accordingly.
* feat(unhead): add standard link rels (me, webmention, privacy-policy, etc.)
Several standard `<link>` rel keywords were missing from `KnownLinkRel`
and the `Link` union, making them look like "custom" rels that needed
`defineLink`. Add dedicated interfaces and union members for: `me`,
`webmention`, `privacy-policy`, `terms-of-service`, `expect`,
`compression-dictionary`, and `alternate stylesheet`.
`alternate stylesheet` requires `title` per spec; `expect` carries an
optional `blocking: 'render'`. Update `defineLink` examples and docs to
use genuinely non-standard rels (`openid2.provider`, `EditURI`) instead
of rels that are now directly supported.
* test: update exports snapshot for defineLink/defineScript
* fix(unhead): require textContent or innerHTML on data scripts
DataScriptTextContent used optional textContent/innerHTML in both union
branches, allowing empty data scripts like `{ type: 'application/ld+json' }`
to type-check with no content at all. Make each branch require its
respective content field so JsonLdScript, SpeculationRulesScript, and
ApplicationJsonScript enforce non-empty payloads at the type level.
Add type tests covering empty-payload rejection for ld+json, speculation
rules, application/json, and importmap. Rename two script test titles
from `rel="..."` to `type="..."` per review feedback.
Copy file name to clipboardExpand all lines: docs/6.migration-guide/1.v3.md
+10-10Lines changed: 10 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -406,7 +406,7 @@ The SSR hooks (`ssr:beforeRender`, `ssr:render`, `ssr:rendered`) are now synchro
406
406
407
407
π¦ Impact Level: **Medium**
408
408
409
-
The `Link` and `Script` types are now strict discriminated unions. Known `rel` and `type` values enforce per-tag required properties at the type level. `GenericLink` and `GenericScript` are still exported for custom values.
409
+
The `Link` and `Script` types are now strict discriminated unions. Known `rel` and `type` values enforce per-tag required properties at the type level. Use the new `defineLink` and `defineScript` helpers to declare custom values without losing strictness on known ones.
410
410
411
411
### Link Tags
412
412
@@ -423,28 +423,28 @@ useHead({
423
423
})
424
424
```
425
425
426
-
For custom`rel` values not in the known set, use `satisfies GenericLink`:
426
+
For non-standard`rel` values not covered by `KnownLinkRel` (e.g. OpenID endpoints, RSD links), use `defineLink`:
Inline scripts must have `textContent` or `innerHTML` and cannot include `src`, `async`, or `defer`. For custom `type` values, use `satisfies GenericScript`:
440
+
Inline scripts must have `textContent` or `innerHTML` and cannot include `src`, `async`, or `defer`. For custom `type` values, use `defineScript`:
@@ -460,14 +460,14 @@ Meta `content` is now required on name, property, and http-equiv meta tags. Use
460
460
461
461
### String Variables
462
462
463
-
When `rel` or `type` comes from a variable typed as `string`, TypeScript cannot narrow the union. Use `as const` or `satisfies`:
463
+
When `rel` or `type` comes from a variable typed as `string`, TypeScript cannot narrow the union. Wrap it with `defineLink` / `defineScript` or use `as const` for literals:
464
464
465
465
```ts
466
-
importtype { GenericLink} from'unhead/types'
466
+
import{ defineLink, useHead} from'unhead'
467
467
468
468
const rel =getRelFromConfig() // string, not a literal
Copy file name to clipboardExpand all lines: docs/7.releases/1.v3.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -213,4 +213,4 @@ For the full migration guide, see [Migrate to v3](/docs/migration-guide/v3).
213
213
-`TemplateParamsPlugin` and `AliasSortingPlugin` are no longer included by default
214
214
-`init`, `dom:renderTag`, `dom:rendered` hooks removed; `dom:beforeRender` is now synchronous
215
215
-`@unhead/addons` renamed to `@unhead/bundler`; framework Vite plugins now use a named `Unhead` export instead of a default export
216
-
-`Link` / `Script` unions are strict: custom `rel` / `type` values need `satisfies GenericLink` / `GenericScript`, and meta `content` is now required (use `content: null` to remove)
216
+
-`Link` / `Script` unions are strict: use the new `defineLink` / `defineScript` helpers for custom `rel` / `type` values, and meta `content` is now required (use `content: null` to remove)
`Link`and`Script`usediscriminatedunionskeyedon`rel`and`type`. Knownvaluesenforceper-tagrequiredpropertiesatthetypelevel. For custom or non-standard values, use `satisfies` with the generic fallback types:
365
+
`Link`and`Script`usediscriminatedunionskeyedon`rel`and`type`. Knownvaluesenforceper-tagrequiredpropertiesatthetypelevel. For non-standard values not covered by the built-in union, use the `defineLink` and `defineScript` helpers, which preserve strict narrowing on known values and fall through to `GenericLink` / `GenericScript` for anything else:
366
366
367
367
::code-group
368
368
369
-
```ts [Custom Link rel]
370
-
import type { GenericLink } from '@unhead/dynamic-import/types'
369
+
```ts [Non-standard Link rel]
370
+
import { defineLink, useHead } from '@unhead/dynamic-import'
371
371
372
372
useHead({
373
373
link: [
374
374
{ rel: 'canonical', href: 'https://example.com' }, // known rel, works directly
0 commit comments