From c07c88a6e0f5eae3bb5f3d5d17160fc1a7151dd4 Mon Sep 17 00:00:00 2001 From: 0xfe <2953427626@qq.com> Date: Tue, 30 Dec 2025 23:32:09 +0800 Subject: [PATCH 1/5] fix(ssr): add support for props modifiers in render functions --- .../server-renderer/__tests__/render.spec.ts | 17 +++++++++++++++++ .../src/helpers/ssrRenderAttrs.ts | 16 +++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/server-renderer/__tests__/render.spec.ts b/packages/server-renderer/__tests__/render.spec.ts index d0a5223b2ff..097350ccc21 100644 --- a/packages/server-renderer/__tests__/render.spec.ts +++ b/packages/server-renderer/__tests__/render.spec.ts @@ -1229,5 +1229,22 @@ function testRender(type: string, render: typeof renderToString) { // during the render phase expect(getterSpy).toHaveBeenCalledTimes(2) }) + test('props modifiers in render functions', async () => { + const app = createApp({ + setup() { + return () => + h( + 'div', + { + '^attr': 'attr', + '.prop': 'prop', + }, + 'Functional Component', + ) + }, + }) + const html = await render(app) + expect(html).toBe(`
Functional Component
`) + }) }) } diff --git a/packages/server-renderer/src/helpers/ssrRenderAttrs.ts b/packages/server-renderer/src/helpers/ssrRenderAttrs.ts index b082da03fe8..cbd4c21cc52 100644 --- a/packages/server-renderer/src/helpers/ssrRenderAttrs.ts +++ b/packages/server-renderer/src/helpers/ssrRenderAttrs.ts @@ -24,6 +24,16 @@ const shouldIgnoreProp = /*@__PURE__*/ makeMap( `,key,ref,innerHTML,textContent,ref_key,ref_for`, ) +const applyModifiers = (prop: string): string | null => { + if (prop.startsWith('^')) { + return prop.slice(1) // attribute binding + } + if (prop.startsWith('.')) { + return null // prop should be ignored + } + return prop // normal prop +} + export function ssrRenderAttrs( props: Record, tag?: string, @@ -45,7 +55,11 @@ export function ssrRenderAttrs( } else if (key === 'className') { ret += ` class="${String(value)}"` } else { - ret += ssrRenderDynamicAttr(key, value, tag) + const modifiedKey = applyModifiers(key) + if (modifiedKey === null) { + continue + } + ret += ssrRenderDynamicAttr(modifiedKey, value, tag) } } return ret From 97035af558b700f03420bc71487065baddaf8ebf Mon Sep 17 00:00:00 2001 From: Gianthard-cyh <45843411+Gianthard-cyh@users.noreply.github.com> Date: Tue, 30 Dec 2025 23:56:29 +0800 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/server-renderer/src/helpers/ssrRenderAttrs.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server-renderer/src/helpers/ssrRenderAttrs.ts b/packages/server-renderer/src/helpers/ssrRenderAttrs.ts index cbd4c21cc52..fa14425b927 100644 --- a/packages/server-renderer/src/helpers/ssrRenderAttrs.ts +++ b/packages/server-renderer/src/helpers/ssrRenderAttrs.ts @@ -26,10 +26,10 @@ const shouldIgnoreProp = /*@__PURE__*/ makeMap( const applyModifiers = (prop: string): string | null => { if (prop.startsWith('^')) { - return prop.slice(1) // attribute binding + return prop.slice(1) // force as attribute } if (prop.startsWith('.')) { - return null // prop should be ignored + return null // force as property (not rendered in SSR) } return prop // normal prop } From c8fb292d034d786b31977c82b753aa1011d8fed7 Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 1 Jan 2026 21:46:43 +0800 Subject: [PATCH 3/5] fix(ssr): simplify attribute modifier handling in ssrRenderAttrs --- .../src/helpers/ssrRenderAttrs.ts | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/packages/server-renderer/src/helpers/ssrRenderAttrs.ts b/packages/server-renderer/src/helpers/ssrRenderAttrs.ts index fa14425b927..b031079e92e 100644 --- a/packages/server-renderer/src/helpers/ssrRenderAttrs.ts +++ b/packages/server-renderer/src/helpers/ssrRenderAttrs.ts @@ -24,30 +24,24 @@ const shouldIgnoreProp = /*@__PURE__*/ makeMap( `,key,ref,innerHTML,textContent,ref_key,ref_for`, ) -const applyModifiers = (prop: string): string | null => { - if (prop.startsWith('^')) { - return prop.slice(1) // force as attribute - } - if (prop.startsWith('.')) { - return null // force as property (not rendered in SSR) - } - return prop // normal prop -} - export function ssrRenderAttrs( props: Record, tag?: string, ): string { let ret = '' - for (const key in props) { + for (let key in props) { if ( shouldIgnoreProp(key) || isOn(key) || - (tag === 'textarea' && key === 'value') + (tag === 'textarea' && key === 'value') || + // force as property (not rendered in SSR) + key.startsWith('.') ) { continue } const value = props[key] + // force as attribute + if (key.startsWith('^')) key = key.slice(1) if (key === 'class') { ret += ` class="${ssrRenderClass(value)}"` } else if (key === 'style') { @@ -55,11 +49,7 @@ export function ssrRenderAttrs( } else if (key === 'className') { ret += ` class="${String(value)}"` } else { - const modifiedKey = applyModifiers(key) - if (modifiedKey === null) { - continue - } - ret += ssrRenderDynamicAttr(modifiedKey, value, tag) + ret += ssrRenderDynamicAttr(key, value, tag) } } return ret From 43a462e34daaf31989395d625a9ced18996b2814 Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 1 Jan 2026 21:47:46 +0800 Subject: [PATCH 4/5] chore: format --- packages/server-renderer/__tests__/render.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server-renderer/__tests__/render.spec.ts b/packages/server-renderer/__tests__/render.spec.ts index 097350ccc21..b60e406c05d 100644 --- a/packages/server-renderer/__tests__/render.spec.ts +++ b/packages/server-renderer/__tests__/render.spec.ts @@ -1229,6 +1229,7 @@ function testRender(type: string, render: typeof renderToString) { // during the render phase expect(getterSpy).toHaveBeenCalledTimes(2) }) + test('props modifiers in render functions', async () => { const app = createApp({ setup() { From 0120fd2148ff6fb522ee2ce3a4adb5d6ae4af42a Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 1 Jan 2026 21:51:08 +0800 Subject: [PATCH 5/5] test(ssr): update test description for props modifiers --- packages/server-renderer/__tests__/render.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server-renderer/__tests__/render.spec.ts b/packages/server-renderer/__tests__/render.spec.ts index b60e406c05d..e0136cb5c97 100644 --- a/packages/server-renderer/__tests__/render.spec.ts +++ b/packages/server-renderer/__tests__/render.spec.ts @@ -1230,7 +1230,7 @@ function testRender(type: string, render: typeof renderToString) { expect(getterSpy).toHaveBeenCalledTimes(2) }) - test('props modifiers in render functions', async () => { + test('props modifiers in render attrs', async () => { const app = createApp({ setup() { return () =>