feat: @nuxt/meta module for head rendering (#179)

Co-authored-by: Anthony Fu <hi@antfu.me>
Co-authored-by: Pooya Parsa <pyapar@gmail.com>
This commit is contained in:
Daniel Roe 2021-07-15 12:28:04 +01:00 committed by GitHub
parent 0bbbddecba
commit b263b4f930
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 605 additions and 133 deletions

View File

@ -11,6 +11,7 @@
"rules": {
"no-console": "off",
"vue/one-component-per-file": "off",
"vue/require-default-prop": "off",
"jsdoc/require-jsdoc": "off",
"jsdoc/require-param": "off",
"jsdoc/require-returns": "off",

View File

@ -1 +1,75 @@
# Meta Tags
You can customize the meta tags for your site through several different ways:
## `useMeta` Composable
Within your `setup()` function you can call `useMeta` with an object of meta properties with keys corresponding to meta tags: `title`, `base`, `script`, `style`, `meta` and `link`, as well as `htmlAttrs` and `bodyAttrs`. Alternatively, you can pass a function returning the object for reactive metadata.
For example:
```ts
import { ref } from 'vue'
import { useMeta } from '@nuxt/app'
export default {
setup () {
useMeta({
bodyAttrs: {
class: 'test'
}
})
}
}
```
## `head` Component Property
If you would prefer to use the options syntax, you can do exactly the same thing within your component options with an object or function called `head`. For example:
```js
export default {
head: {
script: [
{
async: true,
src: 'https://myscript.com/script.js'
}
]
}
}
```
**Note that `head()` does not have access to the component instance.**
## Meta Components
Nuxt provides `<Title>`, `<Base>`, `<Script>`, `<Style>`, `<Meta>`, `<Link>`, `<Body>` and `<Head>` components so that you can interact directly with your metadata within your component template.
Because these component names match native HTML elements, it is very important that they are capitalized in the template.
`<Head>` and `<Body>` can accept nested meta tags (for aesthetic reasons) but this has no effect on _where_ the nested meta tags are rendered in the final HTML.
For example:
```vue
<template>
<div>
Hello World
<Head :lang="dynamic > 50 ? 'en-GB' : 'en-US'">
<Title>{{ dynamic }} title</Title>
<Meta name="description" :content="`My page's ${dynamic} description`" />
<Link rel="preload" href="/test.txt" as="script" />
</Head>
<button class="blue" @click="dynamic = Math.random() * 100">
Click me
</button>
</div>
</template>
<script>
export default {
data: () => ({ dynamic: 49 })
}
</script>
```

34
examples/meta/app.vue Normal file
View File

@ -0,0 +1,34 @@
<template>
<div>
Hello World
<Head :lang="'' + dynamic">
<Title>{{ dynamic }} title</Title>
<Meta name="description" :content="`My page's ${dynamic} description`" />
<Link rel="preload" href="/test.txt" as="script" />
</Head>
<button class="blue" @click="dynamic = Math.random() * 100">
Clickme
</button>
</div>
</template>
<script lang="ts">
import { ref } from 'vue'
import { useMeta } from '@nuxt/app'
export default {
setup () {
useMeta({
bodyAttrs: {
class: 'test'
}
})
return { dynamic: ref(49) }
},
head: {
title: 'Another title'
}
}
</script>

View File

@ -0,0 +1,4 @@
import { defineNuxtConfig } from '@nuxt/kit'
export default defineNuxtConfig({
})

View File

@ -0,0 +1,12 @@
{
"name": "example-meta",
"private": true,
"devDependencies": {
"nuxt3": "latest"
},
"scripts": {
"dev": "nu dev",
"build": "nu build",
"start": "node .output/server"
}
}

View File

@ -6,7 +6,6 @@ export default defineBuildConfig({
{ input: 'src/', name: 'app' }
],
dependencies: [
'@vueuse/head',
'ohmyfetch',
'vue-router',
'vuex5'

View File

@ -20,7 +20,6 @@
"prepack": "unbuild"
},
"dependencies": {
"@vueuse/head": "^0.6.0",
"hookable": "^4.4.1",
"ohmyfetch": "^0.2.0",
"upath": "^2.0.1",
@ -28,6 +27,9 @@
"vue-router": "^4.0.10",
"vuex5": "^0.5.0-testing.3"
},
"peerDependencies": {
"@nuxt/meta": "^0.1.0"
},
"devDependencies": {
"unbuild": "^0.3.2"
}

View File

@ -1,2 +1,5 @@
export * from './nuxt'
export * from './composables'
// @ts-ignore
export * from '@nuxt/meta'

View File

@ -3,6 +3,15 @@ import Hookable from 'hookable'
import { defineGetter } from './utils'
import { legacyPlugin, LegacyContext } from './legacy'
type NuxtMeta = {
htmlAttrs?: string
headAttrs?: string
bodyAttrs?: string
headTags?: string
bodyPrepend?: string
bodyScripts?: string
}
export interface Nuxt {
app: App
globalName: string
@ -16,7 +25,9 @@ export interface Nuxt {
_asyncDataPromises?: Record<string, Promise<any>>
_legacyContext?: LegacyContext
ssrContext?: Record<string, any>
ssrContext?: Record<string, any> & {
renderMeta: () => Promise<NuxtMeta> | NuxtMeta
}
payload: {
serverRendered?: true
data?: Record<string, any>

View File

@ -1,56 +0,0 @@
import { defineComponent } from '@vue/runtime-core'
import { useHead, HeadObject } from '@vueuse/head'
type MappedProps<T extends Record<string, any>> = {
[P in keyof T]: { type: () => T[P] }
}
const props: MappedProps<HeadObject> = {
base: { type: Object },
bodyAttrs: { type: Object },
htmlAttrs: { type: Object },
link: { type: Array },
meta: { type: Array },
script: { type: Array },
style: { type: Array },
title: { type: String }
}
export const Head = defineComponent({
props,
setup (props, { slots }) {
useHead(() => props)
return () => slots.default?.()
}
})
const createHeadComponent = (prop: keyof typeof props, isArray = false) =>
defineComponent({
setup (_props, { attrs, slots }) {
useHead(() => ({
[prop]: isArray ? [attrs] : attrs
}))
return () => slots.default?.()
}
})
const createHeadComponentFromSlot = (prop: keyof typeof props) =>
defineComponent({
setup (_props, { slots }) {
useHead(() => ({
[prop]: slots.default?.()[0]?.children
}))
return () => null
}
})
export const Html = createHeadComponent('htmlAttrs')
export const Body = createHeadComponent('bodyAttrs')
export const Title = createHeadComponentFromSlot('title')
export const Meta = createHeadComponent('meta', true)
export const Link = createHeadComponent('link', true)
export const Script = createHeadComponent('script', true)
export const Style = createHeadComponent('style', true)

View File

@ -1,23 +0,0 @@
import { createHead, renderHeadToString } from '@vueuse/head'
import { defineNuxtPlugin } from '@nuxt/app'
import { Head, Html, Body, Title, Meta, Link, Script, Style } from './head'
export default defineNuxtPlugin((nuxt) => {
const { app, ssrContext } = nuxt
const head = createHead()
app.use(head)
app.component('NuxtHead', Head)
app.component('NuxtHtml', Html)
app.component('NuxtBody', Body)
app.component('NuxtTitle', Title)
app.component('NuxtMeta', Meta)
app.component('NuxtHeadLink', Link)
app.component('NuxtScript', Script)
app.component('NuxtStyle', Style)
if (process.server) {
ssrContext.head = () => renderHeadToString(head)
}
})

View File

@ -0,0 +1,14 @@
import { defineBuildConfig } from 'unbuild'
export default defineBuildConfig({
declaration: true,
entries: [
'src/module',
{ input: 'src/runtime/', outDir: 'dist/runtime', format: 'esm' }
],
externals: [
'@vue/reactivity',
'@vue/shared',
'@vueuse/head'
]
})

1
packages/meta/module.js Normal file
View File

@ -0,0 +1 @@
module.exports = require('./dist/module')

View File

@ -0,0 +1,34 @@
{
"name": "@nuxt/meta",
"version": "0.1.0",
"repository": "nuxt/framework",
"license": "MIT",
"exports": {
".": "./dist/runtime/index.mjs",
"./module": {
"import": "./dist/module.mjs",
"require": "./dist/module.js"
}
},
"types": "./dist/runtime/index.d.ts",
"files": [
"dist"
],
"scripts": {
"prepack": "unbuild"
},
"dependencies": {
"@nuxt/kit": "^0.6.4",
"@vueuse/head": "^0.6.0",
"upath": "^2.0.1"
},
"devDependencies": {
"unbuild": "^0.3.1",
"vue-meta": "3.0.0-alpha.9"
},
"peerDependencies": {
"@vue/reactivity": "3.1.1",
"@vue/shared": "3.1.1",
"vue": "3.1.1"
}
}

View File

@ -0,0 +1,17 @@
import { resolve } from 'upath'
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
name: 'meta',
setup (_options, nuxt) {
const runtimeDir = resolve(__dirname, 'runtime')
nuxt.options.build.transpile.push('@nuxt/meta', runtimeDir)
nuxt.options.alias['@nuxt/meta'] = resolve(runtimeDir, 'index')
nuxt.hook('app:resolve', (app) => {
app.plugins.push({ src: resolve(runtimeDir, 'vueuse-head') })
app.plugins.push({ src: resolve(runtimeDir, 'meta') })
})
}
})

View File

@ -0,0 +1,209 @@
import { defineComponent, SetupContext } from 'vue'
import { useMeta } from './index'
type Props = Readonly<Record<string, any>>
const removeUndefinedProps = (props: Props) =>
Object.fromEntries(Object.entries(props).filter(([_key, value]) => value !== undefined))
const setupForUseMeta = (metaFactory: (props: Props, ctx: SetupContext) => Record<string, any>, renderChild?: boolean) => (props: Props, ctx: SetupContext) => {
useMeta(() => metaFactory(removeUndefinedProps(props), ctx))
return () => renderChild ? ctx.slots.default?.() : null
}
const globalProps = {
accesskey: String,
autocapitalize: String,
autofocus: {
type: Boolean,
default: undefined
},
class: String,
contenteditable: {
type: Boolean,
default: undefined
},
contextmenu: String,
dir: String,
draggable: {
type: Boolean,
default: undefined
},
enterkeyhint: String,
exportparts: String,
hidden: {
type: Boolean,
default: undefined
},
id: String,
inputmode: String,
is: String,
itemid: String,
itemprop: String,
itemref: String,
itemscope: String,
itemtype: String,
lang: String,
nonce: String,
part: String,
slot: String,
spellcheck: {
type: Boolean,
default: undefined
},
style: String,
tabindex: String,
title: String,
translate: String
}
// <script>
export const Script = defineComponent({
name: 'Script',
props: {
...globalProps,
async: Boolean,
crossorigin: {
type: [Boolean, String],
default: undefined
},
defer: Boolean,
integrity: String,
nomodule: Boolean,
nonce: String,
referrerpolicy: String,
src: String,
type: String,
/** @deprecated **/
charset: String,
/** @deprecated **/
language: String
},
setup: setupForUseMeta(script => ({
script: [script]
}))
})
// <link>
export const Link = defineComponent({
name: 'Link',
props: {
...globalProps,
as: String,
crossorigin: String,
disabled: Boolean,
href: String,
hreflang: String,
imagesizes: String,
imagesrcset: String,
integrity: String,
media: String,
prefetch: {
type: Boolean,
default: undefined
},
referrerpolicy: String,
rel: String,
sizes: String,
title: String,
type: String,
/** @deprecated **/
methods: String,
/** @deprecated **/
target: String
},
setup: setupForUseMeta(link => ({
link: [link]
}))
})
// <base>
export const Base = defineComponent({
name: 'Base',
props: {
...globalProps,
href: String,
target: String
},
setup: setupForUseMeta(base => ({
base
}))
})
// <title>
export const Title = defineComponent({
name: 'Title',
setup: setupForUseMeta((_, { slots }) => {
const title = slots.default()?.[0]?.children || null
if (process.dev && title && typeof title !== 'string') {
console.error('<Title> can only take a string in its default slot.')
}
return {
title
}
})
})
// <meta>
export const Meta = defineComponent({
name: 'Meta',
props: {
...globalProps,
charset: String,
content: String,
httpEquiv: String,
name: String
},
setup: setupForUseMeta(meta => ({
meta: [meta]
}))
})
// <style>
export const Style = defineComponent({
name: 'Style',
props: {
...globalProps,
type: String,
media: String,
nonce: String,
title: String,
/** @deprecated **/
scoped: {
type: Boolean,
default: undefined
}
},
setup: setupForUseMeta((props, { slots }) => {
const style = { ...props }
const textContent = slots.default?.()?.[0]?.children
if (textContent) {
if (process.dev && typeof textContent !== 'string') {
console.error('<Style> can only take a string in its default slot.')
}
style.content = textContent
}
return {
style: [style]
}
})
})
// <head>
export const Head = defineComponent({
name: 'Head',
props: {
...globalProps,
manifest: String,
version: String,
xmlns: String
},
setup: setupForUseMeta(headAttrs => ({ headAttrs }), true)
})
// <body>
export const Body = defineComponent({
name: 'Body',
props: globalProps,
setup: setupForUseMeta(bodyAttrs => ({ bodyAttrs }), true)
})

View File

@ -0,0 +1,25 @@
// import { useMeta as useVueMeta } from 'vue-meta'
import { isFunction } from '@vue/shared'
import { computed, ComputedGetter } from '@vue/reactivity'
import { useHead } from '@vueuse/head'
/**
* 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 useMeta (meta: Record<string, any> | ComputedGetter<any>) {
// TODO: refine @nuxt/meta API
// At the moment we force all interaction to happen through passing in
// the meta object or function that returns a meta object.
const source = isFunction(meta) ? computed(meta) : meta
// `vue-meta`
// useVueMeta(source)
// `@vueuse/head`
useHead(source)
}

View File

@ -0,0 +1 @@
export * from './composables'

View File

@ -0,0 +1,19 @@
import { getCurrentInstance } from 'vue'
import { defineNuxtPlugin } from '@nuxt/app'
import * as Components from './components'
import { useMeta } from './index'
export default defineNuxtPlugin((nuxt) => {
nuxt.app.mixin({
created () {
const instance = getCurrentInstance()
if (!instance?.type || !('head' in instance.type)) { return }
useMeta((instance.type as any).head)
}
})
for (const name in Components) {
nuxt.app.component(name, Components[name])
}
})

View File

@ -0,0 +1,35 @@
import { defineNuxtPlugin } from '@nuxt/app'
import { createApp } from 'vue'
import { createMetaManager } from 'vue-meta'
export default defineNuxtPlugin((nuxt) => {
const manager = createMetaManager(process.server)
nuxt.app.use(manager)
if (process.client) {
const teleportTarget = document.createElement('div')
teleportTarget.id = 'head-target'
document.body.appendChild(teleportTarget)
createApp({ render: () => manager.render({}) }).mount('#head-target')
}
if (process.server) {
nuxt.ssrContext.renderMeta = async () => {
const { renderMetaToString } = await import('vue-meta/ssr')
nuxt.ssrContext.teleports = nuxt.ssrContext.teleports || {}
await renderMetaToString(nuxt.app, nuxt.ssrContext)
return {
htmlAttrs: nuxt.ssrContext.teleports.htmlAttrs || '',
headAttrs: nuxt.ssrContext.teleports.headAttrs || '',
bodyAttrs: nuxt.ssrContext.teleports.bodyAttrs || '',
headTags: nuxt.ssrContext.teleports.head || '',
bodyPrepend: nuxt.ssrContext.teleports['body-prepend'] || '',
bodyScripts: nuxt.ssrContext.teleports.body || ''
}
}
}
})

View File

@ -0,0 +1,12 @@
import { createHead, renderHeadToString } from '@vueuse/head'
import { defineNuxtPlugin } from '@nuxt/app'
export default defineNuxtPlugin((nuxt) => {
const head = createHead()
nuxt.app.use(head)
if (process.server) {
nuxt.ssrContext.renderMeta = () => renderHeadToString(head)
}
})

View File

@ -59,6 +59,12 @@ export default function nuxt2CompatModule () {
src: resolve(nitroContext._internal.runtimeDir, 'app/nitro.client.mjs')
})
// Nitro server plugin (for vue-meta)
this.addPlugin({
fileName: 'nitro-compat.server.js',
src: resolve(nitroContext._internal.runtimeDir, 'app/nitro-compat.server.js')
})
// Fix module resolution
nuxt.hook('webpack:config', (configs) => {
for (const config of configs) {

View File

@ -0,0 +1,27 @@
export default ({ ssrContext }) => {
ssrContext.renderMeta = () => {
const meta = ssrContext.meta.inject({
isSSR: ssrContext.nuxt.serverRendered,
ln: process.env.NODE_ENV === 'development'
})
return {
htmlAttrs: meta.htmlAttrs.text(),
headAttrs: meta.headAttrs.text(),
headTags:
meta.title.text() + meta.base.text() +
meta.meta.text() + meta.link.text() +
meta.style.text() + meta.script.text() +
meta.noscript.text(),
bodyAttrs: meta.bodyAttrs.text(),
bodyScriptsPrepend:
meta.meta.text({ pbody: true }) + meta.link.text({ pbody: true }) +
meta.style.text({ pbody: true }) + meta.script.text({ pbody: true }) +
meta.noscript.text({ pbody: true }),
bodyScripts:
meta.meta.text({ body: true }) + meta.link.text({ body: true }) +
meta.style.text({ body: true }) + meta.script.text({ body: true }) +
meta.noscript.text({ body: true })
}
}
}

View File

@ -68,7 +68,7 @@ export async function renderMiddleware (req, res) {
data = renderPayload(payload, url)
res.setHeader('Content-Type', 'text/javascript;charset=UTF-8')
} else {
data = renderHTML(payload, rendered, ssrContext)
data = await renderHTML(payload, rendered, ssrContext)
res.setHeader('Content-Type', 'text/html;charset=UTF-8')
}
@ -77,56 +77,30 @@ export async function renderMiddleware (req, res) {
res.end(data, 'utf-8')
}
function renderHTML (payload, rendered, ssrContext) {
async function renderHTML (payload, rendered, ssrContext) {
const state = `<script>window.__NUXT__=${devalue(payload)}</script>`
const _html = rendered.html
const html = rendered.html
const meta = {
htmlAttrs: '',
bodyAttrs: '',
headAttrs: '',
headTags: '',
bodyTags: '',
bodyScriptsPrepend: '',
bodyScripts: ''
if ('renderMeta' in ssrContext) {
rendered.meta = await ssrContext.renderMeta()
}
// @vueuse/head
if (typeof ssrContext.head === 'function') {
Object.assign(meta, ssrContext.head())
}
// vue-meta
if (ssrContext.meta && typeof ssrContext.meta.inject === 'function') {
const _meta = ssrContext.meta.inject({
isSSR: ssrContext.nuxt.serverRendered,
ln: process.env.NODE_ENV === 'development'
})
meta.htmlAttrs += _meta.htmlAttrs.text()
meta.headAttrs += _meta.headAttrs.text()
meta.headTags +=
_meta.title.text() + _meta.base.text() +
_meta.meta.text() + _meta.link.text() +
_meta.style.text() + _meta.script.text() +
_meta.noscript.text()
meta.bodyAttrs += _meta.bodyAttrs.text()
meta.bodyScriptsPrepend =
_meta.meta.text({ pbody: true }) + _meta.link.text({ pbody: true }) +
_meta.style.text({ pbody: true }) + _meta.script.text({ pbody: true }) +
_meta.noscript.text({ pbody: true })
meta.bodyScripts =
_meta.meta.text({ body: true }) + _meta.link.text({ body: true }) +
_meta.style.text({ body: true }) + _meta.script.text({ body: true }) +
_meta.noscript.text({ body: true })
}
const {
htmlAttrs = '',
bodyAttrs = '',
headAttrs = '',
headTags = '',
bodyScriptsPrepend = '',
bodyScripts = ''
} = rendered.meta || {}
return htmlTemplate({
HTML_ATTRS: meta.htmlAttrs,
HEAD_ATTRS: meta.headAttrs,
HEAD: meta.headTags +
HTML_ATTRS: htmlAttrs,
HEAD_ATTRS: headAttrs,
HEAD: headTags +
rendered.renderResourceHints() + rendered.renderStyles() + (ssrContext.styles || ''),
BODY_ATTRS: meta.bodyAttrs,
APP: meta.bodyScriptsPrepend + _html + state + rendered.renderScripts() + meta.bodyScripts
BODY_ATTRS: bodyAttrs,
APP: bodyScriptsPrepend + html + state + rendered.renderScripts() + bodyScripts
})
}

View File

@ -4,6 +4,11 @@
"repository": "nuxt/framework",
"license": "MIT",
"main": "./dist/index.js",
"bin": {
"nu": "./bin/nuxt.js",
"nuxt": "./bin/nuxt.js",
"nuxt-cli": "./bin/nuxt.js"
},
"files": [
"bin",
"dist"
@ -11,15 +16,11 @@
"scripts": {
"prepack": "unbuild $@ || true"
},
"bin": {
"nu": "./bin/nuxt.js",
"nuxt": "./bin/nuxt.js",
"nuxt-cli": "./bin/nuxt.js"
},
"dependencies": {
"@nuxt/app": "^0.5.0",
"@nuxt/component-discovery": "^0.2.0",
"@nuxt/kit": "^0.6.4",
"@nuxt/meta": "^0.1.0",
"@nuxt/nitro": "^0.9.1",
"@nuxt/pages": "^0.3.0",
"@nuxt/vite-builder": "^0.5.0",

View File

@ -56,6 +56,7 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
options._majorVersion = 3
options.alias.vue = normalize(require.resolve('vue/dist/vue.esm-bundler.js'))
options.buildModules.push(normalize(require.resolve('@nuxt/pages/module')))
options.buildModules.push(normalize(require.resolve('@nuxt/meta/module')))
options.buildModules.push(normalize(require.resolve('@nuxt/component-discovery/module')))
const nuxt = createNuxt(options)

View File

@ -1279,7 +1279,6 @@ __metadata:
version: 0.0.0-use.local
resolution: "@nuxt/app@workspace:packages/app"
dependencies:
"@vueuse/head": ^0.6.0
hookable: ^4.4.1
ohmyfetch: ^0.2.0
unbuild: ^0.3.2
@ -1287,6 +1286,8 @@ __metadata:
vue: ^3.1.4
vue-router: ^4.0.10
vuex5: ^0.5.0-testing.3
peerDependencies:
"@nuxt/meta": ^0.1.0
languageName: unknown
linkType: soft
@ -1355,6 +1356,22 @@ __metadata:
languageName: unknown
linkType: soft
"@nuxt/meta@^0.1.0, @nuxt/meta@workspace:packages/meta":
version: 0.0.0-use.local
resolution: "@nuxt/meta@workspace:packages/meta"
dependencies:
"@nuxt/kit": ^0.6.4
"@vueuse/head": ^0.6.0
unbuild: ^0.3.1
upath: ^2.0.1
vue-meta: 3.0.0-alpha.9
peerDependencies:
"@vue/reactivity": 3.1.1
"@vue/shared": 3.1.1
vue: 3.1.1
languageName: unknown
linkType: soft
"@nuxt/nitro@^0.9.1, @nuxt/nitro@workspace:packages/nitro":
version: 0.0.0-use.local
resolution: "@nuxt/nitro@workspace:packages/nitro"
@ -5416,6 +5433,14 @@ __metadata:
languageName: unknown
linkType: soft
"example-meta@workspace:examples/meta":
version: 0.0.0-use.local
resolution: "example-meta@workspace:examples/meta"
dependencies:
nuxt3: latest
languageName: unknown
linkType: soft
"example-pages@workspace:examples/pages":
version: 0.0.0-use.local
resolution: "example-pages@workspace:examples/pages"
@ -8621,6 +8646,7 @@ fsevents@~2.3.2:
"@nuxt/app": ^0.5.0
"@nuxt/component-discovery": ^0.2.0
"@nuxt/kit": ^0.6.4
"@nuxt/meta": ^0.1.0
"@nuxt/nitro": ^0.9.1
"@nuxt/pages": ^0.3.0
"@nuxt/vite-builder": ^0.5.0
@ -11667,7 +11693,7 @@ fsevents@~2.3.2:
languageName: node
linkType: hard
"unbuild@npm:^0.3.2":
"unbuild@npm:^0.3.1, unbuild@npm:^0.3.2":
version: 0.3.2
resolution: "unbuild@npm:0.3.2"
dependencies:
@ -11987,6 +12013,15 @@ fsevents@~2.3.2:
languageName: node
linkType: hard
"vue-meta@npm:3.0.0-alpha.9":
version: 3.0.0-alpha.9
resolution: "vue-meta@npm:3.0.0-alpha.9"
peerDependencies:
vue: ^3.0.0
checksum: bc0ccfee2b2fa9bdbf7a382a4af671def09c79ba2bbf3c89e54d642ec2de18cc1ad91f3ecd266e3cc8eb01547cef743648fd63b4392dd1c4b5663c6f7b94351f
languageName: node
linkType: hard
"vue-router@npm:^4.0.10":
version: 4.0.10
resolution: "vue-router@npm:4.0.10"