diff --git a/packages/nuxt/src/app/components/nuxt-teleport-island-component.ts b/packages/nuxt/src/app/components/nuxt-teleport-island-component.ts index b8ef06f9c4..2c11dbcb75 100644 --- a/packages/nuxt/src/app/components/nuxt-teleport-island-component.ts +++ b/packages/nuxt/src/app/components/nuxt-teleport-island-component.ts @@ -1,5 +1,5 @@ import type { Component, InjectionKey } from 'vue' -import { Teleport, defineComponent, h, inject, provide } from 'vue' +import { Teleport, defineComponent, h, inject, provide, useId } from 'vue' import { useNuxtApp } from '../nuxt' // @ts-expect-error virtual file import { paths } from '#build/components-chunk' @@ -20,10 +20,6 @@ export default defineComponent({ name: 'NuxtTeleportIslandComponent', inheritAttrs: false, props: { - to: { - type: String, - required: true, - }, nuxtClient: { type: Boolean, default: false, @@ -31,11 +27,12 @@ export default defineComponent({ }, setup (props, { slots }) { const nuxtApp = useNuxtApp() + const to = useId() // if there's already a teleport parent, we don't need to teleport or to render the wrapped component client side if (!nuxtApp.ssrContext?.islandContext || !props.nuxtClient || inject(NuxtTeleportIslandSymbol, false)) { return () => slots.default?.() } - provide(NuxtTeleportIslandSymbol, props.to) + provide(NuxtTeleportIslandSymbol, to) const islandContext = nuxtApp.ssrContext!.islandContext! return () => { @@ -43,7 +40,7 @@ export default defineComponent({ const slotType = slot.type as ExtendedComponent const name = (slotType.__name || slotType.name) as string - islandContext.components[props.to] = { + islandContext.components[to] = { chunk: import.meta.dev ? nuxtApp.$config.app.buildAssetsDir + paths[name] : paths[name], props: slot.props || {}, } @@ -51,8 +48,8 @@ export default defineComponent({ return [h('div', { 'style': 'display: contents;', 'data-island-uid': '', - 'data-island-component': props.to, - }, []), h(Teleport, { to: props.to }, slot)] + 'data-island-component': to, + }, []), h(Teleport, { to }, slot)] } }, }) diff --git a/packages/nuxt/src/components/plugins/islands-transform.ts b/packages/nuxt/src/components/plugins/islands-transform.ts index a3e2aba41a..5f2954b550 100644 --- a/packages/nuxt/src/components/plugins/islands-transform.ts +++ b/packages/nuxt/src/components/plugins/islands-transform.ts @@ -6,7 +6,6 @@ import { parseURL } from 'ufo' import { createUnplugin } from 'unplugin' import MagicString from 'magic-string' import { ELEMENT_NODE, parse, walk } from 'ultrahtml' -import { hash } from 'ohash' import { resolvePath } from '@nuxt/kit' import defu from 'defu' import { isVue } from '../../core/utils' @@ -113,8 +112,6 @@ export const IslandsTransformPlugin = (options: ServerOnlyComponentTransformPlug const { loc, attributes } = node const attributeValue = attributes[':nuxt-client'] || attributes['nuxt-client'] || 'true' - - const uid = hash(id + node.loc[0].start + node.loc[0].end) const wrapperAttributes = extractAttributes(attributes, ['v-if', 'v-else-if', 'v-else']) let startTag = code.slice(startingIndex + loc[0].start, startingIndex + loc[0].end).replace(NUXTCLIENT_ATTR_RE, '') @@ -122,7 +119,7 @@ export const IslandsTransformPlugin = (options: ServerOnlyComponentTransformPlug startTag = startTag.replaceAll(EXTRACTED_ATTRS_RE, '') } - s.appendLeft(startingIndex + loc[0].start, ``) + s.appendLeft(startingIndex + loc[0].start, ``) s.overwrite(startingIndex + loc[0].start, startingIndex + loc[0].end, startTag) s.appendRight(startingIndex + loc[1].end, '') }) diff --git a/packages/nuxt/test/island-transform.test.ts b/packages/nuxt/test/island-transform.test.ts index 3743987565..adbae59ffe 100644 --- a/packages/nuxt/test/island-transform.test.ts +++ b/packages/nuxt/test/island-transform.test.ts @@ -271,7 +271,7 @@ withDefaults(defineProps<{ things?: any[]; somethingElse?: string }>(), { " @@ -305,7 +305,7 @@ withDefaults(defineProps<{ things?: any[]; somethingElse?: string }>(), { " @@ -376,7 +376,7 @@ withDefaults(defineProps<{ things?: any[]; somethingElse?: string }>(), { import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot' @@ -402,9 +402,9 @@ withDefaults(defineProps<{ things?: any[]; somethingElse?: string }>(), { import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component' import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot' " diff --git a/test/basic.test.ts b/test/basic.test.ts index d4039d31cd..98958a47f4 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -1960,12 +1960,12 @@ describe('server components/islands', () => { await page.waitForLoadState('networkidle') await page.getByText('Go to page without lazy server component').click() - const text = (await page.innerText('pre')).replaceAll(/ data-island-uid="([^"]*)"/g, '').replace(/data-island-component="([^"]*)"/g, (_, content) => `data-island-component="${content.split('-')[0]}"`) + const text = (await page.innerText('pre')).replaceAll(/ data-island-uid="([^"]*)"/g, '').replace(/data-island-component="([^"]*)"/g, 'data-island-component') if (isWebpack) { expect(text).toMatchInlineSnapshot('" End page
This is a .server (20ms) async component that was very long ...
42
Sugar Counter 12 x 1 = 12
This is a .server (20ms) async component that was very long ...
42
Sugar Counter 12 x 1 = 12
ServerWithClient.server.vue :

count: 0

This component should not be preloaded
a
b
c
This is not interactive
Sugar Counter 12 x 1 = 12
The component below is not a slot but declared as interactive
Sugar Counter 12 x 1 = 12
"') } else { - expect(text).toMatchInlineSnapshot('" End page
This is a .server (20ms) async component that was very long ...
42
Sugar Counter 12 x 1 = 12
This is a .server (20ms) async component that was very long ...
42
Sugar Counter 12 x 1 = 12
ServerWithClient.server.vue :

count: 0

This component should not be preloaded
a
b
c
This is not interactive
Sugar Counter 12 x 1 = 12
The component below is not a slot but declared as interactive
"') + expect(text).toMatchInlineSnapshot('" End page
This is a .server (20ms) async component that was very long ...
42
Sugar Counter 12 x 1 = 12
This is a .server (20ms) async component that was very long ...
42
Sugar Counter 12 x 1 = 12
ServerWithClient.server.vue :

count: 0

This component should not be preloaded
a
b
c
This is not interactive
Sugar Counter 12 x 1 = 12
The component below is not a slot but declared as interactive
"') } expect(text).toContain('async component that was very long') @@ -2316,7 +2316,7 @@ describe('component islands', () => { const { components } = result result.components = {} result.slots = {} - result.html = result.html.replace(/ data-island-component="([^"]*)"/g, (_, content) => ` data-island-component="${content.split('-')[0]}"`) + result.html = result.html.replace(/data-island-component="([^"]*)"/g, 'data-island-component') const teleportsEntries = Object.entries(components || {}) @@ -2327,12 +2327,11 @@ describe('component islands', () => { "link": [], "style": [], }, - "html": "
ServerWithClient.server.vue :

count: 0

This component should not be preloaded
a
b
c
This is not interactive
Sugar Counter 12 x 1 = 12
The component below is not a slot but declared as interactive
", + "html": "
ServerWithClient.server.vue :

count: 0

This component should not be preloaded
a
b
c
This is not interactive
Sugar Counter 12 x 1 = 12
The component below is not a slot but declared as interactive
", "slots": {}, } `) expect(teleportsEntries).toHaveLength(1) - expect(teleportsEntries[0]![0].startsWith('Counter-')).toBeTruthy() expect(teleportsEntries[0]![1].props).toMatchInlineSnapshot(` { "multiplier": 1,