diff --git a/packages/compiler-ssr/__tests__/ssrElement.spec.ts b/packages/compiler-ssr/__tests__/ssrElement.spec.ts index f1d509acfb0..29b764cc1d1 100644 --- a/packages/compiler-ssr/__tests__/ssrElement.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrElement.spec.ts @@ -131,45 +131,45 @@ describe('ssr: element', () => { test('v-bind:class', () => { expect(getCompiledString(`
`)) .toMatchInlineSnapshot(` - "\`
\`" + }>\`" `) }) test('static class + v-bind:class', () => { expect(getCompiledString(`
`)) .toMatchInlineSnapshot(` - "\`
\`" + }>\`" `) }) test('v-bind:class + static class', () => { expect(getCompiledString(`
`)) .toMatchInlineSnapshot(` - "\`
\`" + }>\`" `) }) test('v-bind:style', () => { expect(getCompiledString(`
`)) .toMatchInlineSnapshot(` - "\`
\`" + }>\`" `) }) test('static style + v-bind:style', () => { expect(getCompiledString(`
`)) .toMatchInlineSnapshot(` - "\`
\`" + }>\`" `) }) diff --git a/packages/compiler-ssr/__tests__/ssrVShow.spec.ts b/packages/compiler-ssr/__tests__/ssrVShow.spec.ts index d0f3ec93036..74f72e714d0 100644 --- a/packages/compiler-ssr/__tests__/ssrVShow.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrVShow.spec.ts @@ -26,9 +26,9 @@ describe('ssr: v-show', () => { return function ssrRender(_ctx, _push, _parent, _attrs) { _push(\`
\`) + }>\`) }" `) }) @@ -41,12 +41,12 @@ describe('ssr: v-show', () => { return function ssrRender(_ctx, _push, _parent, _attrs) { _push(\`
\`) + }>\`) }" `) }) @@ -60,12 +60,12 @@ describe('ssr: v-show', () => { return function ssrRender(_ctx, _push, _parent, _attrs) { _push(\`
\`) + }>\`) }" `) }) @@ -81,13 +81,13 @@ describe('ssr: v-show', () => { return function ssrRender(_ctx, _push, _parent, _attrs) { _push(\`
\`) + }>\`) }" `) }) diff --git a/packages/compiler-ssr/src/transforms/ssrTransformElement.ts b/packages/compiler-ssr/src/transforms/ssrTransformElement.ts index 4a12b0f7ba7..339ae11c3d3 100644 --- a/packages/compiler-ssr/src/transforms/ssrTransformElement.ts +++ b/packages/compiler-ssr/src/transforms/ssrTransformElement.ts @@ -249,12 +249,10 @@ export const ssrTransformElement: NodeTransform = (node, context) => { } if (attrName === 'class') { openTag.push( - ` class="`, (dynamicClassBinding = createCallExpression( context.helper(SSR_RENDER_CLASS), [value], )), - `"`, ) } else if (attrName === 'style') { if (dynamicStyleBinding) { @@ -262,12 +260,10 @@ export const ssrTransformElement: NodeTransform = (node, context) => { mergeCall(dynamicStyleBinding, value) } else { openTag.push( - ` style="`, (dynamicStyleBinding = createCallExpression( context.helper(SSR_RENDER_STYLE), [value], )), - `"`, ) } } else { diff --git a/packages/server-renderer/__tests__/render.spec.ts b/packages/server-renderer/__tests__/render.spec.ts index d0a5223b2ff..0c57cc0096d 100644 --- a/packages/server-renderer/__tests__/render.spec.ts +++ b/packages/server-renderer/__tests__/render.spec.ts @@ -252,6 +252,29 @@ function testRender(type: string, render: typeof renderToString) { ) }) + test('undefined class/style excluded', async () => { + const app = createApp({ + template: `
`, + }) + expect(await render(app)).toBe(`
`) + }) + + test('null class/style excluded', async () => { + const app = createApp({ + template: `
`, + }) + expect(await render(app)).toBe(`
`) + }) + + test('empty string class/style not excluded', async () => { + const app = createApp({ + template: `
`, + }) + expect(await render(app)).toBe( + `
`, + ) + }) + test('template components with dynamic class attribute before static', async () => { const app = createApp({ template: `
`, @@ -261,6 +284,15 @@ function testRender(type: string, render: typeof renderToString) { ) }) + test('template components with both static and undefined dynamic class/style attributes', async () => { + const app = createApp({ + template: `
`, + }) + expect(await render(app)).toBe( + `
`, + ) + }) + test('mixing optimized / vnode / template components', async () => { const OptimizedChild = { props: ['msg'], diff --git a/packages/server-renderer/__tests__/ssrRenderAttrs.spec.ts b/packages/server-renderer/__tests__/ssrRenderAttrs.spec.ts index 984387bb864..894856e0d18 100644 --- a/packages/server-renderer/__tests__/ssrRenderAttrs.spec.ts +++ b/packages/server-renderer/__tests__/ssrRenderAttrs.spec.ts @@ -134,14 +134,16 @@ describe('ssr: renderClass', () => { }) test('standalone', () => { - expect(ssrRenderClass(`foo`)).toBe(`foo`) - expect(ssrRenderClass([`foo`, `bar`])).toBe(`foo bar`) - expect(ssrRenderClass({ foo: true, bar: false })).toBe(`foo`) - expect(ssrRenderClass([{ foo: true, bar: false }, `baz`])).toBe(`foo baz`) + expect(ssrRenderClass(`foo`)).toBe(` class="foo"`) + expect(ssrRenderClass([`foo`, `bar`])).toBe(` class="foo bar"`) + expect(ssrRenderClass({ foo: true, bar: false })).toBe(` class="foo"`) + expect(ssrRenderClass([{ foo: true, bar: false }, `baz`])).toBe( + ` class="foo baz"`, + ) }) test('escape class values', () => { - expect(ssrRenderClass(`"> { @@ -155,6 +157,11 @@ describe('ssr: renderClass', () => { className: ['foo', 'bar'], }), ).toBe(` class="foo,bar"`) + expect( + ssrRenderAttrs({ + className: undefined, + }), + ).toBe(``) }) }) @@ -172,18 +179,18 @@ describe('ssr: renderStyle', () => { }) test('standalone', () => { - expect(ssrRenderStyle(`color:red`)).toBe(`color:red`) + expect(ssrRenderStyle(`color:red`)).toBe(` style="color:red"`) expect( ssrRenderStyle({ color: `red`, }), - ).toBe(`color:red;`) + ).toBe(` style="color:red;"`) expect( ssrRenderStyle([ { color: `red` }, { fontSize: `15px` }, // case conversion ]), - ).toBe(`color:red;font-size:15px;`) + ).toBe(` style="color:red;font-size:15px;"`) }) test('number handling', () => { @@ -192,16 +199,16 @@ describe('ssr: renderStyle', () => { fontSize: null, // invalid value should be ignored opacity: 0.5, }), - ).toBe(`opacity:0.5;`) + ).toBe(` style="opacity:0.5;"`) }) test('escape inline CSS', () => { - expect(ssrRenderStyle(`"> { @@ -216,6 +223,8 @@ describe('ssr: renderStyle', () => { ':--v6': 0, '--foo': 1, }), - ).toBe(`--v1:initial;--v2:initial;--v3: ;--v4: ;--v5:foo;--v6:0;--foo:1;`) + ).toBe( + ` style="--v1:initial;--v2:initial;--v3: ;--v4: ;--v5:foo;--v6:0;--foo:1;"`, + ) }) }) diff --git a/packages/server-renderer/src/helpers/ssrRenderAttrs.ts b/packages/server-renderer/src/helpers/ssrRenderAttrs.ts index b082da03fe8..a2a6f4ab27b 100644 --- a/packages/server-renderer/src/helpers/ssrRenderAttrs.ts +++ b/packages/server-renderer/src/helpers/ssrRenderAttrs.ts @@ -39,10 +39,10 @@ export function ssrRenderAttrs( } const value = props[key] if (key === 'class') { - ret += ` class="${ssrRenderClass(value)}"` + ret += ssrRenderClass(value) } else if (key === 'style') { - ret += ` style="${ssrRenderStyle(value)}"` - } else if (key === 'className') { + ret += ssrRenderStyle(value) + } else if (key === 'className' && value != null) { ret += ` class="${String(value)}"` } else { ret += ssrRenderDynamicAttr(key, value, tag) @@ -86,10 +86,20 @@ export function ssrRenderAttr(key: string, value: unknown): string { } export function ssrRenderClass(raw: unknown): string { - return escapeHtml(normalizeClass(raw)) + if (raw == null) { + return '' + } + return ` class="${escapeHtml(normalizeClass(raw))}"` } export function ssrRenderStyle(raw: unknown): string { + if (raw == null) { + return '' + } + return ` style="${ssrRenderStyleHelper(raw)}"` +} + +function ssrRenderStyleHelper(raw: unknown): string { if (!raw) { return '' }