mirror of https://github.com/vuejs/core.git
fix(compiler-dom): restrict createStaticVNode usage with option elements (#10846)
close #6568 close #7434
This commit is contained in:
parent
c9c9dff805
commit
0e3d6178b0
|
|
@ -1,5 +1,24 @@
|
||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`stringify static html > should bail for <option> elements with number values 1`] = `
|
||||||
|
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
|
const _hoisted_1 = /*#__PURE__*/_createElementVNode("select", null, [
|
||||||
|
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
|
||||||
|
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
|
||||||
|
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
|
||||||
|
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
|
||||||
|
/*#__PURE__*/_createElementVNode("option", { value: 1 })
|
||||||
|
], -1 /* HOISTED */)
|
||||||
|
const _hoisted_2 = [
|
||||||
|
_hoisted_1
|
||||||
|
]
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`stringify static html > should bail on bindings that are hoisted but not stringifiable 1`] = `
|
exports[`stringify static html > should bail on bindings that are hoisted but not stringifiable 1`] = `
|
||||||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
|
|
@ -20,6 +39,19 @@ return function render(_ctx, _cache) {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`stringify static html > should work for <option> elements with string values 1`] = `
|
||||||
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
|
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<select><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option></select>", 1)
|
||||||
|
const _hoisted_2 = [
|
||||||
|
_hoisted_1
|
||||||
|
]
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`stringify static html > should work with bindings that are non-static but stringifiable 1`] = `
|
exports[`stringify static html > should work with bindings that are non-static but stringifiable 1`] = `
|
||||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -485,4 +485,51 @@ describe('stringify static html', () => {
|
||||||
expect(code).toMatch(`<code>text1</code>`)
|
expect(code).toMatch(`<code>text1</code>`)
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should work for <option> elements with string values', () => {
|
||||||
|
const { ast, code } = compileWithStringify(
|
||||||
|
`<div><select>${repeat(
|
||||||
|
`<option value="1" />`,
|
||||||
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||||
|
)}</select></div>`,
|
||||||
|
)
|
||||||
|
// should be optimized now
|
||||||
|
expect(ast.hoists).toMatchObject([
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: CREATE_STATIC,
|
||||||
|
arguments: [
|
||||||
|
JSON.stringify(
|
||||||
|
`<select>${repeat(
|
||||||
|
`<option value="1"></option>`,
|
||||||
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||||
|
)}</select>`,
|
||||||
|
),
|
||||||
|
'1',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should bail for <option> elements with number values', () => {
|
||||||
|
const { ast, code } = compileWithStringify(
|
||||||
|
`<div><select>${repeat(
|
||||||
|
`<option :value="1" />`,
|
||||||
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||||
|
)}</select></div>`,
|
||||||
|
)
|
||||||
|
expect(ast.hoists).toMatchObject([
|
||||||
|
{
|
||||||
|
type: NodeTypes.VNODE_CALL,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import {
|
||||||
type TextCallNode,
|
type TextCallNode,
|
||||||
type TransformContext,
|
type TransformContext,
|
||||||
createCallExpression,
|
createCallExpression,
|
||||||
|
isStaticArgOf,
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
import {
|
import {
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
|
|
@ -200,6 +201,7 @@ function analyzeNode(node: StringifiableNode): [number, number] | false {
|
||||||
// probably only need to check for most common case
|
// probably only need to check for most common case
|
||||||
// i.e. non-phrasing-content tags inside `<p>`
|
// i.e. non-phrasing-content tags inside `<p>`
|
||||||
function walk(node: ElementNode): boolean {
|
function walk(node: ElementNode): boolean {
|
||||||
|
const isOptionTag = node.tag === 'option' && node.ns === Namespaces.HTML
|
||||||
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]
|
||||||
// bail on non-attr bindings
|
// bail on non-attr bindings
|
||||||
|
|
@ -225,6 +227,16 @@ function analyzeNode(node: StringifiableNode): [number, number] | false {
|
||||||
) {
|
) {
|
||||||
return bail()
|
return bail()
|
||||||
}
|
}
|
||||||
|
// <option :value="1"> cannot be safely stringified
|
||||||
|
if (
|
||||||
|
isOptionTag &&
|
||||||
|
isStaticArgOf(p.arg, 'value') &&
|
||||||
|
p.exp &&
|
||||||
|
p.exp.ast &&
|
||||||
|
p.exp.ast.type !== 'StringLiteral'
|
||||||
|
) {
|
||||||
|
return bail()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let i = 0; i < node.children.length; i++) {
|
for (let i = 0; i < node.children.length; i++) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue