mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 23:22:02 +00:00
feat(nuxt): add useHeadSafe
and remove layer around head imports (#19548)
This commit is contained in:
parent
3f9a05601c
commit
c91e4d7933
31
docs/3.api/1.composables/use-head-safe.md
Normal file
31
docs/3.api/1.composables/use-head-safe.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
description: The recommended way to provide head data with user input.
|
||||||
|
---
|
||||||
|
|
||||||
|
# `useHeadSafe`
|
||||||
|
|
||||||
|
The useHeadSafe composable is a wrapper around the [useHead](/docs/api/composables/use-head) composable that restricts the input to only allow safe values.
|
||||||
|
|
||||||
|
::ReadMore{link="https://unhead.harlanzw.com/guide/composables/use-head-safe"}
|
||||||
|
::
|
||||||
|
|
||||||
|
## Type
|
||||||
|
|
||||||
|
```ts
|
||||||
|
useHeadSafe(input: MaybeComputedRef<HeadSafe>): void
|
||||||
|
```
|
||||||
|
|
||||||
|
The whitelist of safe values is:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
htmlAttrs: ['id', 'class', 'lang', 'dir'],
|
||||||
|
bodyAttrs: ['id', 'class'],
|
||||||
|
meta: ['id', 'name', 'property', 'charset', 'content'],
|
||||||
|
noscript: ['id', 'textContent'],
|
||||||
|
script: ['id', 'type', 'textContent'],
|
||||||
|
link: ['id', 'color', 'crossorigin', 'fetchpriority', 'href', 'hreflang', 'imagesrcset', 'imagesizes', 'integrity', 'media', 'referrerpolicy', 'rel', 'sizes', 'type'],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See [@unhead/schema](https://github.com/unjs/unhead/blob/main/packages/schema/src/safeSchema.ts) for more detailed types.
|
@ -2,11 +2,12 @@ import { defineComponent, createStaticVNode, computed, ref, watch } from 'vue'
|
|||||||
import { debounce } from 'perfect-debounce'
|
import { debounce } from 'perfect-debounce'
|
||||||
import { hash } from 'ohash'
|
import { hash } from 'ohash'
|
||||||
import { appendHeader } from 'h3'
|
import { appendHeader } from 'h3'
|
||||||
|
import { useHead } from '@unhead/vue'
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-restricted-paths
|
// eslint-disable-next-line import/no-restricted-paths
|
||||||
import type { NuxtIslandResponse } from '../../core/runtime/nitro/renderer'
|
import type { NuxtIslandResponse } from '../../core/runtime/nitro/renderer'
|
||||||
import { useNuxtApp } from '#app/nuxt'
|
import { useNuxtApp } from '#app/nuxt'
|
||||||
import { useRequestEvent } from '#app/composables/ssr'
|
import { useRequestEvent } from '#app/composables/ssr'
|
||||||
import { useHead } from '#app/composables/head'
|
|
||||||
|
|
||||||
const pKey = '_islandPromises'
|
const pKey = '_islandPromises'
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { getCurrentInstance, reactive, toRefs } from 'vue'
|
import { getCurrentInstance, reactive, toRefs } from 'vue'
|
||||||
import type { DefineComponent, defineComponent } from 'vue'
|
import type { DefineComponent, defineComponent } from 'vue'
|
||||||
|
import { useHead } from '@unhead/vue'
|
||||||
import type { NuxtApp } from '../nuxt'
|
import type { NuxtApp } from '../nuxt'
|
||||||
import { useNuxtApp } from '../nuxt'
|
import { useNuxtApp } from '../nuxt'
|
||||||
import { useAsyncData } from './asyncData'
|
import { useAsyncData } from './asyncData'
|
||||||
import { useRoute } from './router'
|
import { useRoute } from './router'
|
||||||
import { useHead } from './head'
|
|
||||||
|
|
||||||
export const NuxtComponentIndicator = '__nuxt_component'
|
export const NuxtComponentIndicator = '__nuxt_component'
|
||||||
|
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
// eslint-disable-next-line import/no-restricted-paths
|
|
||||||
export type { MetaObject } from '#head'
|
|
||||||
// eslint-disable-next-line import/no-restricted-paths
|
|
||||||
export { useHead, useSeoMeta, useServerSeoMeta } from '#head'
|
|
@ -1,3 +1,17 @@
|
|||||||
|
import type { UseHeadInput } from '@unhead/vue'
|
||||||
|
import type { HeadAugmentations } from 'nuxt/schema'
|
||||||
|
|
||||||
|
/** @deprecated Use `UseHeadInput` from `@unhead/vue` instead. This may be removed in a future minor version. */
|
||||||
|
export type MetaObject = UseHeadInput<HeadAugmentations>
|
||||||
|
export {
|
||||||
|
/** @deprecated Import `useHead` from `#imports` instead. This may be removed in a future minor version. */
|
||||||
|
useHead,
|
||||||
|
/** @deprecated Import `useSeoMeta` from `#imports` instead. This may be removed in a future minor version. */
|
||||||
|
useSeoMeta,
|
||||||
|
/** @deprecated Import `useServerSeoMeta` from `#imports` instead. This may be removed in a future minor version. */
|
||||||
|
useServerSeoMeta
|
||||||
|
} from '@unhead/vue'
|
||||||
|
|
||||||
export { defineNuxtComponent } from './component'
|
export { defineNuxtComponent } from './component'
|
||||||
export { useAsyncData, useLazyAsyncData, useNuxtData, refreshNuxtData, clearNuxtData } from './asyncData'
|
export { useAsyncData, useLazyAsyncData, useNuxtData, refreshNuxtData, clearNuxtData } from './asyncData'
|
||||||
export type { AsyncDataOptions, AsyncData } from './asyncData'
|
export type { AsyncDataOptions, AsyncData } from './asyncData'
|
||||||
@ -15,7 +29,5 @@ export { abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBefor
|
|||||||
export type { AddRouteMiddlewareOptions, RouteMiddleware } from './router'
|
export type { AddRouteMiddlewareOptions, RouteMiddleware } from './router'
|
||||||
export { preloadComponents, prefetchComponents, preloadRouteComponents } from './preload'
|
export { preloadComponents, prefetchComponents, preloadRouteComponents } from './preload'
|
||||||
export { isPrerendered, loadPayload, preloadPayload } from './payload'
|
export { isPrerendered, loadPayload, preloadPayload } from './payload'
|
||||||
export type { MetaObject } from './head'
|
|
||||||
export { useHead, useSeoMeta, useServerSeoMeta } from './head'
|
|
||||||
export type { ReloadNuxtAppOptions } from './chunk'
|
export type { ReloadNuxtAppOptions } from './chunk'
|
||||||
export { reloadNuxtApp } from './chunk'
|
export { reloadNuxtApp } from './chunk'
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { joinURL, hasProtocol } from 'ufo'
|
import { joinURL, hasProtocol } from 'ufo'
|
||||||
|
import { useHead } from '@unhead/vue'
|
||||||
import { useNuxtApp, useRuntimeConfig } from '../nuxt'
|
import { useNuxtApp, useRuntimeConfig } from '../nuxt'
|
||||||
import { useHead } from './head'
|
|
||||||
|
|
||||||
interface LoadPayloadOptions {
|
interface LoadPayloadOptions {
|
||||||
fresh?: boolean
|
fresh?: boolean
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { parseURL } from 'ufo'
|
import { parseURL } from 'ufo'
|
||||||
|
import { useHead } from '@unhead/vue'
|
||||||
import { defineNuxtPlugin } from '#app/nuxt'
|
import { defineNuxtPlugin } from '#app/nuxt'
|
||||||
import { useHead } from '#app/composables/head'
|
|
||||||
|
|
||||||
export default defineNuxtPlugin((nuxtApp) => {
|
export default defineNuxtPlugin((nuxtApp) => {
|
||||||
const externalURLs = ref(new Set<string>())
|
const externalURLs = ref(new Set<string>())
|
||||||
|
@ -3,11 +3,11 @@ import { debounce } from 'perfect-debounce'
|
|||||||
import { hash } from 'ohash'
|
import { hash } from 'ohash'
|
||||||
import { appendHeader } from 'h3'
|
import { appendHeader } from 'h3'
|
||||||
|
|
||||||
|
import { useHead } from '@unhead/vue'
|
||||||
import type { NuxtIslandResponse } from '../../core/runtime/nitro/renderer'
|
import type { NuxtIslandResponse } from '../../core/runtime/nitro/renderer'
|
||||||
import { useNuxtApp } from '#app/nuxt'
|
import { useNuxtApp } from '#app/nuxt'
|
||||||
import { useRequestEvent } from '#app/composables/ssr'
|
import { useRequestEvent } from '#app/composables/ssr'
|
||||||
import { useAsyncData } from '#app/composables/asyncData'
|
import { useAsyncData } from '#app/composables/asyncData'
|
||||||
import { useHead } from '#app/composables/head'
|
|
||||||
|
|
||||||
const pKey = '_islandPromises'
|
const pKey = '_islandPromises'
|
||||||
|
|
||||||
|
@ -1,21 +1,26 @@
|
|||||||
import { resolve } from 'pathe'
|
import { resolve } from 'pathe'
|
||||||
import { addComponent, addPlugin, defineNuxtModule, tryResolveModule } from '@nuxt/kit'
|
import { addComponent, addImportsSources, addPlugin, defineNuxtModule, tryResolveModule } from '@nuxt/kit'
|
||||||
import { distDir } from '../dirs'
|
import { distDir } from '../dirs'
|
||||||
|
|
||||||
const components = ['NoScript', 'Link', 'Base', 'Title', 'Meta', 'Style', 'Head', 'Html', 'Body']
|
const components = ['NoScript', 'Link', 'Base', 'Title', 'Meta', 'Style', 'Head', 'Html', 'Body']
|
||||||
|
|
||||||
export default defineNuxtModule({
|
export default defineNuxtModule({
|
||||||
meta: {
|
meta: {
|
||||||
name: 'meta'
|
name: 'head'
|
||||||
},
|
},
|
||||||
setup (options, nuxt) {
|
setup (options, nuxt) {
|
||||||
const runtimeDir = nuxt.options.alias['#head'] || 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')
|
||||||
|
|
||||||
// Add #head alias
|
// TODO: remove alias in v3.4
|
||||||
nuxt.options.alias['#head'] = runtimeDir
|
nuxt.options.alias['#head'] = nuxt.options.alias['#app']
|
||||||
|
nuxt.hook('prepare:types', ({ tsConfig }) => {
|
||||||
|
tsConfig.compilerOptions = tsConfig.compilerOptions || {}
|
||||||
|
delete tsConfig.compilerOptions.paths['#head']
|
||||||
|
delete tsConfig.compilerOptions.paths['#head/*']
|
||||||
|
})
|
||||||
|
|
||||||
// Register components
|
// Register components
|
||||||
const componentsPath = resolve(runtimeDir, 'components')
|
const componentsPath = resolve(runtimeDir, 'components')
|
||||||
@ -30,14 +35,29 @@ export default defineNuxtModule({
|
|||||||
kebabName: componentName
|
kebabName: componentName
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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'
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
// Opt-out feature allowing dependencies using @vueuse/head to work
|
// Opt-out feature allowing dependencies using @vueuse/head to work
|
||||||
if (nuxt.options.experimental.polyfillVueUseHead) {
|
if (nuxt.options.experimental.polyfillVueUseHead) {
|
||||||
// backwards compatibility
|
// backwards compatibility
|
||||||
nuxt.options.alias['@vueuse/head'] = tryResolveModule('@unhead/vue') || '@unhead/vue'
|
nuxt.options.alias['@vueuse/head'] = tryResolveModule('@unhead/vue') || '@unhead/vue'
|
||||||
addPlugin({ src: resolve(runtimeDir, 'lib/vueuse-head-polyfill.plugin') })
|
addPlugin({ src: resolve(runtimeDir, 'plugins/vueuse-head-polyfill') })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add library-specific plugin
|
// Add library-specific plugin
|
||||||
addPlugin({ src: resolve(runtimeDir, 'lib/unhead.plugin') })
|
addPlugin({ src: resolve(runtimeDir, 'plugins/unhead') })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
import type { PropType, SetupContext } from 'vue'
|
import type { PropType, SetupContext } from 'vue'
|
||||||
import { useHead } from './composables'
|
import { useHead } from '@unhead/vue'
|
||||||
import type {
|
import type {
|
||||||
CrossOrigin,
|
CrossOrigin,
|
||||||
FetchPriority,
|
FetchPriority,
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
import type { HeadEntryOptions, UseHeadInput, ActiveHeadEntry } from '@unhead/vue'
|
|
||||||
import { useSeoMeta as _useSeoMeta } from '@unhead/vue'
|
|
||||||
import type { HeadAugmentations } from 'nuxt/schema'
|
|
||||||
import { useNuxtApp } from '#app/nuxt'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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<T extends HeadAugmentations> (input: UseHeadInput<T>, options?: HeadEntryOptions): ActiveHeadEntry<UseHeadInput<T>> | void {
|
|
||||||
return useNuxtApp()._useHead(input, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `useSeoMeta` composable lets you define your site's SEO meta tags
|
|
||||||
* as a flat object with full TypeScript support.
|
|
||||||
*
|
|
||||||
* This helps you avoid typos and common mistakes, such as using `name`
|
|
||||||
* instead of `property`.
|
|
||||||
*
|
|
||||||
* It is advised to use `useServerSeoMeta` unless you _need_ client-side
|
|
||||||
* rendering of your SEO meta tags.
|
|
||||||
*/
|
|
||||||
export const useSeoMeta: typeof _useSeoMeta = (meta) => {
|
|
||||||
return _useSeoMeta(meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `useServerSeoMeta` composable is identical to `useSeoMeta` except that
|
|
||||||
* it will have no effect (and will return nothing) if called on the client.
|
|
||||||
*/
|
|
||||||
export const useServerSeoMeta: typeof _useSeoMeta = (meta) => {
|
|
||||||
if (process.server) {
|
|
||||||
return _useSeoMeta(meta)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
import type { UseHeadInput } from '@unhead/vue'
|
|
||||||
import type { HeadAugmentations } from 'nuxt/schema'
|
|
||||||
|
|
||||||
export * from './composables'
|
|
||||||
|
|
||||||
export type MetaObject = UseHeadInput<HeadAugmentations>
|
|
@ -1,4 +1,4 @@
|
|||||||
import { createHead, useHead } from '@unhead/vue'
|
import { createHead } from '@unhead/vue'
|
||||||
import { renderSSRHead } from '@unhead/ssr'
|
import { renderSSRHead } from '@unhead/ssr'
|
||||||
import { defineNuxtPlugin } from '#app/nuxt'
|
import { defineNuxtPlugin } from '#app/nuxt'
|
||||||
// @ts-expect-error untyped
|
// @ts-expect-error untyped
|
||||||
@ -25,9 +25,6 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|||||||
nuxtApp.hooks.hook('app:mounted', unpauseDom)
|
nuxtApp.hooks.hook('app:mounted', unpauseDom)
|
||||||
}
|
}
|
||||||
|
|
||||||
// support backwards compatibility, remove at some point
|
|
||||||
nuxtApp._useHead = useHead
|
|
||||||
|
|
||||||
if (process.server) {
|
if (process.server) {
|
||||||
nuxtApp.ssrContext!.renderMeta = async () => {
|
nuxtApp.ssrContext!.renderMeta = async () => {
|
||||||
const meta = await renderSSRHead(head)
|
const meta = await renderSSRHead(head)
|
@ -15,9 +15,6 @@ const commonPresets: InlinePreset[] = [
|
|||||||
const appPreset = defineUnimportPreset({
|
const appPreset = defineUnimportPreset({
|
||||||
from: '#app',
|
from: '#app',
|
||||||
imports: [
|
imports: [
|
||||||
'useHead',
|
|
||||||
'useSeoMeta',
|
|
||||||
'useServerSeoMeta',
|
|
||||||
'useAsyncData',
|
'useAsyncData',
|
||||||
'useLazyAsyncData',
|
'useLazyAsyncData',
|
||||||
'useNuxtData',
|
'useNuxtData',
|
||||||
|
@ -54,7 +54,7 @@ describe('imports:transform', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const excludedNuxtHelpers = ['useHydration']
|
const excludedNuxtHelpers = ['useHydration', 'useHead', 'useSeoMeta', 'useServerSeoMeta']
|
||||||
|
|
||||||
describe('imports:nuxt', () => {
|
describe('imports:nuxt', () => {
|
||||||
try {
|
try {
|
||||||
@ -62,7 +62,8 @@ describe('imports:nuxt', () => {
|
|||||||
const entrypointContents = readFileSync(join(__dirname, '../src/app/composables/index.ts'), 'utf8')
|
const entrypointContents = readFileSync(join(__dirname, '../src/app/composables/index.ts'), 'utf8')
|
||||||
|
|
||||||
const names = findExports(entrypointContents).flatMap(i => i.names || i.name)
|
const names = findExports(entrypointContents).flatMap(i => i.names || i.name)
|
||||||
for (const name of names) {
|
for (let name of names) {
|
||||||
|
name = name.replace(/\/\*.*\*\//, '').trim()
|
||||||
if (excludedNuxtHelpers.includes(name)) {
|
if (excludedNuxtHelpers.includes(name)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { defineUntypedSchema } from 'untyped'
|
import { defineUntypedSchema } from 'untyped'
|
||||||
import { defu } from 'defu'
|
import { defu } from 'defu'
|
||||||
import type { AppHeadMetaObject } from '../types/meta'
|
import type { AppHeadMetaObject } from '../types/head'
|
||||||
|
|
||||||
export default defineUntypedSchema({
|
export default defineUntypedSchema({
|
||||||
/**
|
/**
|
||||||
|
@ -7,7 +7,7 @@ export * from './types/components'
|
|||||||
export * from './types/config'
|
export * from './types/config'
|
||||||
export * from './types/hooks'
|
export * from './types/hooks'
|
||||||
export * from './types/imports'
|
export * from './types/imports'
|
||||||
export * from './types/meta'
|
export * from './types/head'
|
||||||
export * from './types/module'
|
export * from './types/module'
|
||||||
export * from './types/nuxt'
|
export * from './types/nuxt'
|
||||||
export * from './types/router'
|
export * from './types/router'
|
||||||
|
@ -3,7 +3,7 @@ import type { ConfigSchema } from '../../schema/config'
|
|||||||
import type { ServerOptions as ViteServerOptions, UserConfig as ViteUserConfig } from 'vite'
|
import type { ServerOptions as ViteServerOptions, UserConfig as ViteUserConfig } from 'vite'
|
||||||
import type { Options as VuePluginOptions } from '@vitejs/plugin-vue'
|
import type { Options as VuePluginOptions } from '@vitejs/plugin-vue'
|
||||||
import type { Options as VueJsxPluginOptions } from '@vitejs/plugin-vue-jsx'
|
import type { Options as VueJsxPluginOptions } from '@vitejs/plugin-vue-jsx'
|
||||||
import type { AppHeadMetaObject } from './meta'
|
import type { AppHeadMetaObject } from './head'
|
||||||
import type { Nuxt } from './nuxt'
|
import type { Nuxt } from './nuxt'
|
||||||
import type { SchemaDefinition } from 'untyped'
|
import type { SchemaDefinition } from 'untyped'
|
||||||
export type { SchemaDefinition } from 'untyped'
|
export type { SchemaDefinition } from 'untyped'
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import type { Head, MergeHead } from '@unhead/schema'
|
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 {
|
export interface HeadAugmentations extends MergeHead {
|
||||||
// runtime type modifications
|
// runtime type modifications
|
||||||
base?: {}
|
base?: {}
|
@ -416,6 +416,22 @@ describe('head tags', () => {
|
|||||||
expect(indexHtml).toContain('<title>Basic fixture</title>')
|
expect(indexHtml).toContain('<title>Basic fixture</title>')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('SSR script setup should render tags', async () => {
|
||||||
|
const headHtml = await $fetch('/head-script-setup')
|
||||||
|
|
||||||
|
// useHead - title & titleTemplate are working
|
||||||
|
expect(headHtml).toContain('<title>head script setup - Nuxt Playground</title>')
|
||||||
|
// useSeoMeta - template params
|
||||||
|
expect(headHtml).toContain('<meta property="og:title" content="head script setup - Nuxt Playground">')
|
||||||
|
// useSeoMeta - refs
|
||||||
|
expect(headHtml).toContain('<meta name="description" content="head script setup description for Nuxt Playground">')
|
||||||
|
// useServerHead - shorthands
|
||||||
|
expect(headHtml).toContain('>/* Custom styles */</style>')
|
||||||
|
// useHeadSafe - removes dangerous content
|
||||||
|
expect(headHtml).toContain('<script id="xss-script"></script>')
|
||||||
|
expect(headHtml).toContain('<meta content="0;javascript:alert(1)">')
|
||||||
|
})
|
||||||
|
|
||||||
it('SPA should render appHead tags', async () => {
|
it('SPA should render appHead tags', async () => {
|
||||||
const headHtml = await $fetch('/head', { headers: { 'x-nuxt-no-ssr': '1' } })
|
const headHtml = await $fetch('/head', { headers: { 'x-nuxt-no-ssr': '1' } })
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ describe.skipIf(isWindows)('minimal nuxt application', () => {
|
|||||||
|
|
||||||
it('default client bundle size', async () => {
|
it('default client bundle size', async () => {
|
||||||
stats.client = await analyzeSizes('**/*.js', publicDir)
|
stats.client = await analyzeSizes('**/*.js', publicDir)
|
||||||
expect(stats.client.totalBytes).toBeLessThan(106200)
|
expect(stats.client.totalBytes).toBeLessThan(105700)
|
||||||
expect(stats.client.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(`
|
expect(stats.client.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
"_nuxt/_plugin-vue_export-helper.js",
|
"_nuxt/_plugin-vue_export-helper.js",
|
||||||
@ -40,7 +40,7 @@ describe.skipIf(isWindows)('minimal nuxt application', () => {
|
|||||||
|
|
||||||
it('default server bundle size', async () => {
|
it('default server bundle size', async () => {
|
||||||
stats.server = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
stats.server = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
||||||
expect(stats.server.totalBytes).toBeLessThan(94450)
|
expect(stats.server.totalBytes).toBeLessThan(94000)
|
||||||
|
|
||||||
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
||||||
expect(modules.totalBytes).toBeLessThan(2713000)
|
expect(modules.totalBytes).toBeLessThan(2713000)
|
||||||
|
57
test/fixtures/basic/pages/head-script-setup.vue
vendored
Normal file
57
test/fixtures/basic/pages/head-script-setup.vue
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
const description = ref('head script setup description for %site.name')
|
||||||
|
const siteName = ref()
|
||||||
|
// server meta
|
||||||
|
useServerSeoMeta({
|
||||||
|
description,
|
||||||
|
ogDescription: description,
|
||||||
|
ogImage: '%site.url/og-image.png',
|
||||||
|
ogTitle: '%s %separator %site.name',
|
||||||
|
ogType: 'website',
|
||||||
|
ogUrl: '%site.url/head-script-setup'
|
||||||
|
})
|
||||||
|
|
||||||
|
useServerHead({
|
||||||
|
style: [
|
||||||
|
'/* Custom styles */',
|
||||||
|
'h1 { color: salmon; }'
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
useHead({
|
||||||
|
title: 'head script setup',
|
||||||
|
titleTemplate: '%s %separator %site.name',
|
||||||
|
templateParams: {
|
||||||
|
separator: () => '-',
|
||||||
|
site: {
|
||||||
|
url: 'https://example.com',
|
||||||
|
name: siteName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
useHeadSafe({
|
||||||
|
script: [
|
||||||
|
{
|
||||||
|
id: 'xss-script',
|
||||||
|
// @ts-expect-error not allowed
|
||||||
|
innerHTML: 'alert("xss")'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: [
|
||||||
|
{
|
||||||
|
// @ts-expect-error not allowed
|
||||||
|
'http-equiv': 'refresh',
|
||||||
|
content: '0;javascript:alert(1)'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
siteName.value = 'Nuxt Playground'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>head script setup</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
3
test/fixtures/basic/plugins/my-plugin.ts
vendored
3
test/fixtures/basic/plugins/my-plugin.ts
vendored
@ -1,3 +1,6 @@
|
|||||||
|
// @ts-expect-error
|
||||||
|
import { useHead } from '#head'
|
||||||
|
|
||||||
export default defineNuxtPlugin(() => {
|
export default defineNuxtPlugin(() => {
|
||||||
useHead({
|
useHead({
|
||||||
titleTemplate: '%s - Fixture'
|
titleTemplate: '%s - Fixture'
|
||||||
|
@ -22,9 +22,6 @@
|
|||||||
"#app/*": [
|
"#app/*": [
|
||||||
"./packages/nuxt/src/app/*"
|
"./packages/nuxt/src/app/*"
|
||||||
],
|
],
|
||||||
"#head": [
|
|
||||||
"./packages/nuxt/src/head/runtime/index"
|
|
||||||
],
|
|
||||||
"#internal/nitro": [
|
"#internal/nitro": [
|
||||||
"./node_modules/nitropack/dist/runtime"
|
"./node_modules/nitropack/dist/runtime"
|
||||||
],
|
],
|
||||||
|
Loading…
Reference in New Issue
Block a user