perf(nuxt): enable Transition component only on client side (#30720)

This commit is contained in:
Alex Liu 2025-01-25 01:16:26 +08:00 committed by Daniel Roe
parent c6056bd07d
commit e96a96dbd9
No known key found for this signature in database
GPG Key ID: CBC814C393D93268
4 changed files with 34 additions and 13 deletions

View File

@ -1,12 +1,12 @@
import type { DefineComponent, MaybeRef, VNode } from 'vue'
import { Suspense, Transition, computed, defineComponent, h, inject, mergeProps, nextTick, onMounted, provide, ref, unref } from 'vue'
import { Suspense, computed, defineComponent, h, inject, mergeProps, nextTick, onMounted, provide, ref, unref } from 'vue'
import type { RouteLocationNormalizedLoaded } from 'vue-router'
import type { PageMeta } from '../../pages/runtime/composables'
import { useRoute, useRouter } from '../composables/router'
import { useNuxtApp } from '../nuxt'
import { _wrapIf } from './utils'
import { _wrapInTransition } from './utils'
import { LayoutMetaSymbol, PageRouteSymbol } from './injections'
// @ts-expect-error virtual file
@ -80,7 +80,7 @@ export default defineComponent({
const transitionProps = route.meta.layoutTransition ?? defaultLayoutTransition
// We avoid rendering layout transition if there is no layout to render
return _wrapIf(Transition, hasLayout && transitionProps, {
return _wrapInTransition(hasLayout && transitionProps, {
default: () => h(Suspense, { suspensible: true, onResolve: () => { nextTick(done) } }, {
default: () => h(
LayoutProvider,

View File

@ -1,5 +1,5 @@
import { createStaticVNode, h } from 'vue'
import type { Component, RendererNode, VNode } from 'vue'
import { Transition, createStaticVNode, h } from 'vue'
import type { RendererNode, VNode } from 'vue'
// eslint-disable-next-line
import { isString, isPromise, isArray, isObject } from '@vue/shared'
import type { RouteLocationNormalized } from 'vue-router'
@ -10,9 +10,8 @@ import { START_LOCATION } from '#build/pages'
* Internal utility
* @private
*/
export const _wrapIf = (component: Component, props: any, slots: any) => {
props = props === true ? {} : props
return { default: () => props ? h(component, props, slots) : slots.default?.() }
export const _wrapInTransition = (props: any, children: any) => {
return { default: () => import.meta.client && props ? h(Transition, props === true ? {} : props, children) : children.default?.() }
}
const ROUTE_KEY_PARENTHESES_RE = /(:\w+)\([^)]+\)/g

View File

@ -1,4 +1,4 @@
import { Fragment, Suspense, Transition, defineComponent, h, inject, nextTick, ref, watch } from 'vue'
import { Fragment, Suspense, defineComponent, h, inject, nextTick, ref, watch } from 'vue'
import type { KeepAliveProps, TransitionProps, VNode } from 'vue'
import { RouterView } from 'vue-router'
import { defu } from 'defu'
@ -9,7 +9,7 @@ import type { RouterViewSlotProps } from './utils'
import { RouteProvider } from '#app/components/route-provider'
import { useNuxtApp } from '#app/nuxt'
import { useRouter } from '#app/composables/router'
import { _wrapIf } from '#app/components/utils'
import { _wrapInTransition } from '#app/components/utils'
import { LayoutMetaSymbol, PageRouteSymbol } from '#app/components/injections'
// @ts-expect-error virtual file
import { appKeepalive as defaultKeepaliveConfig, appPageTransition as defaultPageTransition } from '#build/nuxt.config.mjs'
@ -101,8 +101,30 @@ export default defineComponent({
nuxtApp.callHook('page:loading:end')
pageLoadingEndHookAlreadyCalled = true
}
previousPageKey = key
if (import.meta.server) {
vnode = h(Suspense, {
suspensible: true,
}, {
default: () => {
const providerVNode = h(RouteProvider, {
key: key || undefined,
vnode: slots.default ? h(Fragment, undefined, slots.default(routeProps)) : routeProps.Component,
route: routeProps.route,
renderKey: key || undefined,
vnodeRef: pageRef,
})
return providerVNode
},
})
return vnode
}
// Client side rendering
const hasTransition = !!(props.transition ?? routeProps.route.meta.pageTransition ?? defaultPageTransition)
const transitionProps = hasTransition && _mergeTransitionProps([
props.transition,
@ -112,7 +134,7 @@ export default defineComponent({
].filter(Boolean))
const keepaliveConfig = props.keepalive ?? routeProps.route.meta.keepalive ?? (defaultKeepaliveConfig as KeepAliveProps)
vnode = _wrapIf(Transition, hasTransition && transitionProps,
vnode = _wrapInTransition(hasTransition && transitionProps,
wrapInKeepAlive(keepaliveConfig, h(Suspense, {
suspensible: true,
onPending: () => nuxtApp.callHook('page:start', routeProps.Component),
@ -134,7 +156,7 @@ export default defineComponent({
trackRootNodes: hasTransition,
vnodeRef: pageRef,
})
if (import.meta.client && keepaliveConfig) {
if (keepaliveConfig) {
(providerVNode.type as any).name = (routeProps.Component.type as any).name || (routeProps.Component.type as any).__name || 'RouteProvider'
}
return providerVNode

View File

@ -78,7 +78,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
const serverDir = join(rootDir, '.output-inline/server')
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"566k"`)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"564k"`)
const modules = await analyzeSizes(['node_modules/**/*'], serverDir)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"92.3k"`)