Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

feat(nuxt): migrate to latest @vueuse/head #8000

Merged
merged 36 commits into from Oct 12, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
1629a5c
feat(head): migrate to latest `@vueuse/head`
harlan-zw Oct 5, 2022
52a8084
fix: correct innerHTML casing
harlan-zw Oct 5, 2022
e9bcdf8
revert: @vue/reactivity change
harlan-zw Oct 5, 2022
55b7361
revert: @vue/reactivity change
harlan-zw Oct 5, 2022
871c4d2
fix: typecheck error
harlan-zw Oct 5, 2022
2545cbb
chore: polishing PR
harlan-zw Oct 7, 2022
61f13ae
chore: bump @vueuse/head
harlan-zw Oct 7, 2022
01ddd16
fix: proper input types for `useHeadRaw`
harlan-zw Oct 7, 2022
f29cd62
feat(components): add `body` and `renderPriority` support
harlan-zw Oct 7, 2022
b4db806
fix: pause dom updates in client only
harlan-zw Oct 7, 2022
62c476b
chore: upgrade to @vueuse/head 1.0.0-rc.4
harlan-zw Oct 11, 2022
ecf29af
Merge branch 'main' of github.com:nuxt/framework into feat/vueuse-hea…
harlan-zw Oct 11, 2022
efe084c
chore: sync lock file
harlan-zw Oct 11, 2022
07d9ae4
Merge branch 'main' into feat/vueuse-head-migration
harlan-zw Oct 11, 2022
df84fce
chore: bump pkg
harlan-zw Oct 11, 2022
e38cd77
Merge branch 'feat/vueuse-head-migration' of github.com:harlan-zw/nux…
harlan-zw Oct 11, 2022
f77137a
Merge branch 'main' into feat/vueuse-head-migration
harlan-zw Oct 11, 2022
9475bb3
revert: test change
harlan-zw Oct 11, 2022
b3e2393
Merge branch 'feat/vueuse-head-migration' of github.com:harlan-zw/nux…
harlan-zw Oct 11, 2022
d0e0c3a
chore: simplify shortcuts
harlan-zw Oct 12, 2022
5b6f2a6
Merge branch 'main' of github.com:nuxt/framework into feat/vueuse-hea…
harlan-zw Oct 12, 2022
20e96ec
chore: simplify the initial meta even further
harlan-zw Oct 12, 2022
fb20fbf
doc: improve seo / useHead doc
harlan-zw Oct 12, 2022
c8f7c11
doc: polish and lint
harlan-zw Oct 12, 2022
6389ce4
doc: polish and lint
harlan-zw Oct 12, 2022
4788b1d
chore: pr fixes
harlan-zw Oct 12, 2022
53d6656
style: de-semify
danielroe Oct 12, 2022
b7bd452
refactor: move to dev-deps
danielroe Oct 12, 2022
8857474
fix: update type import
danielroe Oct 12, 2022
8f34199
Merge remote-tracking branch 'origin/main' into feat/vueuse-head-migr…
danielroe Oct 12, 2022
a8044a5
docs: add apostrophe
danielroe Oct 12, 2022
f94a0ab
docs: and this one too
danielroe Oct 12, 2022
5a92a44
Merge branch 'main' into feat/vueuse-head-migration
harlan-zw Oct 12, 2022
034e8ca
chore: bump @vueuse/head, fixes titleTemplate issue
harlan-zw Oct 12, 2022
53db611
style: small tweaks
danielroe Oct 12, 2022
b21a8ce
test: add some very basic type tests
danielroe Oct 12, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/nuxt/package.json
Expand Up @@ -42,9 +42,9 @@
"@nuxt/telemetry": "^2.1.5",
"@nuxt/ui-templates": "^0.4.0",
"@nuxt/vite-builder": "3.0.0-rc.11",
"@vue/reactivity": "^3.2.39",
"@vue/reactivity": "^3.2.40",
"@vue/shared": "^3.2.39",
"@vueuse/head": "^0.7.12",
"@vueuse/head": "^0.9.6",
"chokidar": "^3.5.3",
"cookie-es": "^0.5.0",
"defu": "^6.1.0",
Expand Down
10 changes: 5 additions & 5 deletions packages/nuxt/src/head/runtime/components.ts
@@ -1,6 +1,6 @@
import { defineComponent, PropType } from 'vue'
import type { SetupContext } from 'vue'
import { useHead } from './composables'
import { useHeadRaw } from './composables'
import type {
Props,
FetchPriority,
Expand All @@ -15,7 +15,7 @@ const removeUndefinedProps = (props: Props) =>
Object.fromEntries(Object.entries(props).filter(([, value]) => value !== undefined))

const setupForUseMeta = (metaFactory: (props: Props, ctx: SetupContext) => Record<string, any>, renderChild?: boolean) => (props: Props, ctx: SetupContext) => {
useHead(() => metaFactory({ ...removeUndefinedProps(props), ...ctx.attrs }, ctx))
useHeadRaw(() => metaFactory({ ...removeUndefinedProps(props), ...ctx.attrs }, ctx))
return () => renderChild ? ctx.slots.default?.() : null
}

Expand Down Expand Up @@ -97,7 +97,7 @@ export const Script = defineComponent({
.map(({ children }) => children)
.join('')
if (textContent) {
script.children = textContent
script.innerHTML = textContent
}
return {
script: [script]
Expand All @@ -120,7 +120,7 @@ export const NoScript = defineComponent({
.map(({ children }) => children)
.join('')
if (textContent) {
noscript.children = textContent
noscript.innerHtml = textContent
danielroe marked this conversation as resolved.
Show resolved Hide resolved
}
return {
noscript: [noscript]
Expand Down Expand Up @@ -244,7 +244,7 @@ export const Style = defineComponent({
if (process.dev && typeof textContent !== 'string') {
console.error('<Style> can only take a string in its default slot.')
}
style.children = textContent
style.textContent = textContent
}
return {
style: [style]
Expand Down
18 changes: 13 additions & 5 deletions packages/nuxt/src/head/runtime/composables.ts
@@ -1,25 +1,33 @@
import { isFunction } from '@vue/shared'
import { computed } from 'vue'
import type { ComputedGetter, ComputedRef } from '@vue/reactivity'
import type { MetaObject } from '@nuxt/schema'
import { MaybeComputedRef } from '@vueuse/shared'
antfu marked this conversation as resolved.
Show resolved Hide resolved
import { useNuxtApp } from '#app'

type Computable<T> = T extends Record<string, any> ? ComputedGetter<T> | { [K in keyof T]: T[K] | ComputedRef<T[K]> } : T

/**
* You can pass in a meta object, which has keys corresponding to meta tags:
* `title`, `base`, `script`, `style`, `meta` and `link`, as well as `htmlAttrs` and `bodyAttrs`.
*
* Alternatively, for reactive meta state, you can pass in a function
* that returns a meta object.
*/
export function useHead (meta: Computable<MetaObject>) {
export function useHead (meta: MaybeComputedRef<MetaObject>) {
const resolvedMeta = isFunction(meta) ? computed(meta) : meta
useNuxtApp()._useHead(resolvedMeta)
}

/**
* Same as `useHead` but will render meta tags without encoding.
*
* Warning: This function opens you up to XSS attacks. Only use this if you trust the source of the meta tag data.
*/
export function useHeadRaw (meta: MaybeComputedRef<MetaObject>) {
const resolvedMeta = isFunction(meta) ? computed(meta) : meta
useNuxtApp()._useHead(resolvedMeta, { raw: true })
}

// TODO: remove useMeta support when Nuxt 3 is stable
/** @deprecated Please use new `useHead` composable instead */
export function useMeta (meta: Computable<MetaObject>) {
export function useMeta (meta: MaybeComputedRef<MetaObject>) {
return useHead(meta)
}
37 changes: 27 additions & 10 deletions packages/nuxt/src/head/runtime/lib/vueuse-head.plugin.ts
@@ -1,8 +1,9 @@
import { createHead, renderHeadToString } from '@vueuse/head'
import { computed, ref, watchEffect, onBeforeUnmount, getCurrentInstance, ComputedGetter } from 'vue'
import { createHead, HeadEntryOptions, renderHeadToString } from '@vueuse/head'
import { computed, ref, watchEffect, onBeforeUnmount, getCurrentInstance, isRef } from 'vue'
import defu from 'defu'
import type { MetaObject } from '..'
import { defineNuxtPlugin } from '#app'
import { MaybeComputedRef } from '@vueuse/shared'
import type { MetaObject, MetaObjectPlain } from '@nuxt/schema'
import { defineNuxtPlugin, useRouter } from '#app'

export default defineNuxtPlugin((nuxtApp) => {
const head = createHead()
Expand All @@ -15,19 +16,35 @@ export default defineNuxtPlugin((nuxtApp) => {
headReady = true
})

nuxtApp._useHead = (_meta: MetaObject | ComputedGetter<MetaObject>) => {
const meta = ref<MetaObject>(_meta)
// pause dom updates in between page transitions
let pauseDOMUpdates = false
head.hookBeforeDomUpdate.push(() => !pauseDOMUpdates)
nuxtApp.hooks.hookOnce('page:finish', () => {
pauseDOMUpdates = false
// start pausing DOM updates when route changes (trigger immediately)
useRouter().beforeEach(() => {
pauseDOMUpdates = true
})
// watch for new route before unpausing dom updates (triggered after suspense resolved)
useRouter().afterEach(() => {
pauseDOMUpdates = false
head.updateDOM()
})
})

nuxtApp._useHead = (_meta: MaybeComputedRef<MetaObject>, options: HeadEntryOptions) => {
const meta = isRef(_meta) ? _meta : ref<MetaObject>(_meta as MetaObject)
const headObj = computed(() => {
const overrides: MetaObject = { meta: [] }
const overrides: MetaObjectPlain = { meta: [] }
if (meta.value.charset) {
overrides.meta!.push({ key: 'charset', charset: meta.value.charset })
overrides.meta!.push({ charset: meta.value.charset })
}
if (meta.value.viewport) {
overrides.meta!.push({ name: 'viewport', content: meta.value.viewport })
}
return defu(overrides, meta.value)
})
head.addHeadObjs(headObj as any)
const removeHeadObjs = head.addHeadObjs(headObj as any, options)

if (process.server) { return }

Expand All @@ -39,7 +56,7 @@ export default defineNuxtPlugin((nuxtApp) => {
if (!vm) { return }

onBeforeUnmount(() => {
head.removeHeadObjs(headObj as any)
removeHeadObjs()
head.updateDOM()
})
}
Expand Down
1 change: 1 addition & 0 deletions packages/schema/package.json
Expand Up @@ -22,6 +22,7 @@
"vite": "~3.1.3"
},
"dependencies": {
"@vueuse/head": "^0.9.5",
"c12": "^0.2.13",
"create-require": "^1.1.1",
"defu": "^6.1.0",
Expand Down
6 changes: 3 additions & 3 deletions packages/schema/src/config/_app.ts
Expand Up @@ -3,7 +3,7 @@ import { existsSync, readdirSync } from 'node:fs'
import defu from 'defu'
import { defineUntypedSchema } from 'untyped'

import { MetaObject } from '../types/meta'
import {MetaObject, MetaObjectPlain} from '../types/meta'

export default defineUntypedSchema({
/**
Expand Down Expand Up @@ -112,7 +112,7 @@ export default defineUntypedSchema({
*/
head: {
$resolve: async (val, get) => {
const resolved: Required<MetaObject> = defu(val, await get('meta'), {
const resolved: Required<MetaObjectPlain> = defu(val, await get('meta'), {
meta: [],
link: [],
style: [],
Expand Down Expand Up @@ -233,7 +233,7 @@ export default defineUntypedSchema({
},

/**
* @type {typeof import('../src/types/meta').MetaObject}
* @type {typeof import('../src/types/meta').MetaObjectPlain}
* @version 3
* @deprecated - use `head` instead
*/
Expand Down
4 changes: 2 additions & 2 deletions packages/schema/src/types/config.ts
Expand Up @@ -2,7 +2,7 @@ import type { KeepAliveProps, TransitionProps } from 'vue'
import { ConfigSchema } from '../../schema/config'
import type { ServerOptions as ViteServerOptions, UserConfig as ViteUserConfig } from 'vite'
import type { Options as VuePluginOptions } from '@vitejs/plugin-vue'
import type { MetaObject } from './meta'
import type { MetaObjectPlain } from './meta'
import type { Nuxt } from './nuxt'

type DeepPartial<T> = T extends Function ? T : T extends Record<string, any> ? { [P in keyof T]?: DeepPartial<T[P]> } : T
Expand Down Expand Up @@ -82,7 +82,7 @@ export interface AppConfigInput extends Record<string, any> {
}

export interface NuxtAppConfig {
head: MetaObject
head: MetaObjectPlain
layoutTransition: boolean | TransitionProps
pageTransition: boolean | TransitionProps
keepalive: boolean | KeepAliveProps
Expand Down
111 changes: 94 additions & 17 deletions packages/schema/src/types/meta.ts
@@ -1,4 +1,18 @@
export interface MetaObject extends Record<string, any> {
import type { HeadObjectPlain, HeadObject } from '@vueuse/head'

export interface HeadAugmentations {
// runtime type modifications
base?: {};
link?: {};
meta?: {};
style?: {};
script?: {};
noscript?: {};
htmlAttrs?: {};
bodyAttrs?: {};
}

export type MetaObjectPlain = HeadObjectPlain<HeadAugmentations> & {
/**
* The character encoding in which the document is encoded => `<meta charset="<value>" />`
*
Expand All @@ -12,21 +26,84 @@ export interface MetaObject extends Record<string, any> {
* @default `'width=device-width, initial-scale=1'`
*/
viewport?: string
}

/** Each item in the array maps to a newly-created `<meta>` element, where object properties map to attributes. */
meta?: Array<Record<string, any>>
/** Each item in the array maps to a newly-created `<link>` element, where object properties map to attributes. */
link?: Array<Record<string, any>>
/** Each item in the array maps to a newly-created `<style>` element, where object properties map to attributes. */
style?: Array<Record<string, any>>
/** Each item in the array maps to a newly-created `<script>` element, where object properties map to attributes. */
script?: Array<Record<string, any>>
/** Each item in the array maps to a newly-created `<noscript>` element, where object properties map to attributes. */
noscript?: Array<Record<string, any>>

titleTemplate?: string | ((title: string) => string)
title?: string

bodyAttrs?: Record<string, any>
htmlAttrs?: Record<string, any>
export interface MetaObject {
/**
* The character encoding in which the document is encoded => `<meta charset="<value>" />`
*
* @default `'utf-8'`
*/
charset?: string
/**
* Configuration of the viewport (the area of the window in which web content can be seen),
* mapped to => `<meta name="viewport" content="<value>" />`
*
* @default `'width=device-width, initial-scale=1'`
*/
viewport?: string
/**
* The <title> HTML element defines the document's title that is shown in a browser's title bar or a page's tab.
* It only contains text; tags within the element are ignored.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title
*/
title?: HeadObject<HeadAugmentations>['title'];
/**
* Generate the title from a template.
*/
titleTemplate?: HeadObject<HeadAugmentations>['titleTemplate'];
danielroe marked this conversation as resolved.
Show resolved Hide resolved
/**
* The <base> HTML element specifies the base URL to use for all relative URLs in a document.
* There can be only one <base> element in a document.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
*/
base?: HeadObject<HeadAugmentations>['base'];
/**
* The <link> HTML element specifies relationships between the current document and an external resource.
* This element is most commonly used to link to stylesheets, but is also used to establish site icons
* (both "favicon" style icons and icons for the home screen and apps on mobile devices) among other things.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-as
*/
link?: HeadObject<HeadAugmentations>['link'];
/**
* The <meta> element represents metadata that cannot be expressed in other HTML elements, like <link> or <script>.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta
*/
meta?: HeadObject<HeadAugmentations>['meta'];
/**
* The <style> HTML element contains style information for a document, or part of a document.
* It contains CSS, which is applied to the contents of the document containing the <style> element.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style
*/
style?: HeadObject<HeadAugmentations>['style'];
/**
* The <script> HTML element is used to embed executable code or data; this is typically used to embed or refer to JavaScript code.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script
*/
script?: HeadObject<HeadAugmentations>['script'];
/**
* The <noscript> HTML element defines a section of HTML to be inserted if a script type on the page is unsupported
* or if scripting is currently turned off in the browser.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript
*/
noscript?: HeadObject<HeadAugmentations>['noscript'];
/**
* Attributes for the <html> HTML element.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html
*/
htmlAttrs?: HeadObject<HeadAugmentations>['htmlAttrs'];
/**
* Attributes for the <body> HTML element.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/body
*/
bodyAttrs?: HeadObject<HeadAugmentations>['bodyAttrs'];
}
3 changes: 2 additions & 1 deletion test/basic.test.ts
Expand Up @@ -219,6 +219,7 @@ describe('pages', () => {
describe('head tags', () => {
it('should render tags', async () => {
const headHtml = await $fetch('/head')

expect(headHtml).toContain('<title>Using a dynamic component - Title Template Fn Change</title>')
expect(headHtml).not.toContain('<meta name="description" content="first">')
expect(headHtml).toContain('<meta charset="utf-16">')
Expand All @@ -229,7 +230,7 @@ describe('head tags', () => {
expect(headHtml).toContain('<meta name="description" content="overriding with an inline useHead call">')
expect(headHtml).toMatch(/<html[^>]*class="html-attrs-test"/)
expect(headHtml).toMatch(/<body[^>]*class="body-attrs-test"/)
expect(headHtml).toContain('script>console.log("works with useMeta too")</script>')
expect(headHtml).toContain('console.log(&quot;works with useMeta too&quot;)')
expect(headHtml).toContain('<script src="https://a-body-appended-script.com" data-meta-body="true"></script></body>')

const indexHtml = await $fetch('/')
Expand Down