Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions packages/compiler-ssr/src/transforms/ssrTransformElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,13 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
| InterpolationNode
| undefined
// If interpolation, this is dynamic <textarea> content, potentially
// injected by v-model and takes higher priority than v-bind value
if (!existingText || existingText.type !== NodeTypes.INTERPOLATION) {
// injected by v-model and takes higher priority than v-bind value.
// Additionally, directives with content overrides (v-text/v-html)
// have higher priority than the merged props.
if (
!hasContentOverrideDirective(node) &&
(!existingText || existingText.type !== NodeTypes.INTERPOLATION)
) {
// <textarea> with dynamic v-bind. We don't know if the final props
// will contain .value, so we will have to do something special:
// assign the merged props to a temp variable, and check whether
Expand Down Expand Up @@ -176,9 +181,8 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
]
}
} else if (directives.length && !node.children.length) {
// v-text directive has higher priority than the merged props
const vText = findDir(node, 'text')
if (!vText) {
// v-text/v-html have higher priority than the merged props
if (!hasContentOverrideDirective(node)) {
const tempId = `_temp${context.temps++}`
propsExp.arguments = [
createAssignmentExpression(
Expand Down Expand Up @@ -449,6 +453,10 @@ function findVModel(node: PlainElementNode): DirectiveNode | undefined {
) as DirectiveNode | undefined
}

function hasContentOverrideDirective(node: PlainElementNode): boolean {
return !!findDir(node, 'text') || !!findDir(node, 'html')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This existing line here: https://github.com/vuejs/core/pull/13975/files#diff-40b0d34e7fda1abbad1716852aa8a9db97fdbfce639710889dc62423f247a834R230 actually already handles v-html output being correct since it would result in rawChildrenMap always being updated. Because of this, the v-html lookup done here is not strictly necessary for v-html SSR output to work but including it here would at least short-circuit past the _ssrInterpolate expression node creation which is desired in this case; I also think including it here makes the logic more expressive.

}

export function ssrProcessElement(
node: PlainElementNode,
context: SSRTransformContext,
Expand Down
33 changes: 33 additions & 0 deletions packages/server-renderer/__tests__/ssrDirectives.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,17 @@ describe('ssr: directives', () => {
).toBe(`<input type="checkbox" value="foo">`)
})

test('element with v-html', async () => {
expect(
await renderToString(
createApp({
data: () => ({ foo: 'hello' }),
template: `<span v-html="foo"/>`,
}),
),
).toBe(`<span>hello</span>`)
})

test('textarea', async () => {
expect(
await renderToString(
Expand All @@ -183,6 +194,28 @@ describe('ssr: directives', () => {
).toBe(`<textarea>hello</textarea>`)
})

test('textarea with v-text', async () => {
expect(
await renderToString(
createApp({
data: () => ({ foo: 'hello' }),
template: `<textarea v-text="foo"/>`,
}),
),
).toBe(`<textarea>hello</textarea>`)
})

test('textarea with v-html', async () => {
expect(
await renderToString(
createApp({
data: () => ({ foo: 'hello' }),
template: `<textarea v-html="foo"/>`,
}),
),
).toBe(`<textarea>hello</textarea>`)
})

test('dynamic type', async () => {
expect(
await renderToString(
Expand Down
Loading