fix(compiler-dom): properly stringify v-html/v-text with constant value

fix #5439
close #5445
This commit is contained in:
Evan You 2022-05-13 08:57:43 +08:00
parent cd92654510
commit 6283b2ec41
5 changed files with 89 additions and 29 deletions

View File

@ -59,6 +59,7 @@ export {
PropsExpression PropsExpression
} from './transforms/transformElement' } from './transforms/transformElement'
export { processSlotOutlet } from './transforms/transformSlotOutlet' export { processSlotOutlet } from './transforms/transformSlotOutlet'
export { getConstantType } from './transforms/hoistStatic'
export { generateCodeFrame } from '@vue/shared' export { generateCodeFrame } from '@vue/shared'
// v2 compat only // v2 compat only

View File

@ -32,3 +32,23 @@ return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2)) return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
}" }"
`; `;
exports[`stringify static html stringify v-html 1`] = `
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode } = Vue
const _hoisted_1 = /*#__PURE__*/_createStaticVNode(\\"<pre data-type=\\\\\\"js\\\\\\"><code><span>show-it </span></code></pre><div class><span class>1</span><span class>2</span></div>\\", 2)
return function render(_ctx, _cache) {
return _hoisted_1
}"
`;
exports[`stringify static html stringify v-text 1`] = `
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode } = Vue
const _hoisted_1 = /*#__PURE__*/_createStaticVNode(\\"<pre data-type=\\\\\\"js\\\\\\"><code>&lt;span&gt;show-it &lt;/span&gt;</code></pre><div class><span class>1</span><span class>2</span></div>\\", 2)
return function render(_ctx, _cache) {
return _hoisted_1
}"
`;

View File

@ -433,4 +433,25 @@ describe('stringify static html', () => {
] ]
}) })
}) })
// #5439
test('stringify v-html', () => {
const { code } = compileWithStringify(`
<pre data-type="js"><code v-html="'&lt;span&gt;show-it &lt;/span&gt;'"></code></pre>
<div class>
<span class>1</span><span class>2</span>
</div>`)
expect(code).toMatch(`<code><span>show-it </span></code>`)
expect(code).toMatchSnapshot()
})
test('stringify v-text', () => {
const { code } = compileWithStringify(`
<pre data-type="js"><code v-text="'&lt;span&gt;show-it &lt;/span&gt;'"></code></pre>
<div class>
<span class>1</span><span class>2</span>
</div>`)
expect(code).toMatch(`<code>&lt;span&gt;show-it &lt;/span&gt;</code>`)
expect(code).toMatchSnapshot()
})
}) })

View File

@ -279,6 +279,7 @@ function stringifyElement(
context: TransformContext context: TransformContext
): string { ): string {
let res = `<${node.tag}` let res = `<${node.tag}`
let innerHTML = ''
for (let i = 0; i < node.props.length; i++) { for (let i = 0; i < node.props.length; i++) {
const p = node.props[i] const p = node.props[i]
if (p.type === NodeTypes.ATTRIBUTE) { if (p.type === NodeTypes.ATTRIBUTE) {
@ -286,28 +287,38 @@ function stringifyElement(
if (p.value) { if (p.value) {
res += `="${escapeHtml(p.value.content)}"` res += `="${escapeHtml(p.value.content)}"`
} }
} else if (p.type === NodeTypes.DIRECTIVE && p.name === 'bind') { } else if (p.type === NodeTypes.DIRECTIVE) {
const exp = p.exp as SimpleExpressionNode if (p.name === 'bind') {
if (exp.content[0] === '_') { const exp = p.exp as SimpleExpressionNode
// internally generated string constant references if (exp.content[0] === '_') {
// e.g. imported URL strings via compiler-sfc transformAssetUrl plugin // internally generated string constant references
res += ` ${(p.arg as SimpleExpressionNode).content}="__VUE_EXP_START__${ // e.g. imported URL strings via compiler-sfc transformAssetUrl plugin
exp.content res += ` ${
}__VUE_EXP_END__"` (p.arg as SimpleExpressionNode).content
continue }="__VUE_EXP_START__${exp.content}__VUE_EXP_END__"`
} continue
// constant v-bind, e.g. :foo="1"
let evaluated = evaluateConstant(exp)
if (evaluated != null) {
const arg = p.arg && (p.arg as SimpleExpressionNode).content
if (arg === 'class') {
evaluated = normalizeClass(evaluated)
} else if (arg === 'style') {
evaluated = stringifyStyle(normalizeStyle(evaluated))
} }
res += ` ${(p.arg as SimpleExpressionNode).content}="${escapeHtml( // constant v-bind, e.g. :foo="1"
evaluated let evaluated = evaluateConstant(exp)
)}"` if (evaluated != null) {
const arg = p.arg && (p.arg as SimpleExpressionNode).content
if (arg === 'class') {
evaluated = normalizeClass(evaluated)
} else if (arg === 'style') {
evaluated = stringifyStyle(normalizeStyle(evaluated))
}
res += ` ${(p.arg as SimpleExpressionNode).content}="${escapeHtml(
evaluated
)}"`
}
} else if (p.name === 'html') {
// #5439 v-html with constant value
// not sure why would anyone do this but it can happen
innerHTML = evaluateConstant(p.exp as SimpleExpressionNode)
} else if (p.name === 'text') {
innerHTML = escapeHtml(
toDisplayString(evaluateConstant(p.exp as SimpleExpressionNode))
)
} }
} }
} }
@ -315,8 +326,12 @@ function stringifyElement(
res += ` ${context.scopeId}` res += ` ${context.scopeId}`
} }
res += `>` res += `>`
for (let i = 0; i < node.children.length; i++) { if (innerHTML) {
res += stringifyNode(node.children[i], context) res += innerHTML
} else {
for (let i = 0; i < node.children.length; i++) {
res += stringifyNode(node.children[i], context)
}
} }
if (!isVoidTag(node.tag)) { if (!isVoidTag(node.tag)) {
res += `</${node.tag}>` res += `</${node.tag}>`

View File

@ -3,7 +3,8 @@ import {
createObjectProperty, createObjectProperty,
createSimpleExpression, createSimpleExpression,
TO_DISPLAY_STRING, TO_DISPLAY_STRING,
createCallExpression createCallExpression,
getConstantType
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { createDOMCompilerError, DOMErrorCodes } from '../errors' import { createDOMCompilerError, DOMErrorCodes } from '../errors'
@ -25,11 +26,13 @@ export const transformVText: DirectiveTransform = (dir, node, context) => {
createObjectProperty( createObjectProperty(
createSimpleExpression(`textContent`, true), createSimpleExpression(`textContent`, true),
exp exp
? createCallExpression( ? getConstantType(exp, context) > 0
context.helperString(TO_DISPLAY_STRING), ? exp
[exp], : createCallExpression(
loc context.helperString(TO_DISPLAY_STRING),
) [exp],
loc
)
: createSimpleExpression('', true) : createSimpleExpression('', true)
) )
] ]