mirror of
https://github.com/nuxt/nuxt.git
synced 2025-01-22 11:22:43 +00:00
chore: unplugin import replacements and v3, v4 exports
This commit is contained in:
parent
d4796b6085
commit
cf36b9b607
12
package.json
12
package.json
@ -44,9 +44,9 @@
|
|||||||
"@nuxt/vite-builder": "workspace:*",
|
"@nuxt/vite-builder": "workspace:*",
|
||||||
"@nuxt/webpack-builder": "workspace:*",
|
"@nuxt/webpack-builder": "workspace:*",
|
||||||
"@types/node": "22.10.7",
|
"@types/node": "22.10.7",
|
||||||
"@unhead/schema": "2.0.0-alpha.6",
|
"@unhead/schema": "2.0.0-alpha.7",
|
||||||
"@unhead/vue": "2.0.0-alpha.6",
|
"@unhead/vue": "2.0.0-alpha.7",
|
||||||
"@unhead/shared": "2.0.0-alpha.6",
|
"@unhead/shared": "2.0.0-alpha.7",
|
||||||
"@vue/compiler-core": "3.5.13",
|
"@vue/compiler-core": "3.5.13",
|
||||||
"@vue/compiler-dom": "3.5.13",
|
"@vue/compiler-dom": "3.5.13",
|
||||||
"@vue/shared": "3.5.13",
|
"@vue/shared": "3.5.13",
|
||||||
@ -63,7 +63,7 @@
|
|||||||
"typescript": "5.7.3",
|
"typescript": "5.7.3",
|
||||||
"ufo": "1.5.4",
|
"ufo": "1.5.4",
|
||||||
"unbuild": "3.3.1",
|
"unbuild": "3.3.1",
|
||||||
"unhead": "2.0.0-alpha.6",
|
"unhead": "2.0.0-alpha.7",
|
||||||
"unimport": "3.14.6",
|
"unimport": "3.14.6",
|
||||||
"vite": "6.0.9",
|
"vite": "6.0.9",
|
||||||
"vue": "3.5.13"
|
"vue": "3.5.13"
|
||||||
@ -83,8 +83,8 @@
|
|||||||
"@types/babel__helper-plugin-utils": "7.10.3",
|
"@types/babel__helper-plugin-utils": "7.10.3",
|
||||||
"@types/node": "22.10.7",
|
"@types/node": "22.10.7",
|
||||||
"@types/semver": "7.5.8",
|
"@types/semver": "7.5.8",
|
||||||
"@unhead/schema": "2.0.0-alpha.6",
|
"@unhead/schema": "2.0.0-alpha.7",
|
||||||
"@unhead/vue": "2.0.0-alpha.6",
|
"@unhead/vue": "2.0.0-alpha.7",
|
||||||
"@vitest/coverage-v8": "3.0.2",
|
"@vitest/coverage-v8": "3.0.2",
|
||||||
"@vue/test-utils": "2.4.6",
|
"@vue/test-utils": "2.4.6",
|
||||||
"acorn": "8.14.0",
|
"acorn": "8.14.0",
|
||||||
|
@ -71,7 +71,7 @@
|
|||||||
"@nuxt/schema": "workspace:*",
|
"@nuxt/schema": "workspace:*",
|
||||||
"@nuxt/telemetry": "^2.6.4",
|
"@nuxt/telemetry": "^2.6.4",
|
||||||
"@nuxt/vite-builder": "workspace:*",
|
"@nuxt/vite-builder": "workspace:*",
|
||||||
"@unhead/vue": "^2.0.0-alpha.6",
|
"@unhead/vue": "^2.0.0-alpha.7",
|
||||||
"@vue/shared": "^3.5.13",
|
"@vue/shared": "^3.5.13",
|
||||||
"acorn": "8.14.0",
|
"acorn": "8.14.0",
|
||||||
"c12": "^2.0.1",
|
"c12": "^2.0.1",
|
||||||
|
@ -91,7 +91,7 @@ export default defineComponent({
|
|||||||
const instance = getCurrentInstance()!
|
const instance = getCurrentInstance()!
|
||||||
const event = useRequestEvent()
|
const event = useRequestEvent()
|
||||||
|
|
||||||
let activeHead: ActiveHeadEntry<Partial<ResolvedHead>>
|
let activeHead: ActiveHeadEntry<ResolvedHead>
|
||||||
|
|
||||||
// TODO: remove use of `$fetch.raw` when nitro 503 issues on windows dev server are resolved
|
// TODO: remove use of `$fetch.raw` when nitro 503 issues on windows dev server are resolved
|
||||||
const eventFetch = import.meta.server ? event!.fetch : import.meta.dev ? $fetch.raw : globalThis.fetch
|
const eventFetch = import.meta.server ? event!.fetch : import.meta.dev ? $fetch.raw : globalThis.fetch
|
||||||
|
@ -1,78 +1,9 @@
|
|||||||
import type { UseHeadInput, UseHeadOptions, UseHeadSafeInput, UseSeoMetaInput, VueHeadClient } from '@unhead/vue'
|
export {
|
||||||
import type { ActiveHeadEntry, MergeHead } from '@unhead/schema'
|
injectHead,
|
||||||
import { hasInjectionContext, inject } from 'vue'
|
useHead,
|
||||||
import {
|
useServerHead,
|
||||||
useHead as headCore,
|
useSeoMeta,
|
||||||
useHeadSafe as headSafe,
|
useServerSeoMeta,
|
||||||
headSymbol,
|
useHeadSafe,
|
||||||
useSeoMeta as seoMeta, useServerHead as serverHead, useServerHeadSafe as serverHeadSafe,
|
useServerHeadSafe,
|
||||||
useServerSeoMeta as serverSeoMeta,
|
} from '#unhead/composables'
|
||||||
} from '@unhead/vue'
|
|
||||||
import { tryUseNuxtApp, useNuxtApp } from '#app'
|
|
||||||
import type { NuxtApp } from '#app'
|
|
||||||
// @ts-expect-error build-time
|
|
||||||
import { isNuxt4 } from '#build/nuxt.config.mjs'
|
|
||||||
|
|
||||||
function resolveUnheadInject (): VueHeadClient<MergeHead> | undefined {
|
|
||||||
// try use Vue inject
|
|
||||||
if (hasInjectionContext()) {
|
|
||||||
return inject<VueHeadClient<MergeHead>>(headSymbol)!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injects the head client from the Nuxt context or Vue inject.
|
|
||||||
*
|
|
||||||
* In Nuxt v3 this function will not throw an error if the context is missing.
|
|
||||||
*/
|
|
||||||
export function injectHead (nuxtApp?: NuxtApp): VueHeadClient<MergeHead> {
|
|
||||||
// Nuxt 4 will throw an error if the context is missing
|
|
||||||
const nuxt = nuxtApp || (isNuxt4 ? useNuxtApp() : tryUseNuxtApp())
|
|
||||||
return nuxt?.ssrContext?.head || nuxt?.runWithContext(resolveUnheadInject) as VueHeadClient<MergeHead> || resolveUnheadInject()
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NuxtUseHeadOptions extends UseHeadOptions {
|
|
||||||
nuxt?: NuxtApp
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useHead<T extends MergeHead> (input: UseHeadInput<T>, options: NuxtUseHeadOptions = {}): ActiveHeadEntry<UseHeadInput<T>> | void {
|
|
||||||
const head = injectHead(options.nuxt)
|
|
||||||
if (isNuxt4 || head) {
|
|
||||||
return headCore(input, { head, ...options }) as ActiveHeadEntry<UseHeadInput<T>>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useHeadSafe<T extends MergeHead> (input: UseHeadSafeInput, options: NuxtUseHeadOptions = {}): ActiveHeadEntry<UseHeadInput<T>> | void {
|
|
||||||
const head = injectHead(options.nuxt)
|
|
||||||
if (isNuxt4 || head) {
|
|
||||||
return headSafe(input, { head, ...options }) as ActiveHeadEntry<UseHeadInput>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useSeoMeta (input: UseSeoMetaInput, options: NuxtUseHeadOptions = {}): ActiveHeadEntry<UseSeoMetaInput> | void {
|
|
||||||
const head = injectHead(options.nuxt)
|
|
||||||
if (isNuxt4 || head) {
|
|
||||||
return seoMeta(input, { head, ...options }) as ActiveHeadEntry<UseHeadInput>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useServerHead<T extends MergeHead> (input: UseHeadInput<T>, options: NuxtUseHeadOptions = {}): ActiveHeadEntry<UseHeadInput<T>> | void {
|
|
||||||
const head = injectHead(options.nuxt)
|
|
||||||
if (isNuxt4 || head) {
|
|
||||||
return serverHead(input, { head, ...options }) as ActiveHeadEntry<UseHeadInput<T>>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useServerHeadSafe (input: UseHeadSafeInput, options: NuxtUseHeadOptions = {}): ActiveHeadEntry<UseSeoMetaInput> | void {
|
|
||||||
const head = injectHead(options.nuxt)
|
|
||||||
if (isNuxt4 || head) {
|
|
||||||
return serverHeadSafe(input, { head, ...options }) as ActiveHeadEntry<UseHeadInput>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useServerSeoMeta (input: UseSeoMetaInput, options: NuxtUseHeadOptions = {}): ActiveHeadEntry<UseHeadInput> | void {
|
|
||||||
const head = injectHead(options.nuxt)
|
|
||||||
if (isNuxt4 || head) {
|
|
||||||
return serverSeoMeta(input, { head, ...options }) as ActiveHeadEntry<UseHeadInput>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { UseScriptInput, UseScriptOptions } from '@unhead/vue/legacy'
|
import type { UseScriptInput } from '@unhead/vue/legacy'
|
||||||
import { createError } from './error'
|
import { createError } from './error'
|
||||||
|
|
||||||
function renderStubMessage (name: string) {
|
function renderStubMessage (name: string) {
|
||||||
@ -13,7 +13,7 @@ function renderStubMessage (name: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
export function useScript<T extends Record<string | symbol, any>> (input: UseScriptInput, options?: UseScriptOptions) {
|
export function useScript<T extends Record<string | symbol, any>> (input: UseScriptInput, options?: Record<string, unknown>) {
|
||||||
renderStubMessage('useScript')
|
renderStubMessage('useScript')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -525,7 +525,6 @@ export const nuxtConfigTemplate: NuxtTemplate = {
|
|||||||
`export const chunkErrorEvent = ${ctx.nuxt.options.experimental.emitRouteChunkError ? ctx.nuxt.options.builder === '@nuxt/vite-builder' ? '"vite:preloadError"' : '"nuxt:preloadError"' : 'false'}`,
|
`export const chunkErrorEvent = ${ctx.nuxt.options.experimental.emitRouteChunkError ? ctx.nuxt.options.builder === '@nuxt/vite-builder' ? '"vite:preloadError"' : '"nuxt:preloadError"' : 'false'}`,
|
||||||
`export const crawlLinks = ${!!((ctx.nuxt as any)._nitro as Nitro).options.prerender.crawlLinks}`,
|
`export const crawlLinks = ${!!((ctx.nuxt as any)._nitro as Nitro).options.prerender.crawlLinks}`,
|
||||||
`export const spaLoadingTemplateOutside = ${ctx.nuxt.options.experimental.spaLoadingTemplateLocation === 'body'}`,
|
`export const spaLoadingTemplateOutside = ${ctx.nuxt.options.experimental.spaLoadingTemplateLocation === 'body'}`,
|
||||||
`export const isNuxt4 = ${ctx.nuxt.options.future.compatibilityVersion === 4}`,
|
|
||||||
].join('\n\n')
|
].join('\n\n')
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { dirname, resolve } from 'pathe'
|
import { resolve } from 'pathe'
|
||||||
import { addComponent, addPlugin, addTemplate, defineNuxtModule, tryResolveModule } from '@nuxt/kit'
|
import { addBuildPlugin, addComponent, addPlugin, addTemplate, defineNuxtModule, tryResolveModule } from '@nuxt/kit'
|
||||||
import type { NuxtOptions } from '@nuxt/schema'
|
import type { NuxtOptions } from '@nuxt/schema'
|
||||||
import { distDir } from '../dirs'
|
import { distDir } from '../dirs'
|
||||||
|
import { UnheadImportsPlugin } from './plugins/unhead-imports'
|
||||||
|
|
||||||
const components = ['NoScript', 'Link', 'Base', 'Title', 'Meta', 'Style', 'Head', 'Html', 'Body']
|
const components = ['NoScript', 'Link', 'Base', 'Title', 'Meta', 'Style', 'Head', 'Html', 'Body']
|
||||||
|
|
||||||
@ -10,13 +11,13 @@ export default defineNuxtModule<NuxtOptions['unhead']>({
|
|||||||
name: 'nuxt:meta',
|
name: 'nuxt:meta',
|
||||||
configKey: 'unhead',
|
configKey: 'unhead',
|
||||||
},
|
},
|
||||||
async setup (options, nuxt) {
|
setup (options, nuxt) {
|
||||||
const runtimeDir = resolve(distDir, 'head/runtime')
|
const runtimeDir = resolve(distDir, 'head/runtime')
|
||||||
|
|
||||||
// Transpile @unhead/vue
|
// Transpile @unhead/vue
|
||||||
nuxt.options.build.transpile.push('@unhead/vue')
|
nuxt.options.build.transpile.push('@unhead/vue')
|
||||||
|
|
||||||
const isNuxtV4 = nuxt.options.future?.compatibilityVersion === 4
|
const isNuxtV4 = nuxt.options._majorVersion === 4 || nuxt.options.future?.compatibilityVersion === 4
|
||||||
// Register components
|
// Register components
|
||||||
const componentsPath = resolve(runtimeDir, 'components')
|
const componentsPath = resolve(runtimeDir, 'components')
|
||||||
for (const componentName of components) {
|
for (const componentName of components) {
|
||||||
@ -38,44 +39,11 @@ export default defineNuxtModule<NuxtOptions['unhead']>({
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// for Nuxt v3 users we will alias `@unhead/vue` to our custom export path so that
|
nuxt.options.alias['#unhead/composables'] = resolve(runtimeDir, 'composables', isNuxtV4 ? 'v4' : 'v3')
|
||||||
// import { useHead } from '@unhead/vue'
|
addBuildPlugin(UnheadImportsPlugin({
|
||||||
// will work in a context without the Vue app such as Nuxt plugins and such
|
sourcemap: !!nuxt.options.sourcemap.server,
|
||||||
// for Nuxt v4 user should import from #imports
|
rootDir: nuxt.options.rootDir,
|
||||||
if (!isNuxtV4) {
|
}))
|
||||||
const realUnheadPath = await tryResolveModule('@unhead/vue', nuxt.options.modulesDir) || '@unhead/vue'
|
|
||||||
// Transpile @unhead/vue
|
|
||||||
nuxt.options.build.transpile.push(realUnheadPath)
|
|
||||||
for (const subpath of ['legacy', 'types']) {
|
|
||||||
nuxt.options.alias[`@unhead/vue/${subpath}`] = resolve(dirname(realUnheadPath), subpath)
|
|
||||||
}
|
|
||||||
addTemplate({
|
|
||||||
filename: 'unhead-exports.mjs',
|
|
||||||
getContents () {
|
|
||||||
return `
|
|
||||||
export {
|
|
||||||
injectHead,
|
|
||||||
useHead,
|
|
||||||
useHeadSafe,
|
|
||||||
useSeoMeta,
|
|
||||||
useServerHead,
|
|
||||||
useServerHeadSafe,
|
|
||||||
useServerSeoMeta,
|
|
||||||
} from '#app/composables/head'
|
|
||||||
|
|
||||||
export {
|
|
||||||
createHeadCore,
|
|
||||||
resolveUnrefHeadInput,
|
|
||||||
unheadVueComposablesImports,
|
|
||||||
} from '${JSON.stringify(realUnheadPath)}'
|
|
||||||
|
|
||||||
export * from '@unhead/vue/types'
|
|
||||||
`
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
nuxt.options.alias['@unhead/vue'] = '#build/unhead-exports.mjs'
|
|
||||||
}
|
|
||||||
|
|
||||||
addTemplate({
|
addTemplate({
|
||||||
filename: 'unhead-options.mjs',
|
filename: 'unhead-options.mjs',
|
||||||
|
85
packages/nuxt/src/head/plugins/unhead-imports.ts
Normal file
85
packages/nuxt/src/head/plugins/unhead-imports.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { createUnplugin } from 'unplugin'
|
||||||
|
import MagicString from 'magic-string'
|
||||||
|
import type { ImportSpecifier } from 'estree'
|
||||||
|
import { relative } from 'pathe'
|
||||||
|
import { parseAndWalk, withLocations } from '../../core/utils/parse'
|
||||||
|
import { isJS, isVue } from '../../core/utils'
|
||||||
|
import { distDir } from '../../dirs'
|
||||||
|
import { logger } from '../../utils'
|
||||||
|
|
||||||
|
interface UnheadImportsPluginOptions {
|
||||||
|
sourcemap: boolean
|
||||||
|
rootDir: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const UNHEAD_LIB_RE = /node_modules\/(?:@unhead\/[^/]+|unhead)\//
|
||||||
|
|
||||||
|
function toImports (specifiers: ImportSpecifier[]) {
|
||||||
|
return specifiers.map((specifier) => {
|
||||||
|
const isNamedImport = specifier.imported && specifier.imported.name !== specifier.local.name
|
||||||
|
return isNamedImport ? `${specifier.imported.name} as ${specifier.local.name}` : specifier.local.name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const UnheadNuxtContextComposables = [
|
||||||
|
'useHead',
|
||||||
|
'useHeadSafe',
|
||||||
|
'useServerHeadSafe',
|
||||||
|
'useSeoMeta',
|
||||||
|
'useServerSeoMeta',
|
||||||
|
'useServerHead',
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To use composable in an async context we need to pass Nuxt context to the Unhead composables.
|
||||||
|
*
|
||||||
|
* We swap imports from @unhead/vue to #app/composables/head and warn users for type safety.
|
||||||
|
*/
|
||||||
|
export const UnheadImportsPlugin = (options: UnheadImportsPluginOptions) => createUnplugin(() => {
|
||||||
|
return {
|
||||||
|
name: 'nuxt:head:unhead-imports',
|
||||||
|
enforce: 'post',
|
||||||
|
transformInclude (id) {
|
||||||
|
return (isJS(id) || isVue(id, { type: ['script'] })) && !id.startsWith(distDir) && !UNHEAD_LIB_RE.test(id)
|
||||||
|
},
|
||||||
|
transform (code, id) {
|
||||||
|
if (!code.includes('@unhead/vue')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const s = new MagicString(code)
|
||||||
|
const importsToAdd: ImportSpecifier[] = []
|
||||||
|
// Without setup function, vue compiler does not generate __name
|
||||||
|
parseAndWalk(code, id, function (node) {
|
||||||
|
// find any imports from @unhead/vue, swap the matchImports for an import from #app/composables/head
|
||||||
|
if (node.type === 'ImportDeclaration' && ['@unhead/vue', '#app/composables/head'].includes(node.source.value)) {
|
||||||
|
importsToAdd.push(...node.specifiers as ImportSpecifier[])
|
||||||
|
const { start, end } = withLocations(node)
|
||||||
|
s.remove(start, end)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const importsFromUnhead = importsToAdd.filter(specifier => UnheadNuxtContextComposables.includes(specifier.imported.name))
|
||||||
|
const importsFromHead = importsToAdd.filter(specifier => !UnheadNuxtContextComposables.includes(specifier.imported.name))
|
||||||
|
if (importsFromUnhead.length) {
|
||||||
|
// warn if user has imported from @unhead/vue themselves
|
||||||
|
if (!id.includes('node_modules')) {
|
||||||
|
logger.warn(`You are importing from \`@unhead/vue\` in \`./${relative(options.rootDir, id)}\`. Please import from \`#app\` instead for full type safety.`)
|
||||||
|
}
|
||||||
|
s.prepend(`import { ${toImports(importsFromUnhead).join(', ')} } from '#app/composables/head'\n`)
|
||||||
|
}
|
||||||
|
// if there are imports from #app/composables/head, add an import from @unhead/vue
|
||||||
|
if (importsFromHead.length) {
|
||||||
|
s.prepend(`import { ${toImports(importsFromHead).join(', ')} } from '@unhead/vue'\n`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.hasChanged()) {
|
||||||
|
return {
|
||||||
|
code: s.toString(),
|
||||||
|
map: options.sourcemap
|
||||||
|
? s.generateMap({ hires: true })
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
@ -1,4 +1,4 @@
|
|||||||
import { computed, defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
import type { PropType, SetupContext } from 'vue'
|
import type { PropType, SetupContext } from 'vue'
|
||||||
import type {
|
import type {
|
||||||
CrossOrigin,
|
CrossOrigin,
|
||||||
@ -23,7 +23,7 @@ const removeUndefinedProps = (props: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const setupForUseMeta = (metaFactory: (props: Props, ctx: SetupContext) => Record<string, any>, renderChild?: boolean) => (props: Props, ctx: SetupContext) => {
|
const setupForUseMeta = (metaFactory: (props: Props, ctx: SetupContext) => Record<string, any>, renderChild?: boolean) => (props: Props, ctx: SetupContext) => {
|
||||||
useHead(computed(() => metaFactory({ ...removeUndefinedProps(props), ...ctx.attrs }, ctx)))
|
useHead(() => metaFactory({ ...removeUndefinedProps(props), ...ctx.attrs }, ctx))
|
||||||
return () => renderChild ? ctx.slots.default?.() : null
|
return () => renderChild ? ctx.slots.default?.() : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
73
packages/nuxt/src/head/runtime/composables/v3.ts
Normal file
73
packages/nuxt/src/head/runtime/composables/v3.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import type { UseHeadInput, UseHeadOptions, UseHeadSafeInput, UseSeoMetaInput, VueHeadClient } from '@unhead/vue'
|
||||||
|
import type { ActiveHeadEntry, MergeHead } from '@unhead/schema'
|
||||||
|
import { hasInjectionContext, inject } from 'vue'
|
||||||
|
import {
|
||||||
|
useHead as headCore,
|
||||||
|
useHeadSafe as headSafe,
|
||||||
|
headSymbol,
|
||||||
|
useSeoMeta as seoMeta, useServerHead as serverHead, useServerHeadSafe as serverHeadSafe,
|
||||||
|
useServerSeoMeta as serverSeoMeta,
|
||||||
|
} from '@unhead/vue'
|
||||||
|
import { tryUseNuxtApp } from '#app'
|
||||||
|
import type { NuxtApp } from '#app'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injects the head client from the Nuxt context or Vue inject.
|
||||||
|
*
|
||||||
|
* In Nuxt v3 this function will not throw an error if the context is missing.
|
||||||
|
*/
|
||||||
|
export function injectHead (nuxtApp?: NuxtApp): VueHeadClient<MergeHead> {
|
||||||
|
// Nuxt 4 will throw an error if the context is missing
|
||||||
|
const nuxt = nuxtApp || tryUseNuxtApp()
|
||||||
|
return nuxt?.ssrContext?.head || nuxt?.runWithContext(() => {
|
||||||
|
if (hasInjectionContext()) {
|
||||||
|
return inject<VueHeadClient<MergeHead>>(headSymbol)!
|
||||||
|
}
|
||||||
|
}) as VueHeadClient<MergeHead>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NuxtUseHeadOptions extends UseHeadOptions {
|
||||||
|
nuxt?: NuxtApp
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useHead<T extends MergeHead> (input: UseHeadInput<T>, options: NuxtUseHeadOptions = {}): ActiveHeadEntry<UseHeadInput<T>> | void {
|
||||||
|
const head = injectHead(options.nuxt)
|
||||||
|
if (head) {
|
||||||
|
return headCore(input, { head, ...options }) as ActiveHeadEntry<UseHeadInput<T>>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useHeadSafe<T extends MergeHead> (input: UseHeadSafeInput, options: NuxtUseHeadOptions = {}): ActiveHeadEntry<UseHeadInput<T>> | void {
|
||||||
|
const head = injectHead(options.nuxt)
|
||||||
|
if (head) {
|
||||||
|
return headSafe(input, { head, ...options }) as ActiveHeadEntry<UseHeadInput>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSeoMeta (input: UseSeoMetaInput, options: NuxtUseHeadOptions = {}): ActiveHeadEntry<UseSeoMetaInput> | void {
|
||||||
|
const head = injectHead(options.nuxt)
|
||||||
|
if (head) {
|
||||||
|
return seoMeta(input, { head, ...options }) as ActiveHeadEntry<UseHeadInput>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useServerHead<T extends MergeHead> (input: UseHeadInput<T>, options: NuxtUseHeadOptions = {}): ActiveHeadEntry<UseHeadInput<T>> | void {
|
||||||
|
const head = injectHead(options.nuxt)
|
||||||
|
if (head) {
|
||||||
|
return serverHead(input, { head, ...options }) as ActiveHeadEntry<UseHeadInput<T>>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useServerHeadSafe (input: UseHeadSafeInput, options: NuxtUseHeadOptions = {}): ActiveHeadEntry<UseSeoMetaInput> | void {
|
||||||
|
const head = injectHead(options.nuxt)
|
||||||
|
if (head) {
|
||||||
|
return serverHeadSafe(input, { head, ...options }) as ActiveHeadEntry<UseHeadInput>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useServerSeoMeta (input: UseSeoMetaInput, options: NuxtUseHeadOptions = {}): ActiveHeadEntry<UseHeadInput> | void {
|
||||||
|
const head = injectHead(options.nuxt)
|
||||||
|
if (head) {
|
||||||
|
return serverSeoMeta(input, { head, ...options }) as ActiveHeadEntry<UseHeadInput>
|
||||||
|
}
|
||||||
|
}
|
59
packages/nuxt/src/head/runtime/composables/v4.ts
Normal file
59
packages/nuxt/src/head/runtime/composables/v4.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import type { UseHeadInput, UseHeadOptions, UseHeadSafeInput, UseSeoMetaInput, VueHeadClient } from '@unhead/vue'
|
||||||
|
import type { ActiveHeadEntry, MergeHead } from '@unhead/schema'
|
||||||
|
import { hasInjectionContext, inject } from 'vue'
|
||||||
|
import {
|
||||||
|
useHead as headCore,
|
||||||
|
useHeadSafe as headSafe,
|
||||||
|
headSymbol,
|
||||||
|
useSeoMeta as seoMeta, useServerHead as serverHead, useServerHeadSafe as serverHeadSafe,
|
||||||
|
useServerSeoMeta as serverSeoMeta,
|
||||||
|
} from '@unhead/vue'
|
||||||
|
import { useNuxtApp } from '#app'
|
||||||
|
import type { NuxtApp } from '#app'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injects the head client from the Nuxt context or Vue inject.
|
||||||
|
*/
|
||||||
|
export function injectHead (nuxtApp?: NuxtApp): VueHeadClient<MergeHead> {
|
||||||
|
// Nuxt 4 will throw an error if the context is missing
|
||||||
|
const nuxt = nuxtApp || useNuxtApp()
|
||||||
|
return nuxt.ssrContext?.head || nuxt.runWithContext(() => {
|
||||||
|
if (hasInjectionContext()) {
|
||||||
|
return inject<VueHeadClient<MergeHead>>(headSymbol)!
|
||||||
|
}
|
||||||
|
}) as VueHeadClient<MergeHead>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NuxtUseHeadOptions extends UseHeadOptions {
|
||||||
|
nuxt?: NuxtApp
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useHead<T extends MergeHead> (input: UseHeadInput<T>, options: NuxtUseHeadOptions = {}): ActiveHeadEntry<UseHeadInput<T>> {
|
||||||
|
const head = injectHead(options.nuxt)
|
||||||
|
return headCore(input, { head, ...options }) as ActiveHeadEntry<UseHeadInput<T>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useHeadSafe<T extends MergeHead> (input: UseHeadSafeInput, options: NuxtUseHeadOptions = {}): ActiveHeadEntry<UseHeadInput<T>> {
|
||||||
|
const head = injectHead(options.nuxt)
|
||||||
|
return headSafe(input, { head, ...options }) as ActiveHeadEntry<UseHeadInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSeoMeta (input: UseSeoMetaInput, options: NuxtUseHeadOptions = {}): ActiveHeadEntry<UseSeoMetaInput> {
|
||||||
|
const head = injectHead(options.nuxt)
|
||||||
|
return seoMeta(input, { head, ...options }) as ActiveHeadEntry<UseHeadInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useServerHead<T extends MergeHead> (input: UseHeadInput<T>, options: NuxtUseHeadOptions = {}): ActiveHeadEntry<UseHeadInput<T>> {
|
||||||
|
const head = injectHead(options.nuxt)
|
||||||
|
return serverHead(input, { head, ...options }) as ActiveHeadEntry<UseHeadInput<T>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useServerHeadSafe (input: UseHeadSafeInput, options: NuxtUseHeadOptions = {}): ActiveHeadEntry<UseSeoMetaInput> {
|
||||||
|
const head = injectHead(options.nuxt)
|
||||||
|
return serverHeadSafe(input, { head, ...options }) as ActiveHeadEntry<UseHeadInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useServerSeoMeta (input: UseSeoMetaInput, options: NuxtUseHeadOptions = {}): ActiveHeadEntry<UseHeadInput> {
|
||||||
|
const head = injectHead(options.nuxt)
|
||||||
|
return serverSeoMeta(input, { head, ...options }) as ActiveHeadEntry<UseHeadInput>
|
||||||
|
}
|
@ -37,7 +37,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/pug": "2.0.10",
|
"@types/pug": "2.0.10",
|
||||||
"@unhead/schema": "2.0.0-alpha.6",
|
"@unhead/schema": "2.0.0-alpha.7",
|
||||||
"@vitejs/plugin-vue": "5.2.1",
|
"@vitejs/plugin-vue": "5.2.1",
|
||||||
"@vitejs/plugin-vue-jsx": "4.1.1",
|
"@vitejs/plugin-vue-jsx": "4.1.1",
|
||||||
"@vue/compiler-core": "3.5.13",
|
"@vue/compiler-core": "3.5.13",
|
||||||
|
@ -12,9 +12,9 @@ overrides:
|
|||||||
'@nuxt/vite-builder': workspace:*
|
'@nuxt/vite-builder': workspace:*
|
||||||
'@nuxt/webpack-builder': workspace:*
|
'@nuxt/webpack-builder': workspace:*
|
||||||
'@types/node': 22.10.7
|
'@types/node': 22.10.7
|
||||||
'@unhead/schema': 2.0.0-alpha.6
|
'@unhead/schema': 2.0.0-alpha.7
|
||||||
'@unhead/vue': 2.0.0-alpha.6
|
'@unhead/vue': 2.0.0-alpha.7
|
||||||
'@unhead/shared': 2.0.0-alpha.6
|
'@unhead/shared': 2.0.0-alpha.7
|
||||||
'@vue/compiler-core': 3.5.13
|
'@vue/compiler-core': 3.5.13
|
||||||
'@vue/compiler-dom': 3.5.13
|
'@vue/compiler-dom': 3.5.13
|
||||||
'@vue/shared': 3.5.13
|
'@vue/shared': 3.5.13
|
||||||
@ -31,7 +31,7 @@ overrides:
|
|||||||
typescript: 5.7.3
|
typescript: 5.7.3
|
||||||
ufo: 1.5.4
|
ufo: 1.5.4
|
||||||
unbuild: 3.3.1
|
unbuild: 3.3.1
|
||||||
unhead: 2.0.0-alpha.6
|
unhead: 2.0.0-alpha.7
|
||||||
unimport: 3.14.6
|
unimport: 3.14.6
|
||||||
vite: 6.0.9
|
vite: 6.0.9
|
||||||
vue: 3.5.13
|
vue: 3.5.13
|
||||||
@ -83,11 +83,11 @@ importers:
|
|||||||
specifier: 7.5.8
|
specifier: 7.5.8
|
||||||
version: 7.5.8
|
version: 7.5.8
|
||||||
'@unhead/schema':
|
'@unhead/schema':
|
||||||
specifier: 2.0.0-alpha.6
|
specifier: 2.0.0-alpha.7
|
||||||
version: 2.0.0-alpha.6
|
version: 2.0.0-alpha.7
|
||||||
'@unhead/vue':
|
'@unhead/vue':
|
||||||
specifier: 2.0.0-alpha.6
|
specifier: 2.0.0-alpha.7
|
||||||
version: 2.0.0-alpha.6(vue@3.5.13(typescript@5.7.3))
|
version: 2.0.0-alpha.7(vue@3.5.13(typescript@5.7.3))
|
||||||
'@vitest/coverage-v8':
|
'@vitest/coverage-v8':
|
||||||
specifier: 3.0.2
|
specifier: 3.0.2
|
||||||
version: 3.0.2(vitest@3.0.2(@types/node@22.10.7)(happy-dom@16.6.0)(jiti@2.4.2)(terser@5.32.0)(tsx@4.19.2)(yaml@2.6.1))
|
version: 3.0.2(vitest@3.0.2(@types/node@22.10.7)(happy-dom@16.6.0)(jiti@2.4.2)(terser@5.32.0)(tsx@4.19.2)(yaml@2.6.1))
|
||||||
@ -339,8 +339,8 @@ importers:
|
|||||||
specifier: 22.10.7
|
specifier: 22.10.7
|
||||||
version: 22.10.7
|
version: 22.10.7
|
||||||
'@unhead/vue':
|
'@unhead/vue':
|
||||||
specifier: 2.0.0-alpha.6
|
specifier: 2.0.0-alpha.7
|
||||||
version: 2.0.0-alpha.6(vue@3.5.13(typescript@5.7.3))
|
version: 2.0.0-alpha.7(vue@3.5.13(typescript@5.7.3))
|
||||||
'@vue/shared':
|
'@vue/shared':
|
||||||
specifier: 3.5.13
|
specifier: 3.5.13
|
||||||
version: 3.5.13
|
version: 3.5.13
|
||||||
@ -684,8 +684,8 @@ importers:
|
|||||||
specifier: 2.0.10
|
specifier: 2.0.10
|
||||||
version: 2.0.10
|
version: 2.0.10
|
||||||
'@unhead/schema':
|
'@unhead/schema':
|
||||||
specifier: 2.0.0-alpha.6
|
specifier: 2.0.0-alpha.7
|
||||||
version: 2.0.0-alpha.6
|
version: 2.0.0-alpha.7
|
||||||
'@vitejs/plugin-vue':
|
'@vitejs/plugin-vue':
|
||||||
specifier: 5.2.1
|
specifier: 5.2.1
|
||||||
version: 5.2.1(vite@6.0.9(@types/node@22.10.7)(jiti@2.4.2)(terser@5.32.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.7.3))
|
version: 5.2.1(vite@6.0.9(@types/node@22.10.7)(jiti@2.4.2)(terser@5.32.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.7.3))
|
||||||
@ -2842,14 +2842,14 @@ packages:
|
|||||||
'@ungap/structured-clone@1.2.0':
|
'@ungap/structured-clone@1.2.0':
|
||||||
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
|
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
|
||||||
|
|
||||||
'@unhead/schema@2.0.0-alpha.6':
|
'@unhead/schema@2.0.0-alpha.7':
|
||||||
resolution: {integrity: sha512-UjObgqdmWaKYwGk0KzK5snTdETikaU2A2Dqvz1ElS/zFNLsFmBIg4HwidvlA/oxF4/FvbZTlKsTutgiKWAHePA==}
|
resolution: {integrity: sha512-HzCoMwgex4RJRf37ASI6QBYsWTu1Q/de5TRDb0B8lgp/lXMnG3+gQQP2jXLv6h5Nvo7dT7opVxhgyj6zxXkVTA==}
|
||||||
|
|
||||||
'@unhead/shared@2.0.0-alpha.6':
|
'@unhead/shared@2.0.0-alpha.7':
|
||||||
resolution: {integrity: sha512-Uvp+Qzb/b8Me+VOqddjOK7aLFuBw/mk3Fx//ipB93FfjtXZ02WErDPTsOv+4aOK86JKG8CrxOkYosGcw8WceLw==}
|
resolution: {integrity: sha512-ttyCQUbY/0UQ3mEix3dKhk+5NL8vPp69zv25wY71nHXaLE3lrnGjOUjkETiE7XH2nflhnZdIslUumZADnl3Tjw==}
|
||||||
|
|
||||||
'@unhead/vue@2.0.0-alpha.6':
|
'@unhead/vue@2.0.0-alpha.7':
|
||||||
resolution: {integrity: sha512-VfSycV+tLTGpPKlihmQNdHo/cxvyvtUKnPRUFAB1KMHjf9xn5twq2fXetf2LYZtTQunKuqZ08kwlRt+pAvd3dg==}
|
resolution: {integrity: sha512-7Zt0RteYvl+rDlh+4HuELqaLM9SI+rTPuuZWftiml4c6gG96UrAnjq7d0gljFL1jfi2TsUwQJdjrLPEXNY8Ftw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: 3.5.13
|
vue: 3.5.13
|
||||||
|
|
||||||
@ -7432,8 +7432,8 @@ packages:
|
|||||||
unenv@1.10.0:
|
unenv@1.10.0:
|
||||||
resolution: {integrity: sha512-wY5bskBQFL9n3Eca5XnhH6KbUo/tfvkwm9OpcdCvLaeA7piBNbavbOKJySEwQ1V0RH6HvNlSAFRTpvTqgKRQXQ==}
|
resolution: {integrity: sha512-wY5bskBQFL9n3Eca5XnhH6KbUo/tfvkwm9OpcdCvLaeA7piBNbavbOKJySEwQ1V0RH6HvNlSAFRTpvTqgKRQXQ==}
|
||||||
|
|
||||||
unhead@2.0.0-alpha.6:
|
unhead@2.0.0-alpha.7:
|
||||||
resolution: {integrity: sha512-kLQLu+wMbFgdULGyeq6uCXuAPXi6XFlNFPQCsur1dLfNGBxoJptY/uACrLiB1ttc8EFSxYVm0JWn17urm/t1iA==}
|
resolution: {integrity: sha512-iaiL8eHACK8t+PXkeTsVgMs52CCqd9LFJplisRqFqqwspxzsTzpXPppmqizA0UZHtCG9P5rg8eEiDhHMQCzOtg==}
|
||||||
|
|
||||||
unicode-emoji-modifier-base@1.0.0:
|
unicode-emoji-modifier-base@1.0.0:
|
||||||
resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==}
|
resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==}
|
||||||
@ -9110,7 +9110,7 @@ snapshots:
|
|||||||
'@types/google.maps': 3.58.1
|
'@types/google.maps': 3.58.1
|
||||||
'@types/vimeo__player': 2.18.3
|
'@types/vimeo__player': 2.18.3
|
||||||
'@types/youtube': 0.1.0
|
'@types/youtube': 0.1.0
|
||||||
'@unhead/vue': 2.0.0-alpha.6(vue@3.5.13(typescript@5.7.3))
|
'@unhead/vue': 2.0.0-alpha.7(vue@3.5.13(typescript@5.7.3))
|
||||||
'@vueuse/core': 11.1.0(vue@3.5.13(typescript@5.7.3))
|
'@vueuse/core': 11.1.0(vue@3.5.13(typescript@5.7.3))
|
||||||
consola: 3.4.0
|
consola: 3.4.0
|
||||||
defu: 6.1.4
|
defu: 6.1.4
|
||||||
@ -10066,22 +10066,22 @@ snapshots:
|
|||||||
|
|
||||||
'@ungap/structured-clone@1.2.0': {}
|
'@ungap/structured-clone@1.2.0': {}
|
||||||
|
|
||||||
'@unhead/schema@2.0.0-alpha.6':
|
'@unhead/schema@2.0.0-alpha.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
hookable: 5.5.3
|
hookable: 5.5.3
|
||||||
zhead: 2.2.4
|
zhead: 2.2.4
|
||||||
|
|
||||||
'@unhead/shared@2.0.0-alpha.6':
|
'@unhead/shared@2.0.0-alpha.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@unhead/schema': 2.0.0-alpha.6
|
'@unhead/schema': 2.0.0-alpha.7
|
||||||
packrup: 0.1.2
|
packrup: 0.1.2
|
||||||
|
|
||||||
'@unhead/vue@2.0.0-alpha.6(vue@3.5.13(typescript@5.7.3))':
|
'@unhead/vue@2.0.0-alpha.7(vue@3.5.13(typescript@5.7.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@unhead/schema': 2.0.0-alpha.6
|
'@unhead/schema': 2.0.0-alpha.7
|
||||||
'@unhead/shared': 2.0.0-alpha.6
|
'@unhead/shared': 2.0.0-alpha.7
|
||||||
hookable: 5.5.3
|
hookable: 5.5.3
|
||||||
unhead: 2.0.0-alpha.6
|
unhead: 2.0.0-alpha.7
|
||||||
vue: 3.5.13(typescript@5.7.3)
|
vue: 3.5.13(typescript@5.7.3)
|
||||||
|
|
||||||
'@unocss/astro@0.62.4(rollup@4.31.0)(vite@6.0.9(@types/node@22.10.7)(jiti@2.4.2)(terser@5.32.0)(tsx@4.19.2)(yaml@2.6.1))':
|
'@unocss/astro@0.62.4(rollup@4.31.0)(vite@6.0.9(@types/node@22.10.7)(jiti@2.4.2)(terser@5.32.0)(tsx@4.19.2)(yaml@2.6.1))':
|
||||||
@ -15630,10 +15630,10 @@ snapshots:
|
|||||||
node-fetch-native: 1.6.4
|
node-fetch-native: 1.6.4
|
||||||
pathe: 1.1.2
|
pathe: 1.1.2
|
||||||
|
|
||||||
unhead@2.0.0-alpha.6:
|
unhead@2.0.0-alpha.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@unhead/schema': 2.0.0-alpha.6
|
'@unhead/schema': 2.0.0-alpha.7
|
||||||
'@unhead/shared': 2.0.0-alpha.6
|
'@unhead/shared': 2.0.0-alpha.7
|
||||||
hookable: 5.5.3
|
hookable: 5.5.3
|
||||||
|
|
||||||
unicode-emoji-modifier-base@1.0.0: {}
|
unicode-emoji-modifier-base@1.0.0: {}
|
||||||
|
2
test/fixtures/basic-types/types.ts
vendored
2
test/fixtures/basic-types/types.ts
vendored
@ -405,7 +405,7 @@ describe('head', () => {
|
|||||||
link: computed(() => []),
|
link: computed(() => []),
|
||||||
meta: [
|
meta: [
|
||||||
{ key: 'key', name: 'description', content: 'some description ' },
|
{ key: 'key', name: 'description', content: 'some description ' },
|
||||||
computed(() => ({ key: 'key', name: 'description', content: 'some description ' })),
|
() => ({ key: 'key', name: 'description', content: 'some description ' }),
|
||||||
],
|
],
|
||||||
titleTemplate: (titleChunk) => {
|
titleTemplate: (titleChunk) => {
|
||||||
return titleChunk ? `${titleChunk} - Site Title` : 'Site Title'
|
return titleChunk ? `${titleChunk} - Site Title` : 'Site Title'
|
||||||
|
@ -32,6 +32,9 @@
|
|||||||
],
|
],
|
||||||
"#app/*": [
|
"#app/*": [
|
||||||
"./packages/nuxt/src/app/*"
|
"./packages/nuxt/src/app/*"
|
||||||
|
],
|
||||||
|
"#unhead/composables": [
|
||||||
|
"./packages/nuxt/src/head/runtime/composables/v4"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user