mirror of
https://github.com/nuxt/nuxt.git
synced 2025-02-16 21:58:19 +00:00
parent
3426fca610
commit
f3f594f7f9
@ -1,60 +1,3 @@
|
||||
import { getCurrentInstance, inject } from 'vue'
|
||||
import { useNuxtApp } from '../nuxt'
|
||||
import { clientOnlySymbol } from '#app/components/client-only'
|
||||
import { useId as _useId } from 'vue'
|
||||
|
||||
const ATTR_KEY = 'data-n-ids'
|
||||
const SEPARATOR = '-'
|
||||
|
||||
/**
|
||||
* Generate an SSR-friendly unique identifier that can be passed to accessibility attributes.
|
||||
*
|
||||
* The generated ID is unique in the context of the current Nuxt instance and key.
|
||||
*/
|
||||
export function useId (): string
|
||||
export function useId (key?: string): string {
|
||||
if (typeof key !== 'string') {
|
||||
throw new TypeError('[nuxt] [useId] key must be a string.')
|
||||
}
|
||||
// TODO: implement in composable-keys
|
||||
// Make sure key starts with a letter to be a valid selector
|
||||
key = `n${key.slice(1)}`
|
||||
const nuxtApp = useNuxtApp()
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
if (!instance) {
|
||||
// TODO: support auto-incrementing ID for plugins if there is need?
|
||||
throw new TypeError('[nuxt] `useId` must be called within a component setup function.')
|
||||
}
|
||||
|
||||
nuxtApp._genId ||= 0
|
||||
instance._nuxtIdIndex ||= {}
|
||||
instance._nuxtIdIndex[key] ||= 0
|
||||
|
||||
const instanceIndex = key + SEPARATOR + instance._nuxtIdIndex[key]++
|
||||
|
||||
if (import.meta.server) {
|
||||
const ids = JSON.parse(instance.attrs[ATTR_KEY] as string | undefined || '{}')
|
||||
ids[instanceIndex] = key + SEPARATOR + nuxtApp._genId++
|
||||
instance.attrs[ATTR_KEY] = JSON.stringify(ids)
|
||||
return ids[instanceIndex]
|
||||
}
|
||||
|
||||
if (nuxtApp.payload.serverRendered && nuxtApp.isHydrating && !inject(clientOnlySymbol, false)) {
|
||||
// Access data attribute from sibling if root is a comment node and sibling is an element
|
||||
const el = instance.vnode.el?.nodeType === 8 && instance.vnode.el?.nextElementSibling?.getAttribute
|
||||
? instance.vnode.el?.nextElementSibling
|
||||
: instance.vnode.el
|
||||
|
||||
const ids = JSON.parse(el?.getAttribute?.(ATTR_KEY) || '{}')
|
||||
if (ids[instanceIndex]) {
|
||||
return ids[instanceIndex]
|
||||
}
|
||||
|
||||
if (import.meta.dev && instance.vnode.type && typeof instance.vnode.type === 'object' && 'inheritAttrs' in instance.vnode.type && instance.vnode.type.inheritAttrs === false) {
|
||||
console.warn('[nuxt] `useId` might not work correctly with components that have `inheritAttrs: false`.')
|
||||
}
|
||||
}
|
||||
|
||||
// pure client-side ids, avoiding potential collision with server-side ids
|
||||
return key + '_' + nuxtApp._genId++
|
||||
}
|
||||
export const useId = _useId
|
||||
|
@ -105,10 +105,6 @@ const granularAppPresets: InlinePreset[] = [
|
||||
imports: ['usePreviewMode'],
|
||||
from: '#app/composables/preview',
|
||||
},
|
||||
{
|
||||
imports: ['useId'],
|
||||
from: '#app/composables/id',
|
||||
},
|
||||
{
|
||||
imports: ['useRouteAnnouncer'],
|
||||
from: '#app/composables/route-announcer',
|
||||
@ -231,6 +227,14 @@ const vuePreset = defineUnimportPreset({
|
||||
'useCssVars',
|
||||
'useSlots',
|
||||
'useTransitionState',
|
||||
'useId',
|
||||
'useTemplateRef',
|
||||
'hydrateOnInteraction',
|
||||
'hydrateOnMediaQuery',
|
||||
'hydrateOnVisible',
|
||||
'hydrateOnIdle',
|
||||
'useHost',
|
||||
'useShadowRoot',
|
||||
],
|
||||
})
|
||||
|
||||
|
@ -56,7 +56,7 @@ describe('imports:transform', () => {
|
||||
})
|
||||
})
|
||||
|
||||
const excludedNuxtHelpers = ['useHydration', 'useHead', 'useSeoMeta', 'useServerSeoMeta']
|
||||
const excludedNuxtHelpers = ['useHydration', 'useHead', 'useSeoMeta', 'useServerSeoMeta', 'useId']
|
||||
|
||||
describe('imports:nuxt', () => {
|
||||
try {
|
||||
|
@ -127,10 +127,10 @@ describe('treeshake client only in ssr', () => {
|
||||
const ssrResult = await SFCCompile(`SomeComponent${state.index}.vue`, WithClientOnly, state.options, true)
|
||||
|
||||
const treeshaken = await treeshake(ssrResult)
|
||||
const [_, scopeId] = clientResult.match(/_pushScopeId\("(.*)"\)/)!
|
||||
const [_, scopeId] = clientResult.match(/['"]__scopeId['"],\s*['"](data-v-[^'"]+)['"]/)!
|
||||
|
||||
// ensure the id is correctly passed between server and client
|
||||
expect(clientResult).toContain(`pushScopeId("${scopeId}")`)
|
||||
expect(clientResult).toContain(`'__scopeId',"${scopeId}"`)
|
||||
expect(treeshaken).toContain(`<div ${scopeId}>`)
|
||||
|
||||
expect(clientResult).toContain('should-be-treeshaken')
|
||||
|
@ -19,7 +19,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
for (const outputDir of ['.output', '.output-inline']) {
|
||||
it('default client bundle size', async () => {
|
||||
const clientStats = await analyzeSizes('**/*.js', join(rootDir, outputDir, 'public'))
|
||||
expect.soft(roundToKilobytes(clientStats.totalBytes)).toMatchInlineSnapshot(`"107k"`)
|
||||
expect.soft(roundToKilobytes(clientStats.totalBytes)).toMatchInlineSnapshot(`"112k"`)
|
||||
expect(clientStats.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(`
|
||||
[
|
||||
"_nuxt/entry.js",
|
||||
@ -32,10 +32,10 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
const serverDir = join(rootDir, '.output/server')
|
||||
|
||||
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"211k"`)
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"205k"`)
|
||||
|
||||
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1348k"`)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1370k"`)
|
||||
|
||||
const packages = modules.files
|
||||
.filter(m => m.endsWith('package.json'))
|
||||
@ -74,7 +74,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
const serverDir = join(rootDir, '.output-inline/server')
|
||||
|
||||
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"535k"`)
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"549k"`)
|
||||
|
||||
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"80.3k"`)
|
||||
|
@ -4,7 +4,6 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
import { defineEventHandler } from 'h3'
|
||||
import { destr } from 'destr'
|
||||
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { mountSuspended, registerEndpoint } from '@nuxt/test-utils/runtime'
|
||||
|
||||
import { hasProtocol } from 'ufo'
|
||||
@ -17,7 +16,6 @@ import { setResponseStatus, useRequestEvent, useRequestFetch, useRequestHeaders
|
||||
import { clearNuxtState, useState } from '#app/composables/state'
|
||||
import { useRequestURL } from '#app/composables/url'
|
||||
import { getAppManifest, getRouteRules } from '#app/composables/manifest'
|
||||
import { useId } from '#app/composables/id'
|
||||
import { callOnce } from '#app/composables/once'
|
||||
import { useLoadingIndicator } from '#app/composables/loading-indicator'
|
||||
import { useRouteAnnouncer } from '#app/composables/route-announcer'
|
||||
@ -79,7 +77,6 @@ describe('composables', () => {
|
||||
'clearNuxtState',
|
||||
'useState',
|
||||
'useRequestURL',
|
||||
'useId',
|
||||
'useRoute',
|
||||
'navigateTo',
|
||||
'abortNavigation',
|
||||
@ -101,6 +98,7 @@ describe('composables', () => {
|
||||
'reloadNuxtApp',
|
||||
'refreshCookie',
|
||||
'onPrehydrate',
|
||||
'useId',
|
||||
'useFetch',
|
||||
'useHead',
|
||||
'useLazyFetch',
|
||||
@ -459,33 +457,6 @@ describe('clearNuxtState', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('useId', () => {
|
||||
it('default', () => {
|
||||
const vals = new Set<string>()
|
||||
for (let index = 0; index < 100; index++) {
|
||||
mount(defineComponent({
|
||||
setup () {
|
||||
const id = useId()
|
||||
vals.add(id)
|
||||
return () => h('div', id)
|
||||
},
|
||||
}))
|
||||
}
|
||||
expect(vals.size).toBe(100)
|
||||
})
|
||||
|
||||
it('generates unique ids per-component', () => {
|
||||
const component = defineComponent({
|
||||
setup () {
|
||||
const id = useId()
|
||||
return () => h('div', id)
|
||||
},
|
||||
})
|
||||
|
||||
expect(mount(component).html()).not.toBe(mount(component).html())
|
||||
})
|
||||
})
|
||||
|
||||
describe('url', () => {
|
||||
it('useRequestURL', () => {
|
||||
const url = useRequestURL()
|
||||
|
Loading…
Reference in New Issue
Block a user