diff --git a/README.md b/README.md
index 68427c68..63ce7bbb 100644
--- a/README.md
+++ b/README.md
@@ -223,7 +223,7 @@ export default defineNuxtConfig({
})
```
-This `useHead` composable uses `@vueuse/head` under the hood (rather than `vue-meta`) to manipulate your `
`.
+This `useHead` composable uses `@unhead/vue` under the hood (rather than `vue-meta`) to manipulate your ``.
Accordingly, we recommend not to use both the native Nuxt 2 `head()` properties as well as `useHead`, as they may conflict.
For more information on how to use this composable, see [the docs](https://nuxt.com/docs/getting-started/seo-meta#seo-and-meta).
diff --git a/packages/bridge-schema/build.config.ts b/packages/bridge-schema/build.config.ts
index be771c31..e698b5c1 100644
--- a/packages/bridge-schema/build.config.ts
+++ b/packages/bridge-schema/build.config.ts
@@ -40,7 +40,6 @@ export default defineBuildConfig({
'vue-meta',
'vue-router',
'vue-bundle-renderer',
- '@vueuse/head',
'vue',
'hookable',
'nitropack',
@@ -57,6 +56,7 @@ export default defineBuildConfig({
'postcss',
'consola',
'ignore',
+ '@unhead/schema',
// Implicit
'@vue/compiler-core',
'@vue/shared',
diff --git a/packages/bridge-schema/package.json b/packages/bridge-schema/package.json
index de6aea1d..4e7430ee 100644
--- a/packages/bridge-schema/package.json
+++ b/packages/bridge-schema/package.json
@@ -17,7 +17,7 @@
"devDependencies": {
"@types/lodash.template": "^4.5.1",
"@types/semver": "^7.5.0",
- "@vueuse/head": "^1.1.26",
+ "@unhead/schema": "^1.1.27",
"nitropack": "^2.4.1",
"unbuild": "latest",
"vite": "~4.3.9"
diff --git a/packages/bridge-schema/src/config/app.ts b/packages/bridge-schema/src/config/app.ts
index c8c5f5e9..c1de9d10 100644
--- a/packages/bridge-schema/src/config/app.ts
+++ b/packages/bridge-schema/src/config/app.ts
@@ -2,12 +2,12 @@ import { existsSync, readdirSync } from 'node:fs'
import { defineUntypedSchema } from 'untyped'
import { resolve, join } from 'pathe'
import defu from 'defu'
+import { AppHeadMetaObject } from '../types/head'
export default defineUntypedSchema({
vue: {
/**
* Properties that will be set directly on `Vue.config` for vue@2.
- *
* @see [vue@2 Documentation](https://v2.vuejs.org/v2/api/#Global-Config)
* @type {typeof import('vue/types/vue').VueConfiguration}
*/
@@ -24,18 +24,75 @@ export default defineUntypedSchema({
app: {
/**
* The folder name for the built site assets, relative to `baseURL` (or `cdnURL` if set).
- *
* @deprecated - use `buildAssetsDir` instead
*/
assetsPath: {
$resolve: async (val, get) => val ?? (await get('buildAssetsDir'))
+ },
+ /**
+ * Set default configuration for `` on every page.
+ * @example
+ * ```js
+ * app: {
+ * head: {
+ * meta: [
+ * //
+ * { name: 'viewport', content: 'width=device-width, initial-scale=1' }
+ * ],
+ * script: [
+ * //
+ * { src: 'https://awesome-lib.js' }
+ * ],
+ * link: [
+ * //
+ * { rel: 'stylesheet', href: 'https://awesome-lib.css' }
+ * ],
+ * // please note that this is an area that is likely to change
+ * style: [
+ * //
+ * { children: ':root { color: red }', type: 'text/css' }
+ * ],
+ * noscript: [
+ * //
+ * { children: 'JavaScript is required' }
+ * ]
+ * }
+ * }
+ * ```
+ * @type {typeof import('../src/types/head').AppHeadMetaObject}
+ */
+ head: {
+ $resolve: async (val, get) => {
+ const resolved: Required = defu(val, await get('meta'), {
+ meta: [],
+ link: [],
+ style: [],
+ script: [],
+ noscript: []
+ })
+
+ // provides default charset and viewport if not set
+ if (!resolved.meta.find(m => m.charset)?.charset) {
+ resolved.meta.unshift({ charset: resolved.charset || 'utf-8' })
+ }
+ if (!resolved.meta.find(m => m.name === 'viewport')?.content) {
+ resolved.meta.unshift({ name: 'viewport', content: resolved.viewport || 'width=device-width, initial-scale=1' })
+ }
+
+ resolved.meta = resolved.meta.filter(Boolean)
+ resolved.link = resolved.link.filter(Boolean)
+ resolved.style = resolved.style.filter(Boolean)
+ resolved.script = resolved.script.filter(Boolean)
+ resolved.noscript = resolved.noscript.filter(Boolean)
+
+ return resolved
+ }
}
},
/**
* The path to an HTML template file for rendering Nuxt responses.
* Uses `/app.html` if it exists, or the Nuxt's default template if not.
- *
* @example
* ```html
*
@@ -75,7 +132,6 @@ export default defineUntypedSchema({
/**
* Options to pass directly to `vue-meta`.
- *
* @see [documentation](https://vue-meta.nuxtjs.org/api/#plugin-options).
* @type {typeof import('vue-meta').VueMetaOptions}
*/
@@ -83,7 +139,6 @@ export default defineUntypedSchema({
/**
* Set default configuration for `` on every page.
- *
* @see [documentation](https://vue-meta.nuxtjs.org/api/#metainfo-properties) for specifics.
* @type {typeof import('vue-meta').MetaInfo}
*/
@@ -123,7 +178,6 @@ export default defineUntypedSchema({
* You may want to extend plugins or change their order. For this, you can pass
* a function using `extendPlugins`. It accepts an array of plugin objects and
* should return an array of plugin objects.
- *
* @type {(plugins: Array<{ src: string, mode?: 'client' | 'server' }>) => Array<{ src: string, mode?: 'client' | 'server' }>}
*/
extendPlugins: null,
@@ -132,7 +186,6 @@ export default defineUntypedSchema({
* An object where each key name maps to a path to a layout .vue file.
*
* Normally, there is no need to configure this directly.
- *
* @type {Record}
*/
layouts: {},
@@ -141,7 +194,6 @@ export default defineUntypedSchema({
* Set a custom error page layout.
*
* Normally, there is no need to configure this directly.
- *
* @type {string}
*/
ErrorPage: null,
@@ -206,7 +258,6 @@ export default defineUntypedSchema({
*
* You can either pass a string (the transition name) or an object with properties to bind
* to the `` component that will wrap your pages.
- *
* @see [vue@2 documentation](https://v2.vuejs.org/v2/guide/transitions.html)
* @see [vue@3 documentation](https://vuejs.org/guide/built-ins/transition-group.html#enter-leave-transitions)
*/
@@ -229,7 +280,6 @@ export default defineUntypedSchema({
*
* You can either pass a string (the transition name) or an object with properties to bind
* to the `` component that will wrap your layouts.
- *
* @see [vue@2 documentation](https://v2.vuejs.org/v2/guide/transitions.html)
*/
layoutTransition: {
diff --git a/packages/bridge-schema/src/types/head.ts b/packages/bridge-schema/src/types/head.ts
new file mode 100644
index 00000000..70f54cf0
--- /dev/null
+++ b/packages/bridge-schema/src/types/head.ts
@@ -0,0 +1,31 @@
+import type { Head, MergeHead } from '@unhead/schema'
+
+/** @deprecated Extend types from `@unhead/schema` directly. This may be removed in a future minor version. */
+export interface HeadAugmentations extends MergeHead {
+ // runtime type modifications
+ base?: {}
+ link?: {}
+ meta?: {}
+ style?: {}
+ script?: {}
+ noscript?: {}
+ htmlAttrs?: {}
+ bodyAttrs?: {}
+}
+
+export type MetaObjectRaw = Head
+export type MetaObject = MetaObjectRaw
+
+export type AppHeadMetaObject = MetaObjectRaw & {
+ /**
+ * The character encoding in which the document is encoded => ``
+ * @default `'utf-8'`
+ */
+ charset?: string
+ /**
+ * Configuration of the viewport (the area of the window in which web content can be seen),
+ * mapped to => ``
+ * @default `'width=device-width, initial-scale=1'`
+ */
+ viewport?: string
+}
diff --git a/packages/bridge/build.config.ts b/packages/bridge/build.config.ts
index 8943b9ea..a7ead832 100644
--- a/packages/bridge/build.config.ts
+++ b/packages/bridge/build.config.ts
@@ -10,6 +10,7 @@ export default defineBuildConfig({
'webpack',
'vite',
'vue',
- 'vue-meta'
+ 'vue-meta',
+ '@unhead/vue'
]
})
diff --git a/packages/bridge/package.json b/packages/bridge/package.json
index f2c42b6b..c0e652fc 100644
--- a/packages/bridge/package.json
+++ b/packages/bridge/package.json
@@ -29,6 +29,8 @@
"@nuxt/postcss8": "^1.1.3",
"@nuxt/schema": "3.5.3",
"@nuxt/ui-templates": "^1.2.0",
+ "@unhead/ssr": "^1.1.27",
+ "@unhead/vue": "^1.1.27",
"@vitejs/plugin-legacy": "^4.0.4",
"@vitejs/plugin-vue2": "^2.2.0",
"acorn": "^8.9.0",
@@ -79,7 +81,6 @@
"@types/fs-extra": "^9.0.13",
"@types/hash-sum": "^1.0.0",
"@types/node-fetch": "^3.0.2",
- "@vueuse/head": "^1.1.26",
"nuxt": "^2.17.0",
"unbuild": "1.2.1",
"vue": "^2.7.14",
diff --git a/packages/bridge/src/head.ts b/packages/bridge/src/head.ts
index 4c601342..04a36ddb 100644
--- a/packages/bridge/src/head.ts
+++ b/packages/bridge/src/head.ts
@@ -1,9 +1,11 @@
import { resolve } from 'pathe'
-import { addPlugin, addTemplate, defineNuxtModule, tryResolveModule } from '@nuxt/kit'
+import { addComponent, addImportsSources, addPlugin, addTemplate, defineNuxtModule } from '@nuxt/kit'
import { defu } from 'defu'
import type { MetaObject } from '@nuxt/schema'
import { distDir } from './dirs'
+const components = ['NoScript', 'Link', 'Base', 'Title', 'Meta', 'Style', 'Head', 'Html', 'Body']
+
export default defineNuxtModule({
meta: {
name: 'meta'
@@ -15,13 +17,26 @@ export default defineNuxtModule({
setup (options, nuxt) {
const runtimeDir = nuxt.options.alias['#head'] || resolve(distDir, 'head/runtime')
- // Transpile @nuxt/meta and @vueuse/head
- nuxt.options.build.transpile.push('@vueuse/head')
+ // Transpile @unhead/vue and @unhead/ssr
nuxt.options.build.transpile.push('unhead')
// Add #head alias
nuxt.options.alias['#head'] = runtimeDir
+ // Register components
+ const componentsPath = resolve(runtimeDir, 'components')
+ for (const componentName of components) {
+ addComponent({
+ name: componentName,
+ filePath: componentsPath,
+ export: componentName,
+ // built-in that we do not expect the user to override
+ priority: 10,
+ // kebab case version of these tags is not valid
+ kebabName: componentName
+ })
+ }
+
// Global meta -for Bridge, this is necessary to repeat here
// and in packages/schema/src/config/_app.ts
const globalMeta: MetaObject = defu(nuxt.options.app.head, {
@@ -35,14 +50,24 @@ export default defineNuxtModule({
getContents: () => 'export default ' + JSON.stringify({ globalMeta, mixinKey: 'setup' })
})
- if (!tryResolveModule('@vueuse/head')) {
- console.warn('[bridge] Could not find `@vueuse/head`. You may need to install it.')
- }
+ addImportsSources({
+ from: '@unhead/vue',
+ // hard-coded for now we so don't support auto-imports on the deprecated composables
+ imports: [
+ 'injectHead',
+ 'useHead',
+ 'useSeoMeta',
+ 'useHeadSafe',
+ 'useServerHead',
+ 'useServerSeoMeta',
+ 'useServerHeadSafe'
+ ]
+ })
// Add generic plugin
addPlugin({ src: resolve(runtimeDir, 'plugin') })
- // Add library specific plugin
- addPlugin({ src: resolve(runtimeDir, 'vueuse-head.plugin') })
+ // Add library-specific plugin
+ addPlugin({ src: resolve(runtimeDir, 'plugins/unhead') })
}
})
diff --git a/packages/bridge/src/imports/presets.ts b/packages/bridge/src/imports/presets.ts
index 5bac6869..0b453136 100644
--- a/packages/bridge/src/imports/presets.ts
+++ b/packages/bridge/src/imports/presets.ts
@@ -5,7 +5,6 @@ export const commonPresets: InlinePreset[] = [
defineUnimportPreset({
from: '#head',
imports: [
- 'useHead',
'useMeta'
]
}),
diff --git a/packages/bridge/src/runtime/app.plugin.mjs b/packages/bridge/src/runtime/app.plugin.mjs
index 40bc0350..43bfe915 100644
--- a/packages/bridge/src/runtime/app.plugin.mjs
+++ b/packages/bridge/src/runtime/app.plugin.mjs
@@ -41,7 +41,7 @@ export default async (ctx, inject) => {
provide: inject,
unmount: () => { },
use (vuePlugin) {
- runOnceWith(vuePlugin, () => vuePlugin.install(this))
+ runOnceWith(vuePlugin, () => Vue.use(vuePlugin))
},
version
},
diff --git a/packages/bridge/src/runtime/head/components.ts b/packages/bridge/src/runtime/head/components.ts
index 651b77fb..72e30096 100644
--- a/packages/bridge/src/runtime/head/components.ts
+++ b/packages/bridge/src/runtime/head/components.ts
@@ -1,6 +1,6 @@
import { defineComponent } from 'vue'
import type { SetupContext } from 'vue'
-import { useHead } from './composables'
+import { useHead } from '@unhead/vue'
type Props = Readonly>
@@ -58,10 +58,36 @@ const globalProps = {
translate: String
}
+//