diff --git a/packages/nuxt/src/app/components/nuxt-island.ts b/packages/nuxt/src/app/components/nuxt-island.ts index 503d3e34a8..784132a752 100644 --- a/packages/nuxt/src/app/components/nuxt-island.ts +++ b/packages/nuxt/src/app/components/nuxt-island.ts @@ -1,4 +1,4 @@ -import type { Component } from 'vue' +import type { Component, PropType } from 'vue' import { Fragment, Teleport, computed, createStaticVNode, createVNode, defineComponent, getCurrentInstance, h, nextTick, onMounted, ref, toRaw, watch, withMemo } from 'vue' import { debounce } from 'perfect-debounce' import { hash } from 'ohash' @@ -59,6 +59,10 @@ export default defineComponent({ type: Object, default: () => ({}), }, + scopeId: { + type: String as PropType, + default: () => undefined, + }, source: { type: String, default: () => undefined, @@ -131,6 +135,10 @@ export default defineComponent({ const currentSlots = Object.keys(slots) let html = ssrHTML.value + if (props.scopeId) { + html = html.replace(/^<[^> ]*/, full => full + ' ' + props.scopeId) + } + if (import.meta.client && !canLoadClientComponent.value) { for (const [key, value] of Object.entries(payloads.components || {})) { html = html.replace(new RegExp(` data-island-uid="${uid.value}" data-island-component="${key}"[^>]*>`), (full) => { diff --git a/packages/nuxt/src/components/runtime/server-component.ts b/packages/nuxt/src/components/runtime/server-component.ts index bea615b2dc..c5ecee9b2e 100644 --- a/packages/nuxt/src/components/runtime/server-component.ts +++ b/packages/nuxt/src/components/runtime/server-component.ts @@ -1,4 +1,4 @@ -import { defineComponent, h, ref } from 'vue' +import { defineComponent, getCurrentInstance, h, ref } from 'vue' import NuxtIsland from '#app/components/nuxt-island' import { useRoute } from '#app/composables/router' import { isPrerendered } from '#app/composables/payload' @@ -11,6 +11,7 @@ export const createServerComponent = (name: string) => { props: { lazy: Boolean }, emits: ['error'], setup (props, { attrs, slots, expose, emit }) { + const vm = getCurrentInstance() const islandRef = ref(null) expose({ @@ -22,6 +23,7 @@ export const createServerComponent = (name: string) => { name, lazy: props.lazy, props: attrs, + scopeId: vm?.vnode.scopeId, ref: islandRef, onError: (err) => { emit('error', err) diff --git a/test/nuxt/nuxt-island.test.ts b/test/nuxt/nuxt-island.test.ts index e9cb75ae11..a12123884f 100644 --- a/test/nuxt/nuxt-island.test.ts +++ b/test/nuxt/nuxt-island.test.ts @@ -1,6 +1,6 @@ import { beforeEach } from 'node:test' import { describe, expect, it, vi } from 'vitest' -import { h, nextTick } from 'vue' +import { defineComponent, h, nextTick, popScopeId, pushScopeId } from 'vue' import { mountSuspended } from '@nuxt/test-utils/runtime' import { createServerComponent } from '../../packages/nuxt/src/components/runtime/server-component' import { createSimpleRemoteIslandProvider } from '../fixtures/remote-provider' @@ -133,6 +133,19 @@ describe('runtime server component', () => { expect(wrapper.emitted('error')).toHaveLength(1) vi.mocked(fetch).mockReset() }) + + it('expect NuxtIsland to have parent scopeId', async () => { + const wrapper = await mountSuspended(defineComponent({ + render () { + pushScopeId('data-v-654e2b21') + const vnode = h(createServerComponent('dummyName')) + popScopeId() + return vnode + }, + })) + + expect(wrapper.find('*').attributes()).toHaveProperty('data-v-654e2b21') + }) }) describe('client components', () => { @@ -186,7 +199,7 @@ describe('client components', () => { expect(fetch).toHaveBeenCalledOnce() expect(wrapper.html()).toMatchInlineSnapshot(` - "
hello
+ "
hello
client component
@@ -212,7 +225,7 @@ describe('client components', () => { await wrapper.vm.$.exposed!.refresh() await nextTick() expect(wrapper.html()).toMatchInlineSnapshot(` - "
hello
+ "
hello
fallback
" @@ -305,7 +318,7 @@ describe('client components', () => { }) expect(fetch).toHaveBeenCalledOnce() expect(wrapper.html()).toMatchInlineSnapshot(` - "
hello
+ "
hello
slot in client component