From f0442d0ddb571540550ea05ef6141f4b56124ffa Mon Sep 17 00:00:00 2001 From: Julien Huang Date: Wed, 6 Mar 2024 17:45:43 +0100 Subject: [PATCH] feat(nuxt): emit error if `` can't fetch island (#25798) --- docs/3.api/1.components/8.nuxt-island.md | 8 +++++ .../nuxt/src/app/components/nuxt-island.ts | 6 ++-- .../components/runtime/server-component.ts | 8 +++-- test/nuxt/nuxt-island.test.ts | 30 +++++++++++++++---- 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/docs/3.api/1.components/8.nuxt-island.md b/docs/3.api/1.components/8.nuxt-island.md index a7c3dfc99f..013cfb94c4 100644 --- a/docs/3.api/1.components/8.nuxt-island.md +++ b/docs/3.api/1.components/8.nuxt-island.md @@ -60,3 +60,11 @@ Some slots are reserved to `NuxtIsland` for special cases. - `refresh()` - **type**: `() => Promise` - **description**: force refetch the server component by refetching it. + +## Events + +- `error` + - **parameters**: + - **error**: + - **type**: `unknown` + - **description**: emitted when when `NuxtIsland` fails to fetch the new island. diff --git a/packages/nuxt/src/app/components/nuxt-island.ts b/packages/nuxt/src/app/components/nuxt-island.ts index 7191dcf747..80e64a6ea8 100644 --- a/packages/nuxt/src/app/components/nuxt-island.ts +++ b/packages/nuxt/src/app/components/nuxt-island.ts @@ -69,7 +69,8 @@ export default defineComponent({ default: false } }, - async setup (props, { slots, expose }) { + emits: ['error'], + async setup (props, { slots, expose, emit }) { let canTeleport = import.meta.server const teleportKey = ref(0) const key = ref(0) @@ -208,11 +209,12 @@ export default defineComponent({ } } catch (e) { error.value = e + emit('error', e) } } expose({ - refresh: () => fetchComponent(true) + refresh: () => fetchComponent(true), }) if (import.meta.hot) { diff --git a/packages/nuxt/src/components/runtime/server-component.ts b/packages/nuxt/src/components/runtime/server-component.ts index ca32c688a3..d34176b9fd 100644 --- a/packages/nuxt/src/components/runtime/server-component.ts +++ b/packages/nuxt/src/components/runtime/server-component.ts @@ -9,7 +9,8 @@ export const createServerComponent = (name: string) => { name, inheritAttrs: false, props: { lazy: Boolean }, - setup (props, { attrs, slots, expose }) { + emits: ['error'], + setup (props, { attrs, slots, expose, emit }) { const islandRef = ref(null) expose({ @@ -21,7 +22,10 @@ export const createServerComponent = (name: string) => { name, lazy: props.lazy, props: attrs, - ref: islandRef + ref: islandRef, + onError: (err) => { + emit('error', err) + } }, slots) } } diff --git a/test/nuxt/nuxt-island.test.ts b/test/nuxt/nuxt-island.test.ts index 5a40833c32..7187dff6d6 100644 --- a/test/nuxt/nuxt-island.test.ts +++ b/test/nuxt/nuxt-island.test.ts @@ -111,11 +111,32 @@ describe('runtime server component', () => { expect(component.html()).toBe('
2
') vi.mocked(fetch).mockReset() }) + + it('expect NuxtIsland to emit an error', async () => { + const stubFetch = vi.fn(() => { + throw new Error('fetch error') + }) + + vi.stubGlobal('fetch', stubFetch) + + const wrapper = await mountSuspended(createServerComponent('ErrorServerComponent'), { + props: { + name: 'Error', + props: { + force: true + } + }, + attachTo: 'body' + }) + + expect(fetch).toHaveBeenCalledOnce() + expect(wrapper.emitted('error')).toHaveLength(1) + vi.mocked(fetch).mockReset() + }) }) describe('client components', () => { - it('expect swapping nuxt-client should not trigger errors #25289', async () => { const mockPath = '/nuxt-client.js' const componentId = 'Client-12345' @@ -166,7 +187,7 @@ describe('client components', () => { expect(fetch).toHaveBeenCalledOnce() expect(wrapper.html()).toMatchInlineSnapshot(` - "
hello
+ "
hello
client component
@@ -192,7 +213,7 @@ describe('client components', () => { await wrapper.vm.$.exposed!.refresh() await nextTick() expect(wrapper.html()).toMatchInlineSnapshot(` - "
hello
+ "
hello
fallback
" @@ -202,7 +223,6 @@ describe('client components', () => { expectNoConsoleIssue() }) - it('should not replace nested client components data-island-uid', async () => { const componentId = 'Client-12345' @@ -286,7 +306,7 @@ describe('client components', () => { }) expect(fetch).toHaveBeenCalledOnce() expect(wrapper.html()).toMatchInlineSnapshot(` - "
hello
+ "
hello
slot in client component