diff --git a/packages/nuxt/src/components/tree-shake.ts b/packages/nuxt/src/components/tree-shake.ts index 1cb14dde5c4..23d869ddde9 100644 --- a/packages/nuxt/src/components/tree-shake.ts +++ b/packages/nuxt/src/components/tree-shake.ts @@ -6,7 +6,7 @@ import type { Component } from '@nuxt/schema' interface TreeShakeTemplatePluginOptions { sourcemap?: boolean - getComponents(): Component[] + getComponents (): Component[] } export const TreeShakeTemplatePlugin = createUnplugin((options: TreeShakeTemplatePluginOptions) => { @@ -26,7 +26,7 @@ export const TreeShakeTemplatePlugin = createUnplugin((options: TreeShakeTemplat .filter(c => c.mode === 'client' && !components.some(other => other.mode !== 'client' && other.pascalName === c.pascalName)) .map(c => `${c.pascalName}|${c.kebabName}`) .concat('ClientOnly|client-only') - .map(component => `<(${component})[^>]*>[\\s\\S]*?<\\/(${component})>`) + .map(component => `<(${component})(| [^>]*)>[\\s\\S]*?<\\/(${component})>`) regexpMap.set(components, new RegExp(`(${clientOnlyComponents.join('|')})`, 'g')) } @@ -34,8 +34,12 @@ export const TreeShakeTemplatePlugin = createUnplugin((options: TreeShakeTemplat const COMPONENTS_RE = regexpMap.get(components)! const s = new MagicString(code) - // Do not render client-only slots on SSR, but preserve attributes - s.replace(COMPONENTS_RE, r => r.replace(/<([^>]*[^/])\/?>[\s\S]*$/, '<$1 />')) + // Do not render client-only slots on SSR, but preserve attributes and fallback/placeholder slots + s.replace(COMPONENTS_RE, r => r.replace(/<([^>]*[^/])\/?>[\s\S]*$/, (chunk: string, el: string) => { + const fallback = chunk.match(/]*(#|v-slot:)(fallback|placeholder)[^>]*>[\s\S]*?<\/template>/)?.[0] || '' + const tag = el.split(' ').shift() + return `<${el}>${fallback}` + })) if (s.hasChanged()) { return { diff --git a/test/basic.test.ts b/test/basic.test.ts index 554d2bb8a1d..9116bc51db8 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -137,6 +137,8 @@ describe('pages', () => { const html = await $fetch('/client-only-components') expect(html).toContain('
') expect(html).toContain('
') + expect(html).toContain('
Fallback
') + expect(html).not.toContain('Should not be server rendered') await expectNoClientErrors('/client-only-components') }) diff --git a/test/fixtures/basic/pages/client-only-components.vue b/test/fixtures/basic/pages/client-only-components.vue index 940ce2fcd66..f020ea1cfc1 100644 --- a/test/fixtures/basic/pages/client-only-components.vue +++ b/test/fixtures/basic/pages/client-only-components.vue @@ -8,5 +8,11 @@
+ + Should not be server rendered. + +