Skip to content

Commit

Permalink
fix: opt-in only to process innerHTML template params
Browse files Browse the repository at this point in the history
  • Loading branch information
harlan-zw committed Sep 10, 2023
1 parent 26c9f9e commit c4b87ad
Show file tree
Hide file tree
Showing 8 changed files with 38 additions and 13 deletions.
4 changes: 2 additions & 2 deletions packages/schema/src/head.ts
@@ -1,6 +1,6 @@
import type { Hookable, NestedHooks } from 'hookable'
import type { HeadHooks } from './hooks'
import type { HeadTag, TagPosition, TagPriority } from './tags'
import type { HeadTag, ProcessesTemplateParams, TagPosition, TagPriority } from './tags'
import type { Head } from './schema'

/**
Expand Down Expand Up @@ -82,7 +82,7 @@ export interface CreateHeadOptions {
hooks?: NestedHooks<HeadHooks>
}

export interface HeadEntryOptions extends TagPosition, TagPriority {
export interface HeadEntryOptions extends TagPosition, TagPriority, ProcessesTemplateParams {
mode?: RuntimeMode
transform?: (input: unknown) => unknown
head?: Unhead
Expand Down
2 changes: 1 addition & 1 deletion packages/schema/src/schema.ts
Expand Up @@ -5,7 +5,7 @@ export type Never<T> = {
[P in keyof T]?: never
}

export type UserTagConfigWithoutInnerContent = TagPriority & TagPosition & ResolvesDuplicates & Never<InnerContent>
export type UserTagConfigWithoutInnerContent = TagPriority & TagPosition & ResolvesDuplicates & Never<InnerContent> & { processTemplateParams?: false } // only allow opt-out
export type UserAttributesConfig = ResolvesDuplicates & TagPriority & Never<InnerContent & TagPosition>

export interface SchemaAugmentations extends MergeHead {
Expand Down
5 changes: 4 additions & 1 deletion packages/schema/src/tags.ts
Expand Up @@ -74,19 +74,22 @@ export interface TagPriority {
tagPriority?: number | 'critical' | 'high' | 'low' | `before:${string}` | `after:${string}`
}

export type TagUserProperties = TagPriority & TagPosition & MaybePromiseProps<InnerContent> & ResolvesDuplicates
export type TagUserProperties = TagPriority & TagPosition & MaybePromiseProps<InnerContent> & ResolvesDuplicates & ProcessesTemplateParams

export type TagKey = keyof Head

export type TemplateParams = { separator?: string } & Record<string, null | string | Record<string, string>>

export interface ProcessesTemplateParams { processTemplateParams?: boolean }

export interface HasTemplateParams {
templateParams?: TemplateParams
}

export interface HeadTag extends TagPriority, TagPosition, ResolvesDuplicates, HasTemplateParams {
tag: TagKey
props: Record<string, string>
processTemplateParams?: boolean
innerHTML?: string
textContent?: string
/**
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/src/constants.ts
Expand Up @@ -24,7 +24,7 @@ export const ValidHeadTags = [

export const UniqueTags = ['base', 'title', 'titleTemplate', 'bodyAttrs', 'htmlAttrs', 'templateParams']

export const TagConfigKeys = ['tagPosition', 'tagPriority', 'tagDuplicateStrategy', 'innerHTML', 'textContent']
export const TagConfigKeys = ['tagPosition', 'tagPriority', 'tagDuplicateStrategy', 'innerHTML', 'textContent', 'processTemplateParams']

export const IsBrowser = typeof window !== 'undefined'

Expand Down
21 changes: 13 additions & 8 deletions packages/unhead/src/plugins/templateParams.ts
Expand Up @@ -16,14 +16,19 @@ export default defineHeadPlugin({
// pre-process title
params.pageTitle = processTemplateParams(params.pageTitle as string || title || '', params, sep)
for (const tag of tags) {
if (['titleTemplate', 'title'].includes(tag.tag) && typeof tag.textContent === 'string')
tag.textContent = processTemplateParams(tag.textContent, params, sep)
else if (tag.tag === 'meta' && typeof tag.props.content === 'string')
tag.props.content = processTemplateParams(tag.props.content, params, sep)
else if (tag.tag === 'link' && typeof tag.props.href === 'string')
tag.props.href = processTemplateParams(tag.props.href, params, sep)
else if (tag.tag === 'script' && ['application/json', 'application/ld+json'].includes(tag.props.type) && tag.innerHTML && tag.props.id !== 'unhead:payload')
tag.innerHTML = processTemplateParams(tag.innerHTML, params, sep)
// allow opt-out
if (tag.processTemplateParams === false)
continue
if (['titleTemplate', 'title'].includes(tag.tag) && typeof tag.textContent === 'string') { tag.textContent = processTemplateParams(tag.textContent, params, sep) }
else if (tag.tag === 'meta' && typeof tag.props.content === 'string') { tag.props.content = processTemplateParams(tag.props.content, params, sep) }
else if (tag.tag === 'link' && typeof tag.props.href === 'string') { tag.props.href = processTemplateParams(tag.props.href, params, sep) }
// everything else requires explicit opt-in
else if (tag.processTemplateParams === true) {
if (tag.innerHTML)
tag.innerHTML = processTemplateParams(tag.innerHTML, params, sep)
else if (tag.textContent)
tag.textContent = processTemplateParams(tag.textContent, params, sep)
}
}
ctx.tags = tags.filter(tag => tag.tag !== 'templateParams')
},
Expand Down
2 changes: 2 additions & 0 deletions test/unhead/dom/templateParams.test.ts
Expand Up @@ -27,6 +27,7 @@ describe('templateParams', () => {
siteName: '%site.name',
siteUrl: '%site.url',
},
processTemplateParams: true,
},
],
meta: [
Expand Down Expand Up @@ -117,6 +118,7 @@ describe('templateParams', () => {
innerHTML: JSON.stringify({
title: '%s',
}),
processTemplateParams: true,
},
],
})
Expand Down
2 changes: 2 additions & 0 deletions test/unhead/ssr/templateParams.test.ts
Expand Up @@ -25,6 +25,7 @@ describe('ssr templateParams', () => {
innerHTML: JSON.stringify({
title: '%s',
}),
processTemplateParams: true,
},
],
templateParams: {
Expand Down Expand Up @@ -73,6 +74,7 @@ describe('ssr templateParams', () => {
innerHTML: {
title: '%s',
},
processTemplateParams: true,
},
],
})
Expand Down
13 changes: 13 additions & 0 deletions test/vue/ssr/templateParams.test.ts
Expand Up @@ -56,6 +56,7 @@ describe('ssr vue templateParams', () => {
title: '%s',
description: '%site.description',
},
processTemplateParams: true,
},
],
templateParams: {
Expand Down Expand Up @@ -177,4 +178,16 @@ describe('ssr vue templateParams', () => {
<meta name=\\"description\\" content=\\"Hi, welcome to the dev v0.0.0 of test.\\">"
`)
})

test('entry opt-out', async () => {
const head = createHead()
head.push({
title: 'Hello %name',
templateParams: { name: 'World' },
}, {
processTemplateParams: false,
})
const { headTags } = await renderSSRHead(head)
expect(headTags).toMatchInlineSnapshot('"<title>Hello %name</title>"')
})
})

0 comments on commit c4b87ad

Please sign in to comment.