diff --git a/docs/3.api/1.components/8.nuxt-island.md b/docs/3.api/1.components/8.nuxt-island.md index f592ab7d6b..35ff08d2c6 100644 --- a/docs/3.api/1.components/8.nuxt-island.md +++ b/docs/3.api/1.components/8.nuxt-island.md @@ -50,3 +50,9 @@ Every slot is interactive since the parent component is the one providing it. Some slots are reserved to `NuxtIsland` for special cases. - `#fallback`: Specify the content to be rendered before the island loads (if the component is lazy) or if `NuxtIsland` fails to fetch the component. + +## Ref + +- `refresh()` + - **type**: `() => Promise` + - **description**: force refetch the server component by refetching it. diff --git a/packages/nuxt/src/app/components/nuxt-island.ts b/packages/nuxt/src/app/components/nuxt-island.ts index 40162a15e3..994f453a45 100644 --- a/packages/nuxt/src/app/components/nuxt-island.ts +++ b/packages/nuxt/src/app/components/nuxt-island.ts @@ -46,7 +46,7 @@ export default defineComponent({ default: () => undefined } }, - async setup (props, { slots }) { + async setup (props, { slots, expose }) { const error = ref(null) const config = useRuntimeConfig() const nuxtApp = useNuxtApp() @@ -160,6 +160,10 @@ export default defineComponent({ } } + expose({ + refresh: () => fetchComponent(true) + }) + if (import.meta.hot) { import.meta.hot.on(`nuxt-server-component:${props.name}`, () => { fetchComponent(true) diff --git a/packages/nuxt/src/components/runtime/server-component.ts b/packages/nuxt/src/components/runtime/server-component.ts index 88b8ece949..3a2463b09d 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 } from 'vue' +import { defineComponent, h, ref } from 'vue' import NuxtIsland from '#app/components/nuxt-island' /*@__NO_SIDE_EFFECTS__*/ @@ -7,12 +7,19 @@ export const createServerComponent = (name: string) => { name, inheritAttrs: false, props: { lazy: Boolean }, - setup (props, { attrs, slots }) { + setup (props, { attrs, slots, expose }) { + const islandRef = ref(null) + + expose({ + refresh: () => islandRef.value?.refresh() + }) + return () => { return h(NuxtIsland, { name, lazy: props.lazy, - props: attrs + props: attrs, + ref: islandRef }, slots) } } diff --git a/test/nuxt/nuxt-island.test.ts b/test/nuxt/nuxt-island.test.ts index 858cf9039d..437e3fc77e 100644 --- a/test/nuxt/nuxt-island.test.ts +++ b/test/nuxt/nuxt-island.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest' -import { h } from 'vue' +import { h, nextTick } 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' @@ -65,4 +65,35 @@ describe('runtime server component', () => { await server.close() }) + + it('force refresh', async () => { + let count = 0 + const stubFetch = vi.fn(() => { + count++ + return { + id: '123', + html: `
${count}
`, + state: {}, + head: { + link: [], + style: [] + }, + json() { + return this + } + } + }) + vi.stubGlobal('fetch', stubFetch) + + const component = await mountSuspended(createServerComponent('dummyName')) + expect(fetch).toHaveBeenCalledOnce() + + expect(component.html()).toBe('
1
') + + await component.vm.$.exposed!.refresh() + expect(fetch).toHaveBeenCalledTimes(2) + await nextTick() + expect(component.html()).toBe('
2
') + vi.mocked(fetch).mockRestore() + }) })