Skip to content

Commit eb40a87

Browse files
skirtles-codelynxlangya
authored andcommitted
fix(compiler-dom): restrict createStaticVNode usage with option elements (vuejs#10846)
close vuejs#6568 close vuejs#7434
1 parent 7a29f31 commit eb40a87

File tree

3 files changed

+91
-0
lines changed

3 files changed

+91
-0
lines changed

packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap

+32
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3+
exports[`stringify static html > should bail for <option> elements with number values 1`] = `
4+
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
5+
6+
const _hoisted_1 = /*#__PURE__*/_createElementVNode("select", null, [
7+
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
8+
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
9+
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
10+
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
11+
/*#__PURE__*/_createElementVNode("option", { value: 1 })
12+
], -1 /* HOISTED */)
13+
const _hoisted_2 = [
14+
_hoisted_1
15+
]
16+
17+
return function render(_ctx, _cache) {
18+
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
19+
}"
20+
`;
21+
322
exports[`stringify static html > should bail on bindings that are hoisted but not stringifiable 1`] = `
423
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
524
@@ -20,6 +39,19 @@ return function render(_ctx, _cache) {
2039
}"
2140
`;
2241
42+
exports[`stringify static html > should work for <option> elements with string values 1`] = `
43+
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
44+
45+
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)
46+
const _hoisted_2 = [
47+
_hoisted_1
48+
]
49+
50+
return function render(_ctx, _cache) {
51+
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
52+
}"
53+
`;
54+
2355
exports[`stringify static html > should work with bindings that are non-static but stringifiable 1`] = `
2456
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
2557

packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts

+47
Original file line numberDiff line numberDiff line change
@@ -485,4 +485,51 @@ describe('stringify static html', () => {
485485
expect(code).toMatch(`<code>text1</code>`)
486486
expect(code).toMatchSnapshot()
487487
})
488+
489+
test('should work for <option> elements with string values', () => {
490+
const { ast, code } = compileWithStringify(
491+
`<div><select>${repeat(
492+
`<option value="1" />`,
493+
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
494+
)}</select></div>`,
495+
)
496+
// should be optimized now
497+
expect(ast.hoists).toMatchObject([
498+
{
499+
type: NodeTypes.JS_CALL_EXPRESSION,
500+
callee: CREATE_STATIC,
501+
arguments: [
502+
JSON.stringify(
503+
`<select>${repeat(
504+
`<option value="1"></option>`,
505+
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
506+
)}</select>`,
507+
),
508+
'1',
509+
],
510+
},
511+
{
512+
type: NodeTypes.JS_ARRAY_EXPRESSION,
513+
},
514+
])
515+
expect(code).toMatchSnapshot()
516+
})
517+
518+
test('should bail for <option> elements with number values', () => {
519+
const { ast, code } = compileWithStringify(
520+
`<div><select>${repeat(
521+
`<option :value="1" />`,
522+
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
523+
)}</select></div>`,
524+
)
525+
expect(ast.hoists).toMatchObject([
526+
{
527+
type: NodeTypes.VNODE_CALL,
528+
},
529+
{
530+
type: NodeTypes.JS_ARRAY_EXPRESSION,
531+
},
532+
])
533+
expect(code).toMatchSnapshot()
534+
})
488535
})

packages/compiler-dom/src/transforms/stringifyStatic.ts

+12
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
type TextCallNode,
1818
type TransformContext,
1919
createCallExpression,
20+
isStaticArgOf,
2021
} from '@vue/compiler-core'
2122
import {
2223
escapeHtml,
@@ -200,6 +201,7 @@ function analyzeNode(node: StringifiableNode): [number, number] | false {
200201
// probably only need to check for most common case
201202
// i.e. non-phrasing-content tags inside `<p>`
202203
function walk(node: ElementNode): boolean {
204+
const isOptionTag = node.tag === 'option' && node.ns === Namespaces.HTML
203205
for (let i = 0; i < node.props.length; i++) {
204206
const p = node.props[i]
205207
// bail on non-attr bindings
@@ -225,6 +227,16 @@ function analyzeNode(node: StringifiableNode): [number, number] | false {
225227
) {
226228
return bail()
227229
}
230+
// <option :value="1"> cannot be safely stringified
231+
if (
232+
isOptionTag &&
233+
isStaticArgOf(p.arg, 'value') &&
234+
p.exp &&
235+
p.exp.ast &&
236+
p.exp.ast.type !== 'StringLiteral'
237+
) {
238+
return bail()
239+
}
228240
}
229241
}
230242
for (let i = 0; i < node.children.length; i++) {

0 commit comments

Comments
 (0)