From f2abae5dd78dbbe1d0a0101a2a11c91c404bba1b Mon Sep 17 00:00:00 2001 From: julien huang Date: Sat, 4 Mar 2023 17:47:56 +0100 Subject: [PATCH 01/11] feat(nuxt): forward layout component ref to NuxtLayout --- packages/nuxt/src/app/components/layout.ts | 52 ++++++++++++++----- test/basic.test.ts | 32 ++++++++++++ test/fixtures/basic/layouts/custom.vue | 17 ++++++ test/fixtures/basic/layouts/custom2.vue | 17 ++++++ .../basic/pages/expose-wrapper/layout.vue | 36 +++++++++++++ 5 files changed, 142 insertions(+), 12 deletions(-) create mode 100644 test/fixtures/basic/pages/expose-wrapper/layout.vue diff --git a/packages/nuxt/src/app/components/layout.ts b/packages/nuxt/src/app/components/layout.ts index 99f0add3082f..46d0101ca063 100644 --- a/packages/nuxt/src/app/components/layout.ts +++ b/packages/nuxt/src/app/components/layout.ts @@ -1,5 +1,6 @@ -import type { Ref, VNode } from 'vue' -import { computed, defineComponent, h, inject, nextTick, onMounted, Transition, unref } from 'vue' +import type { Ref } from 'vue' +import { markRaw, computed, defineComponent, h, inject, nextTick, onMounted, Transition, unref, VNode } from 'vue' + import type { RouteLocationNormalizedLoaded } from 'vue-router' import { _wrapIf } from './utils' import { useRoute } from '#app/composables/router' @@ -21,6 +22,9 @@ const LayoutLoader = defineComponent({ async setup (props, context) { let vnode: VNode + const exposed = markRaw({}) as Record + context.expose(exposed) + if (process.dev && process.client) { onMounted(() => { nextTick(() => { @@ -34,14 +38,22 @@ const LayoutLoader = defineComponent({ const LayoutComponent = await layouts[props.name]().then((r: any) => r.default || r) return () => { - if (process.dev && process.client && props.hasTransition) { - vnode = h(LayoutComponent, context.attrs, context.slots) - return vnode + vnode = h(LayoutComponent, context.attrs, context.slots) + + if (process.client) { + nextTick(() => { + Object.keys(exposed).forEach(key => delete exposed[key]) + if (vnode && vnode.component && vnode.component.exposed) { + Object.assign(exposed, vnode.component.exposed) + } + }) } - return h(LayoutComponent, context.attrs, context.slots) + + return vnode } } }) + export default defineComponent({ name: 'NuxtLayout', inheritAttrs: false, @@ -57,6 +69,9 @@ export default defineComponent({ const route = injectedRoute === useRoute() ? useVueRouterRoute() : injectedRoute const layout = computed(() => unref(props.name) ?? route.meta.layout as string ?? 'default') + const exposed = markRaw({}) as Record + context.expose(exposed) + let vnode: VNode let _layout: string | false if (process.dev && process.client) { @@ -79,12 +94,25 @@ export default defineComponent({ // We avoid rendering layout transition if there is no layout to render return _wrapIf(Transition, hasLayout && transitionProps, { - default: () => _wrapIf(LayoutLoader, hasLayout && { - key: layout.value, - name: layout.value, - ...(process.dev ? { hasTransition: !!transitionProps } : {}), - ...context.attrs - }, context.slots).default() + default: () => { + const layoutNode = _wrapIf(LayoutLoader, hasLayout && { + key: layout.value, + name: layout.value, + ...(process.dev ? { hasTransition: !!transitionProps } : {}), + ...context.attrs + }, context.slots).default() + + if (process.client) { + nextTick(() => { + Object.keys(exposed).forEach(key => delete exposed[key]) + if (layoutNode && layoutNode.component && layoutNode.component.exposed) { + Object.assign(exposed, layoutNode.component.exposed) + } + }) + } + + return layoutNode + } }).default() } } diff --git a/test/basic.test.ts b/test/basic.test.ts index c098ad5ac2a2..99c87b32b62b 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -263,6 +263,38 @@ describe('pages', () => { .then(results => results.forEach(isVisible => expect(isVisible).toBeTruthy())) }) + it('/expose-wrapper/layout', async () => { + await expectNoClientErrors('/expose-wrapper/layout') + + let lastLog: string|undefined + const page = await createPage('/expose-wrapper/layout') + page.on('console', (log) => { + lastLog = log.text() + }) + page.on('pageerror', (log) => { + lastLog = log.message + }) + await page.waitForLoadState('networkidle') + await page.locator('#log-foo').click() + expect(lastLog).toContain('.logFoo is not a function') + await page.locator('#log-hello').click() + expect(lastLog).toContain('world') + await page.locator('#add-count').click() + expect(await page.locator('#count').innerText()).toContain('1') + + // change page + await page.locator('#swap-layout').click() + expect(await page.locator('#count').innerText()).toContain('0') + await page.locator('#log-foo').click() + expect(lastLog).toContain('bar') + await page.locator('#log-hello').click() + expect(lastLog).toContain('.logHello is not a function') + await page.locator('#add-count').click() + expect(await page.locator('#count').innerText()).toContain('1') + await page.locator('#swap-layout').click() + expect(await page.locator('#count').innerText()).toContain('0') + }) + it('/client-only-explicit-import', async () => { const html = await $fetch('/client-only-explicit-import') diff --git a/test/fixtures/basic/layouts/custom.vue b/test/fixtures/basic/layouts/custom.vue index e7938d8f69d6..3d711503dc71 100644 --- a/test/fixtures/basic/layouts/custom.vue +++ b/test/fixtures/basic/layouts/custom.vue @@ -2,5 +2,22 @@
Custom Layout: + +
+ {{ count }} +
+
+ + diff --git a/test/fixtures/basic/layouts/custom2.vue b/test/fixtures/basic/layouts/custom2.vue index 35236542c813..fa52c6f1b8f5 100644 --- a/test/fixtures/basic/layouts/custom2.vue +++ b/test/fixtures/basic/layouts/custom2.vue @@ -2,5 +2,22 @@
Custom2 Layout: + +
+ {{ count }} +
+
+ + diff --git a/test/fixtures/basic/pages/expose-wrapper/layout.vue b/test/fixtures/basic/pages/expose-wrapper/layout.vue new file mode 100644 index 000000000000..473712421fa5 --- /dev/null +++ b/test/fixtures/basic/pages/expose-wrapper/layout.vue @@ -0,0 +1,36 @@ + + + From 8bb5d96d8ee2ad29491132ae35d39f83c9d7703f Mon Sep 17 00:00:00 2001 From: julien huang Date: Sun, 5 Mar 2023 17:33:11 +0100 Subject: [PATCH 02/11] chore: lint --- packages/nuxt/src/app/components/layout.ts | 4 ++-- test/fixtures/basic/layouts/custom.vue | 4 +++- test/fixtures/basic/layouts/custom2.vue | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/nuxt/src/app/components/layout.ts b/packages/nuxt/src/app/components/layout.ts index 46d0101ca063..3bb7deae93fa 100644 --- a/packages/nuxt/src/app/components/layout.ts +++ b/packages/nuxt/src/app/components/layout.ts @@ -1,5 +1,5 @@ -import type { Ref } from 'vue' -import { markRaw, computed, defineComponent, h, inject, nextTick, onMounted, Transition, unref, VNode } from 'vue' +import type { Ref, VNode } from 'vue' +import { markRaw, computed, defineComponent, h, inject, nextTick, onMounted, Transition, unref } from 'vue' import type { RouteLocationNormalizedLoaded } from 'vue-router' import { _wrapIf } from './utils' diff --git a/test/fixtures/basic/layouts/custom.vue b/test/fixtures/basic/layouts/custom.vue index 3d711503dc71..07cc6de950ee 100644 --- a/test/fixtures/basic/layouts/custom.vue +++ b/test/fixtures/basic/layouts/custom.vue @@ -6,7 +6,9 @@
{{ count }}
- + diff --git a/test/fixtures/basic/layouts/custom2.vue b/test/fixtures/basic/layouts/custom2.vue index fa52c6f1b8f5..40737327e9eb 100644 --- a/test/fixtures/basic/layouts/custom2.vue +++ b/test/fixtures/basic/layouts/custom2.vue @@ -6,7 +6,9 @@
{{ count }}
- + From ff007087fb8cf3b83eee6a78dad1c886648803fb Mon Sep 17 00:00:00 2001 From: julien huang Date: Sun, 5 Mar 2023 19:23:52 +0100 Subject: [PATCH 03/11] fix: use props to tranfer exposed data --- packages/nuxt/src/app/components/layout.ts | 10 ++++++---- test/basic.test.ts | 6 +++--- .../{expose-wrapper => wrapper-expose}/layout.vue | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) rename test/fixtures/basic/pages/{expose-wrapper => wrapper-expose}/layout.vue (83%) diff --git a/packages/nuxt/src/app/components/layout.ts b/packages/nuxt/src/app/components/layout.ts index 3bb7deae93fa..a3740cfbb316 100644 --- a/packages/nuxt/src/app/components/layout.ts +++ b/packages/nuxt/src/app/components/layout.ts @@ -17,14 +17,13 @@ const LayoutLoader = defineComponent({ inheritAttrs: false, props: { name: String, + exposed: Object as Record, ...process.dev ? { hasTransition: Boolean } : {} }, async setup (props, context) { let vnode: VNode - - const exposed = markRaw({}) as Record - context.expose(exposed) - + // eslint-disable-next-line vue/no-setup-props-destructure + const { exposed } = props if (process.dev && process.client) { onMounted(() => { nextTick(() => { @@ -42,8 +41,10 @@ const LayoutLoader = defineComponent({ if (process.client) { nextTick(() => { + // @ts-expect-error Object.keys(exposed).forEach(key => delete exposed[key]) if (vnode && vnode.component && vnode.component.exposed) { + // @ts-expect-error Object.assign(exposed, vnode.component.exposed) } }) @@ -98,6 +99,7 @@ export default defineComponent({ const layoutNode = _wrapIf(LayoutLoader, hasLayout && { key: layout.value, name: layout.value, + exposed, ...(process.dev ? { hasTransition: !!transitionProps } : {}), ...context.attrs }, context.slots).default() diff --git a/test/basic.test.ts b/test/basic.test.ts index 99c87b32b62b..74d84d18e8cb 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -263,11 +263,11 @@ describe('pages', () => { .then(results => results.forEach(isVisible => expect(isVisible).toBeTruthy())) }) - it('/expose-wrapper/layout', async () => { - await expectNoClientErrors('/expose-wrapper/layout') + it('/wrapper-expose/layout', async () => { + await expectNoClientErrors('/wrapper-expose/layout') let lastLog: string|undefined - const page = await createPage('/expose-wrapper/layout') + const page = await createPage('/wrapper-expose/layout') page.on('console', (log) => { lastLog = log.text() }) diff --git a/test/fixtures/basic/pages/expose-wrapper/layout.vue b/test/fixtures/basic/pages/wrapper-expose/layout.vue similarity index 83% rename from test/fixtures/basic/pages/expose-wrapper/layout.vue rename to test/fixtures/basic/pages/wrapper-expose/layout.vue index 473712421fa5..3a37834c63cd 100644 --- a/test/fixtures/basic/pages/expose-wrapper/layout.vue +++ b/test/fixtures/basic/pages/wrapper-expose/layout.vue @@ -8,7 +8,7 @@ +
{{layout}}
@@ -29,7 +29,7 @@ function logHello () { } function swapLayout () { - const newLayout = currentLayout === 'customLayout' ? 'customLayout2' : 'customLayout' + const newLayout = currentLayout === 'custom2' ? 'custom' : 'custom2' setPageLayout(newLayout) currentLayout = newLayout } From 1edb9fe830dd3bdec30b1809bdf4cb3a77b2d9e8 Mon Sep 17 00:00:00 2001 From: julien huang Date: Sun, 5 Mar 2023 23:02:31 +0100 Subject: [PATCH 04/11] test: fix fixtures --- test/basic.test.ts | 31 ++++++++++--------- test/fixtures/basic/layouts/custom.vue | 10 +++--- test/fixtures/basic/layouts/custom2.vue | 6 ++-- .../basic/pages/wrapper-expose/layout.vue | 15 +++++---- 4 files changed, 32 insertions(+), 30 deletions(-) diff --git a/test/basic.test.ts b/test/basic.test.ts index 74d84d18e8cb..5bae089bf9a1 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -275,24 +275,27 @@ describe('pages', () => { lastLog = log.message }) await page.waitForLoadState('networkidle') - await page.locator('#log-foo').click() + await page.locator('.log-foo').first().click() expect(lastLog).toContain('.logFoo is not a function') - await page.locator('#log-hello').click() + await page.locator('.log-hello').first().click() expect(lastLog).toContain('world') - await page.locator('#add-count').click() - expect(await page.locator('#count').innerText()).toContain('1') - - // change page - await page.locator('#swap-layout').click() - expect(await page.locator('#count').innerText()).toContain('0') - await page.locator('#log-foo').click() + await page.locator('.add-count').first().click() + expect(await page.locator('.count').first().innerText()).toContain('1') + + // change layout + await page.locator('.swap-layout').click() + await page.waitForTimeout(25) + expect(await page.locator('.count').first().innerText()).toContain('0') + await page.locator('.log-foo').first().click() expect(lastLog).toContain('bar') - await page.locator('#log-hello').click() + await page.locator('.log-hello').first().click() expect(lastLog).toContain('.logHello is not a function') - await page.locator('#add-count').click() - expect(await page.locator('#count').innerText()).toContain('1') - await page.locator('#swap-layout').click() - expect(await page.locator('#count').innerText()).toContain('0') + await page.locator('.add-count').first().click() + expect(await page.locator('.count').first().innerText()).toContain('1') + // change layout + await page.locator('.swap-layout').click() + await page.waitForTimeout(25) + expect(await page.locator('.count').first().innerText()).toContain('0') }) it('/client-only-explicit-import', async () => { diff --git a/test/fixtures/basic/layouts/custom.vue b/test/fixtures/basic/layouts/custom.vue index 07cc6de950ee..7321cc97a740 100644 --- a/test/fixtures/basic/layouts/custom.vue +++ b/test/fixtures/basic/layouts/custom.vue @@ -3,10 +3,10 @@ Custom Layout: -
+
{{ count }}
-
@@ -15,11 +15,11 @@ diff --git a/test/fixtures/basic/layouts/custom2.vue b/test/fixtures/basic/layouts/custom2.vue index 40737327e9eb..9abe74108ac9 100644 --- a/test/fixtures/basic/layouts/custom2.vue +++ b/test/fixtures/basic/layouts/custom2.vue @@ -3,10 +3,10 @@ Custom2 Layout: -
+
{{ count }}
-
@@ -16,7 +16,7 @@ const count = ref(0) function logFoo () { - console.log('foo') + console.log('bar') } defineExpose({ diff --git a/test/fixtures/basic/pages/wrapper-expose/layout.vue b/test/fixtures/basic/pages/wrapper-expose/layout.vue index 3a37834c63cd..4d2212e5a3ad 100644 --- a/test/fixtures/basic/pages/wrapper-expose/layout.vue +++ b/test/fixtures/basic/pages/wrapper-expose/layout.vue @@ -1,21 +1,21 @@ From 3b56708ab6af49afa11b6ad7772aa4c48e1f240b Mon Sep 17 00:00:00 2001 From: julien huang Date: Mon, 6 Mar 2023 00:02:02 +0100 Subject: [PATCH 05/11] test: fix fixtures --- test/fixtures/basic/layouts/with-props.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/fixtures/basic/layouts/with-props.vue b/test/fixtures/basic/layouts/with-props.vue index 1d87ad7b544f..5f2a714f7a63 100644 --- a/test/fixtures/basic/layouts/with-props.vue +++ b/test/fixtures/basic/layouts/with-props.vue @@ -1,6 +1,8 @@ +```` + ::ReadMore{link="/docs/guide/directory-structure/layouts"} :: From e6f703e6a713155201b2a9ef624a4f9120391160 Mon Sep 17 00:00:00 2001 From: julien huang Date: Sat, 10 Jun 2023 23:24:24 +0200 Subject: [PATCH 09/11] refactor: rename to layoutRef --- packages/nuxt/src/app/components/layout.ts | 2 +- test/fixtures/basic/pages/wrapper-expose/layout.vue | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/nuxt/src/app/components/layout.ts b/packages/nuxt/src/app/components/layout.ts index 85177f5b4b33..74b93bd96a95 100644 --- a/packages/nuxt/src/app/components/layout.ts +++ b/packages/nuxt/src/app/components/layout.ts @@ -58,7 +58,7 @@ export default defineComponent({ const layout = computed(() => unref(props.name) ?? route.meta.layout as string ?? 'default') const layoutRef = ref() - context.expose({ layout: layoutRef }) + context.expose({ layoutRef }) let vnode: VNode let _layout: string | false diff --git a/test/fixtures/basic/pages/wrapper-expose/layout.vue b/test/fixtures/basic/pages/wrapper-expose/layout.vue index 55c4774bee51..04edce5bf3be 100644 --- a/test/fixtures/basic/pages/wrapper-expose/layout.vue +++ b/test/fixtures/basic/pages/wrapper-expose/layout.vue @@ -22,10 +22,10 @@ definePageMeta({ }) function logFoo () { - layout.value.layout.logFoo() + layout.value.layoutRef.logFoo() } function logHello () { - layout.value.layout.logHello() + layout.value.layoutRef.logHello() } function swapLayout () { From 42060466a0d1662835e910e769db79ab974e5abf Mon Sep 17 00:00:00 2001 From: Julien Huang Date: Sat, 10 Jun 2023 23:49:17 +0200 Subject: [PATCH 10/11] Update packages/nuxt/src/app/components/layout.ts Co-authored-by: Daniel Roe --- packages/nuxt/src/app/components/layout.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/nuxt/src/app/components/layout.ts b/packages/nuxt/src/app/components/layout.ts index 74b93bd96a95..bf59878de3c1 100644 --- a/packages/nuxt/src/app/components/layout.ts +++ b/packages/nuxt/src/app/components/layout.ts @@ -36,8 +36,11 @@ const LayoutLoader = defineComponent({ const LayoutComponent = await layouts[props.name]().then((r: any) => r.default || r) return () => { - vnode = h(LayoutComponent, mergeProps(context.attrs, { ref: props.layoutRef }), context.slots) - return vnode + if (process.dev && process.client && props.hasTransition) { + vnode = h(LayoutComponent, mergeProps(context.attrs, { ref: props.layoutRef }), context.slots) + return vnode + } + return h(LayoutComponent, mergeProps(context.attrs, { ref: props.layoutRef }), context.slots) } } }) From ee661cedcf0380296c2515309b4712002b95cb17 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Sat, 10 Jun 2023 23:05:46 +0100 Subject: [PATCH 11/11] chore: remove extra prop --- packages/nuxt/src/app/components/layout.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/nuxt/src/app/components/layout.ts b/packages/nuxt/src/app/components/layout.ts index bf59878de3c1..a44892639aa7 100644 --- a/packages/nuxt/src/app/components/layout.ts +++ b/packages/nuxt/src/app/components/layout.ts @@ -16,7 +16,6 @@ const LayoutLoader = defineComponent({ inheritAttrs: false, props: { name: String, - exposed: Object as Record, layoutRef: Object as () => VNodeRef, ...process.dev ? { hasTransition: Boolean } : {} },