Skip to content

Commit

Permalink
feat: add titleTemplate option (#22) (#49)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
BobbieGoede and antfu authored Aug 1, 2022
1 parent cb7269f commit 1382c43
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 26 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ const finalHTML = `

## API

### `createHead()`
### `createHead(head?: HeadObject | Ref<HeadObject>)`

Create the head manager instance.

Expand All @@ -99,6 +99,7 @@ Create the head manager instance.
```ts
interface HeadObject {
title?: MaybeRef<string>
titleTemplate?: MaybeRef<string> | ((title?: string) => string)
meta?: MaybeRef<HeadAttrs[]>
link?: MaybeRef<HeadAttrs[]>
base?: MaybeRef<HeadAttrs>
Expand Down
6 changes: 5 additions & 1 deletion example/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export const createApp = () => {
setup() {
useHead({
title: "About",
titleTemplate: "%s | About Template",
})
return () => (
<div>
Expand Down Expand Up @@ -136,7 +137,10 @@ export const createApp = () => {
return () => <RouterView />
},
})
const head = createHead()

const head = createHead({
titleTemplate: "%s | @vueuse/head",
})

app.use(head)
app.use(router)
Expand Down
75 changes: 57 additions & 18 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
UnwrapRef,
watchEffect,
VNode,
unref,
shallowRef,
} from "vue"
import {
PROVIDE_KEY,
Expand All @@ -26,6 +28,7 @@ export type HeadAttrs = { [k: string]: any }

export type HeadObject = {
title?: MaybeRef<string>
titleTemplate?: MaybeRef<string> | ((title?: string) => string)
meta?: MaybeRef<HeadAttrs[]>
link?: MaybeRef<HeadAttrs[]>
base?: MaybeRef<HeadAttrs>
Expand Down Expand Up @@ -101,7 +104,7 @@ export const injectHead = () => {
return head
}

const acceptFields: Array<keyof HeadObject> = [
const acceptFields: Array<keyof Omit<HeadObject, "titleTemplate">> = [
"title",
"meta",
"link",
Expand All @@ -113,25 +116,45 @@ const acceptFields: Array<keyof HeadObject> = [
"bodyAttrs",
]

const renderTemplate = (
template: HeadObjectPlain["titleTemplate"],
title?: string,
): string => {
if (template == null) return ""
if (typeof template === "string") {
return template.replace("%s", title ?? "")
}
return template(unref(title))
}

const headObjToTags = (obj: HeadObjectPlain) => {
const tags: HeadTag[] = []
const keys = Object.keys(obj) as Array<keyof HeadObjectPlain>

for (const key of Object.keys(obj) as Array<keyof HeadObjectPlain>) {
for (const key of keys) {
if (obj[key] == null) continue

if (key === "title") {
tags.push({ tag: key, props: { children: obj[key] } })
} else if (key === "base") {
tags.push({ tag: key, props: { key: "default", ...obj[key] } })
} else if (acceptFields.includes(key)) {
const value = obj[key]
if (Array.isArray(value)) {
value.forEach((item) => {
tags.push({ tag: key, props: item })
})
} else if (value) {
tags.push({ tag: key, props: value })
}
switch (key) {
case "title":
tags.push({ tag: key, props: { children: obj[key] } })
break
case "titleTemplate":
break
case "base":
tags.push({ tag: key, props: { key: "default", ...obj[key] } })
break
default:
if (acceptFields.includes(key)) {
const value = obj[key]
if (Array.isArray(value)) {
value.forEach((item) => {
tags.push({ tag: key, props: item })
})
} else if (value) {
tags.push({ tag: key, props: value })
}
}
break
}
}

Expand Down Expand Up @@ -252,10 +275,14 @@ const updateElements = (
)
}

export const createHead = () => {
export const createHead = (initHeadObject?: MaybeRef<HeadObjectPlain>) => {
let allHeadObjs: Ref<HeadObjectPlain>[] = []
let previousTags = new Set<string>()

if (initHeadObject) {
allHeadObjs.push(shallowRef(initHeadObject))
}

const head: HeadClient = {
install(app) {
app.config.globalProperties.$head = head
Expand All @@ -268,8 +295,13 @@ export const createHead = () => {
get headTags() {
const deduped: HeadTag[] = []

const titleTemplate = allHeadObjs
.map((i) => unref(i).titleTemplate)
.reverse()
.find((i) => i != null)

allHeadObjs.forEach((objs) => {
const tags = headObjToTags(objs.value)
const tags = headObjToTags(unref(objs))
tags.forEach((tag) => {
if (
tag.tag === "meta" ||
Expand Down Expand Up @@ -297,6 +329,13 @@ export const createHead = () => {
}
}

if (titleTemplate && tag.tag === "title") {
tag.props.children = renderTemplate(
titleTemplate,
tag.props.children,
)
}

deduped.push(tag)
})
})
Expand Down Expand Up @@ -356,8 +395,8 @@ export const createHead = () => {
const IS_BROWSER = typeof window !== "undefined"

export const useHead = (obj: MaybeRef<HeadObject>) => {
const headObj = ref(obj) as Ref<HeadObjectPlain>
const head = injectHead()
const headObj = ref(obj) as Ref<HeadObjectPlain>

head.addHeadObjs(headObj)

Expand Down
6 changes: 3 additions & 3 deletions tests/snapshots/test.tsx.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Generated by [AVA](https://avajs.dev).
<meta charset="UTF-8">␊
<meta http-equiv="X-UA-Compatible" content="IE=edge">␊
<meta name="viewport" content="width=device-width, initial-scale=1.0">␊
<title>count: 0</title>␊
<title>count: 0 | @vueuse/head</title>␊
<meta name="global-meta" content="some global meta tag"><meta name="description" content="desc 2"><meta property="og:locale:alternate" content="fr"><meta property="og:locale:alternate" content="zh"><base href="/"><style>body {background: red}</style><noscript>This app requires javascript to work</noscript><script>console.log("a")</script><link href="/foo" rel="stylesheet"><meta name="head:count" content="9">`

## <Head>: basic
Expand All @@ -37,7 +37,7 @@ Generated by [AVA](https://avajs.dev).
},
{
props: {
children: '0',
children: '0 | @vueuse/head',
},
tag: 'title',
},
Expand Down Expand Up @@ -83,7 +83,7 @@ Generated by [AVA](https://avajs.dev).
},
{
props: {
children: '1',
children: '1 | @vueuse/head',
},
tag: 'title',
},
Expand Down
Binary file modified tests/snapshots/test.tsx.snap
Binary file not shown.
6 changes: 3 additions & 3 deletions tests/test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,13 @@ test("browser", async (t) => {
t.snapshot(headHTML)

await page.click("button.counter")
t.is(await page.title(), "count: 1")
t.is(await page.title(), "count: 1 | @vueuse/head")

await page.click("button.change-home-title")
t.is(await page.title(), "count: 1")
t.is(await page.title(), "count: 1 | @vueuse/head")

await page.click('a[href="/about"]')
t.is(await page.title(), "About")
t.is(await page.title(), "About | About Template")
})

test("useHead: server async setup", async (t) => {
Expand Down

0 comments on commit 1382c43

Please sign in to comment.