feat(nuxt): emit error if <NuxtIsland> can't fetch island (#25798)

This commit is contained in:
Julien Huang 2024-03-06 17:45:43 +01:00 committed by GitHub
parent 83314f1c95
commit f0442d0ddb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 43 additions and 9 deletions

View File

@ -60,3 +60,11 @@ Some slots are reserved to `NuxtIsland` for special cases.
- `refresh()` - `refresh()`
- **type**: `() => Promise<void>` - **type**: `() => Promise<void>`
- **description**: force refetch the server component by refetching it. - **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.

View File

@ -69,7 +69,8 @@ export default defineComponent({
default: false default: false
} }
}, },
async setup (props, { slots, expose }) { emits: ['error'],
async setup (props, { slots, expose, emit }) {
let canTeleport = import.meta.server let canTeleport = import.meta.server
const teleportKey = ref(0) const teleportKey = ref(0)
const key = ref(0) const key = ref(0)
@ -208,11 +209,12 @@ export default defineComponent({
} }
} catch (e) { } catch (e) {
error.value = e error.value = e
emit('error', e)
} }
} }
expose({ expose({
refresh: () => fetchComponent(true) refresh: () => fetchComponent(true),
}) })
if (import.meta.hot) { if (import.meta.hot) {

View File

@ -9,7 +9,8 @@ export const createServerComponent = (name: string) => {
name, name,
inheritAttrs: false, inheritAttrs: false,
props: { lazy: Boolean }, props: { lazy: Boolean },
setup (props, { attrs, slots, expose }) { emits: ['error'],
setup (props, { attrs, slots, expose, emit }) {
const islandRef = ref<null | typeof NuxtIsland>(null) const islandRef = ref<null | typeof NuxtIsland>(null)
expose({ expose({
@ -21,7 +22,10 @@ export const createServerComponent = (name: string) => {
name, name,
lazy: props.lazy, lazy: props.lazy,
props: attrs, props: attrs,
ref: islandRef ref: islandRef,
onError: (err) => {
emit('error', err)
}
}, slots) }, slots)
} }
} }

View File

@ -111,11 +111,32 @@ describe('runtime server component', () => {
expect(component.html()).toBe('<div>2</div>') expect(component.html()).toBe('<div>2</div>')
vi.mocked(fetch).mockReset() 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', () => { describe('client components', () => {
it('expect swapping nuxt-client should not trigger errors #25289', async () => { it('expect swapping nuxt-client should not trigger errors #25289', async () => {
const mockPath = '/nuxt-client.js' const mockPath = '/nuxt-client.js'
const componentId = 'Client-12345' const componentId = 'Client-12345'
@ -166,7 +187,7 @@ describe('client components', () => {
expect(fetch).toHaveBeenCalledOnce() expect(fetch).toHaveBeenCalledOnce()
expect(wrapper.html()).toMatchInlineSnapshot(` expect(wrapper.html()).toMatchInlineSnapshot(`
"<div data-island-uid="3">hello<div data-island-uid="3" data-island-component="Client-12345"> "<div data-island-uid="4">hello<div data-island-uid="4" data-island-component="Client-12345">
<div>client component</div> <div>client component</div>
</div> </div>
</div> </div>
@ -192,7 +213,7 @@ describe('client components', () => {
await wrapper.vm.$.exposed!.refresh() await wrapper.vm.$.exposed!.refresh()
await nextTick() await nextTick()
expect(wrapper.html()).toMatchInlineSnapshot(` expect(wrapper.html()).toMatchInlineSnapshot(`
"<div data-island-uid="3">hello<div> "<div data-island-uid="4">hello<div>
<div>fallback</div> <div>fallback</div>
</div> </div>
</div>" </div>"
@ -202,7 +223,6 @@ describe('client components', () => {
expectNoConsoleIssue() expectNoConsoleIssue()
}) })
it('should not replace nested client components data-island-uid', async () => { it('should not replace nested client components data-island-uid', async () => {
const componentId = 'Client-12345' const componentId = 'Client-12345'
@ -286,7 +306,7 @@ describe('client components', () => {
}) })
expect(fetch).toHaveBeenCalledOnce() expect(fetch).toHaveBeenCalledOnce()
expect(wrapper.html()).toMatchInlineSnapshot(` expect(wrapper.html()).toMatchInlineSnapshot(`
"<div data-island-uid="5">hello<div data-island-uid="5" data-island-component="ClientWithSlot-12345"> "<div data-island-uid="6">hello<div data-island-uid="6" data-island-component="ClientWithSlot-12345">
<div class="client-component"> <div class="client-component">
<div style="display: contents" data-island-uid="" data-island-slot="default"> <div style="display: contents" data-island-uid="" data-island-slot="default">
<div>slot in client component</div> <div>slot in client component</div>