From 74231878ea39132aa1da38807e83b195694d4ca4 Mon Sep 17 00:00:00 2001 From: tbitw2549 Date: Fri, 30 Aug 2024 00:04:39 +0300 Subject: [PATCH] chore: update components with documentation --- .../runtime/client-delayed-component.ts | 45 +++++++++++++++---- test/basic.test.ts | 8 ++-- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/packages/nuxt/src/components/runtime/client-delayed-component.ts b/packages/nuxt/src/components/runtime/client-delayed-component.ts index 806cabc3a..e5f908ac7 100644 --- a/packages/nuxt/src/components/runtime/client-delayed-component.ts +++ b/packages/nuxt/src/components/runtime/client-delayed-component.ts @@ -1,5 +1,6 @@ -import { defineAsyncComponent, defineComponent, h, hydrateOnIdle, hydrateOnInteraction, hydrateOnMediaQuery, hydrateOnVisible, ref, watch } from 'vue' +import { defineAsyncComponent, defineComponent, getCurrentInstance, h, hydrateOnIdle, hydrateOnInteraction, hydrateOnMediaQuery, hydrateOnVisible, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue' import type { AsyncComponentLoader, HydrationStrategy } from 'vue' +import { onNuxtReady, useNuxtApp } from '#app' /* @__NO_SIDE_EFFECTS__ */ export const createLazyIOComponent = (loader: AsyncComponentLoader) => { @@ -9,7 +10,12 @@ export const createLazyIOComponent = (loader: AsyncComponentLoader) => { if (import.meta.server) { return () => h(defineAsyncComponent(loader), attrs) } - return () => h(defineAsyncComponent({ loader, hydrate: hydrateOnVisible(attrs.hydrate as IntersectionObserverInit | undefined) })) + const ready = ref(false) + const nuxt = useNuxtApp() + const instance = getCurrentInstance()! + onNuxtReady(() => ready.value = true) + // This is a hack to prevent hydration mismatches for all hydration strategies + return () => ready.value ? h(defineAsyncComponent({ loader, hydrate: hydrateOnVisible(attrs.hydrate as IntersectionObserverInit | undefined) })) : nuxt.isHydrating && instance.vnode.el ? h('div', attrs) : null }, }) } @@ -22,7 +28,12 @@ export const createLazyNetworkComponent = (loader: AsyncComponentLoader) => { if (import.meta.server) { return () => h(defineAsyncComponent(loader), attrs) } - return () => h(defineAsyncComponent({ loader, hydrate: hydrateOnIdle(attrs.hydrate as number | undefined) })) + const ready = ref(false) + const nuxt = useNuxtApp() + const instance = getCurrentInstance()! + onNuxtReady(() => ready.value = true) + // This one seems to work fine due to the intended use case + return () => ready.value ? h(defineAsyncComponent({ loader, hydrate: hydrateOnIdle(attrs.hydrate as number | undefined) })) : nuxt.isHydrating && instance.vnode.el ? h('div', attrs) : null }, }) } @@ -35,8 +46,12 @@ export const createLazyEventComponent = (loader: AsyncComponentLoader) => { if (import.meta.server) { return () => h(defineAsyncComponent(loader), attrs) } + const ready = ref(false) + const nuxt = useNuxtApp() + const instance = getCurrentInstance()! + onNuxtReady(() => ready.value = true) const events: Array = attrs.hydrate as Array ?? ['mouseover'] - return () => h(defineAsyncComponent({ loader, hydrate: hydrateOnInteraction(events) })) + return () => ready.value ? h(defineAsyncComponent({ loader, hydrate: hydrateOnInteraction(events) })) : nuxt.isHydrating && instance.vnode.el ? h('div', attrs) : null }, }) } @@ -49,7 +64,12 @@ export const createLazyMediaComponent = (loader: AsyncComponentLoader) => { if (import.meta.server) { return () => h(defineAsyncComponent(loader), attrs) } - return () => h(defineAsyncComponent({ loader, hydrate: hydrateOnMediaQuery(attrs.hydrate ?? '(min-width: 1px)') })) + const ready = ref(false) + const nuxt = useNuxtApp() + const instance = getCurrentInstance()! + onNuxtReady(() => ready.value = true) + // This one, unlike others, can cause a hydration mismatch even a whole minute after the page loads. Given a query of min-width: 1200px, with a small window, the moment the window expands to at least 1200 it hydrates and causes a hydration mismatch. + return () => ready.value ? h(defineAsyncComponent({ loader, hydrate: hydrateOnMediaQuery(attrs.hydrate ?? '(min-width: 1px)') })) : nuxt.isHydrating && instance.vnode.el ? h('div', attrs) : null }, }) } @@ -62,12 +82,21 @@ export const createLazyIfComponent = (loader: AsyncComponentLoader) => { if (import.meta.server) { return () => h(defineAsyncComponent(loader), attrs) } + const ready = ref(false) + const nuxt = useNuxtApp() + const instance = getCurrentInstance()! + onNuxtReady(() => ready.value = true) const shouldHydrate = ref(!!(attrs.hydrate ?? true)) const strategy: HydrationStrategy = (hydrate) => { - const unwatch = watch(shouldHydrate, () => hydrate(), { once: true }) - return () => unwatch() + if (!shouldHydrate.value) { + const unwatch = watch(shouldHydrate, () => hydrate(), { once: true }) + return () => unwatch() + } + hydrate() + return () => {} } - return () => h(defineAsyncComponent({ loader, hydrate: strategy })) + // This one seems to work fine whenever the hydration condition is achieved at client side. For example, a hydration condition of a ref greater than 2 with a button to increment causes no hydration mismatch after 3 presses of the button. + return () => ready.value ? h(defineAsyncComponent({ loader, hydrate: strategy })) : nuxt.isHydrating && instance.vnode.el ? h('div', attrs) : null }, }) } diff --git a/test/basic.test.ts b/test/basic.test.ts index a70b17db6..373835b44 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -2698,8 +2698,8 @@ describe('lazy import components', () => { expect(await page.locator('body').getByText('This shouldn\'t be visible at first with network!').all()).toHaveLength(1) expect(await page.locator('body').getByText('This should be visible at first with viewport!').all()).toHaveLength(1) expect(await page.locator('body').getByText('This should be visible at first with events!').all()).toHaveLength(2) - // The default value is immediately truthy - //expect(await page.locator('body').getByText('This should be visible at first with conditions!').all()).toHaveLength(2) + // The default value is immediately truthy, however, there is a hydration mismatch without the hack + expect(await page.locator('body').getByText('This should be visible at first with conditions!').all()).toHaveLength(2) const component = page.locator('#lazyevent') const rect = (await component.boundingBox())! await page.mouse.move(rect.x + rect.width / 2, rect.y + rect.height / 2) @@ -2707,11 +2707,11 @@ describe('lazy import components', () => { expect(await page.locator('body').getByText('This shouldn\'t be visible at first with events!').all()).toHaveLength(1) await page.locator('#conditionbutton').click() await page.waitForLoadState('networkidle') - //expect(await page.locator('body').getByText('This shouldn\'t be visible at first with conditions!').all()).toHaveLength(2) + expect(await page.locator('body').getByText('This shouldn\'t be visible at first with conditions!').all()).toHaveLength(2) await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)) await page.waitForLoadState('networkidle') expect(await page.locator('body').getByText('This shouldn\'t be visible at first with viewport!').all()).toHaveLength(1) - //expect(await page.locator('body').getByText('This should never be visible!').all()).toHaveLength(1) + expect(await page.locator('body').getByText('This should always be visible!').all()).toHaveLength(1) await page.close() })