feat(compiler): convert text mixed with elements into createVNode calls
This ensures they are tracked as dynamic children when inside blocks. Also guaruntees compiled vnodes always have vnode children in arrays so that they can skip normalizeVNode safely in optimized mode.
This commit is contained in:
@@ -5,13 +5,13 @@ exports[`compiler: integration tests function mode 1`] = `
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { toString: _toString, openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Comment: _Comment, Fragment: _Fragment, renderList: _renderList } = _Vue
|
||||
const { toString: _toString, openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Comment: _Comment, Fragment: _Fragment, renderList: _renderList, Text: _Text } = _Vue
|
||||
|
||||
return (_openBlock(), _createBlock(\\"div\\", {
|
||||
id: \\"foo\\",
|
||||
class: bar.baz
|
||||
}, [
|
||||
_toString(world.burn()),
|
||||
_createVNode(_Text, null, _toString(world.burn()), 1 /* TEXT */),
|
||||
(_openBlock(), ok
|
||||
? _createBlock(\\"div\\", { key: 0 }, \\"yes\\")
|
||||
: _createBlock(_Fragment, { key: 1 }, [\\"no\\"])),
|
||||
@@ -26,7 +26,7 @@ return function render() {
|
||||
`;
|
||||
|
||||
exports[`compiler: integration tests function mode w/ prefixIdentifiers: true 1`] = `
|
||||
"const { toString, openBlock, createVNode, createBlock, Comment, Fragment, renderList } = Vue
|
||||
"const { toString, openBlock, createVNode, createBlock, Comment, Fragment, renderList, Text } = Vue
|
||||
|
||||
return function render() {
|
||||
const _ctx = this
|
||||
@@ -34,7 +34,7 @@ return function render() {
|
||||
id: \\"foo\\",
|
||||
class: _ctx.bar.baz
|
||||
}, [
|
||||
toString(_ctx.world.burn()),
|
||||
createVNode(Text, null, toString(_ctx.world.burn()), 1 /* TEXT */),
|
||||
(openBlock(), (_ctx.ok)
|
||||
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
|
||||
: createBlock(Fragment, { key: 1 }, [\\"no\\"])),
|
||||
@@ -48,7 +48,7 @@ return function render() {
|
||||
`;
|
||||
|
||||
exports[`compiler: integration tests module mode 1`] = `
|
||||
"import { toString, openBlock, createVNode, createBlock, Comment, Fragment, renderList } from \\"vue\\"
|
||||
"import { toString, openBlock, createVNode, createBlock, Comment, Fragment, renderList, Text } from \\"vue\\"
|
||||
|
||||
export default function render() {
|
||||
const _ctx = this
|
||||
@@ -56,7 +56,7 @@ export default function render() {
|
||||
id: \\"foo\\",
|
||||
class: _ctx.bar.baz
|
||||
}, [
|
||||
toString(_ctx.world.burn()),
|
||||
createVNode(Text, null, toString(_ctx.world.burn()), 1 /* TEXT */),
|
||||
(openBlock(), (_ctx.ok)
|
||||
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
|
||||
: createBlock(Fragment, { key: 1 }, [\\"no\\"])),
|
||||
|
||||
@@ -21,7 +21,7 @@ import { transformIf } from '../src/transforms/vIf'
|
||||
import { transformFor } from '../src/transforms/vFor'
|
||||
import { transformElement } from '../src/transforms/transformElement'
|
||||
import { transformSlotOutlet } from '../src/transforms/transformSlotOutlet'
|
||||
import { optimizeText } from '../src/transforms/optimizeText'
|
||||
import { transformText } from '../src/transforms/transformText'
|
||||
|
||||
describe('compiler: transform', () => {
|
||||
test('context state', () => {
|
||||
@@ -243,7 +243,7 @@ describe('compiler: transform', () => {
|
||||
nodeTransforms: [
|
||||
transformIf,
|
||||
transformFor,
|
||||
optimizeText,
|
||||
transformText,
|
||||
transformSlotOutlet,
|
||||
transformElement
|
||||
]
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`compiler: optimize interpolation consecutive text 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { toString: _toString } = _Vue
|
||||
|
||||
return _toString(foo) + \\" bar \\" + _toString(baz)
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: optimize interpolation consecutive text between elements 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { createVNode: _createVNode, toString: _toString, createBlock: _createBlock, Fragment: _Fragment, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createBlock(_Fragment, null, [
|
||||
_createVNode(\\"div\\"),
|
||||
_toString(foo) + \\" bar \\" + _toString(baz),
|
||||
_createVNode(\\"div\\")
|
||||
]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: optimize interpolation consecutive text mixed with elements 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { createVNode: _createVNode, toString: _toString, createBlock: _createBlock, Fragment: _Fragment, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createBlock(_Fragment, null, [
|
||||
_createVNode(\\"div\\"),
|
||||
_toString(foo) + \\" bar \\" + _toString(baz),
|
||||
_createVNode(\\"div\\"),
|
||||
_toString(foo) + \\" bar \\" + _toString(baz),
|
||||
_createVNode(\\"div\\")
|
||||
]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: optimize interpolation no consecutive text 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { toString: _toString } = _Vue
|
||||
|
||||
return _toString(foo)
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: optimize interpolation with prefixIdentifiers: true 1`] = `
|
||||
"const { toString } = Vue
|
||||
|
||||
return function render() {
|
||||
const _ctx = this
|
||||
return toString(_ctx.foo) + \\" bar \\" + toString(_ctx.baz + _ctx.qux)
|
||||
}"
|
||||
`;
|
||||
@@ -0,0 +1,84 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`compiler: transform text consecutive text 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { toString: _toString } = _Vue
|
||||
|
||||
return _toString(foo) + \\" bar \\" + _toString(baz)
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform text consecutive text between elements 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { createVNode: _createVNode, toString: _toString, Text: _Text, createBlock: _createBlock, Fragment: _Fragment, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createBlock(_Fragment, null, [
|
||||
_createVNode(\\"div\\"),
|
||||
_createVNode(_Text, null, _toString(foo) + \\" bar \\" + _toString(baz), 1 /* TEXT */),
|
||||
_createVNode(\\"div\\")
|
||||
]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform text consecutive text mixed with elements 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { createVNode: _createVNode, toString: _toString, Text: _Text, createBlock: _createBlock, Fragment: _Fragment, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createBlock(_Fragment, null, [
|
||||
_createVNode(\\"div\\"),
|
||||
_createVNode(_Text, null, _toString(foo) + \\" bar \\" + _toString(baz), 1 /* TEXT */),
|
||||
_createVNode(\\"div\\"),
|
||||
_createVNode(_Text, null, \\"hello\\"),
|
||||
_createVNode(\\"div\\")
|
||||
]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform text no consecutive text 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { toString: _toString } = _Vue
|
||||
|
||||
return _toString(foo)
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform text text between elements (static) 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { createVNode: _createVNode, Text: _Text, createBlock: _createBlock, Fragment: _Fragment, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createBlock(_Fragment, null, [
|
||||
_createVNode(\\"div\\"),
|
||||
_createVNode(_Text, null, \\"hello\\"),
|
||||
_createVNode(\\"div\\")
|
||||
]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform text with prefixIdentifiers: true 1`] = `
|
||||
"const { toString } = Vue
|
||||
|
||||
return function render() {
|
||||
const _ctx = this
|
||||
return toString(_ctx.foo) + \\" bar \\" + toString(_ctx.baz + _ctx.qux)
|
||||
}"
|
||||
`;
|
||||
@@ -23,7 +23,7 @@ import { transformOn } from '../../src/transforms/vOn'
|
||||
import { transformBind } from '../../src/transforms/vBind'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { createObjectMatcher, genFlagText } from '../testUtils'
|
||||
import { optimizeText } from '../../src/transforms/optimizeText'
|
||||
import { transformText } from '../../src/transforms/transformText'
|
||||
|
||||
function parseWithElementTransform(
|
||||
template: string,
|
||||
@@ -36,7 +36,7 @@ function parseWithElementTransform(
|
||||
// block as root node
|
||||
const ast = parse(`<div>${template}</div>`, options)
|
||||
transform(ast, {
|
||||
nodeTransforms: [transformElement, optimizeText],
|
||||
nodeTransforms: [transformElement, transformText],
|
||||
...options
|
||||
})
|
||||
const codegenNode = (ast as any).children[0].children[0]
|
||||
|
||||
@@ -5,16 +5,19 @@ import {
|
||||
NodeTypes,
|
||||
generate
|
||||
} from '../../src'
|
||||
import { optimizeText } from '../../src/transforms/optimizeText'
|
||||
import { transformText } from '../../src/transforms/transformText'
|
||||
import { transformExpression } from '../../src/transforms/transformExpression'
|
||||
import { transformElement } from '../../src/transforms/transformElement'
|
||||
import { CREATE_VNODE, TEXT } from '../../src/runtimeHelpers'
|
||||
import { genFlagText } from '../testUtils'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
|
||||
function transformWithTextOpt(template: string, options: CompilerOptions = {}) {
|
||||
const ast = parse(template)
|
||||
transform(ast, {
|
||||
nodeTransforms: [
|
||||
...(options.prefixIdentifiers ? [transformExpression] : []),
|
||||
optimizeText,
|
||||
transformText,
|
||||
transformElement
|
||||
],
|
||||
...options
|
||||
@@ -22,7 +25,7 @@ function transformWithTextOpt(template: string, options: CompilerOptions = {}) {
|
||||
return ast
|
||||
}
|
||||
|
||||
describe('compiler: optimize interpolation', () => {
|
||||
describe('compiler: transform text', () => {
|
||||
test('no consecutive text', () => {
|
||||
const root = transformWithTextOpt(`{{ foo }}`)
|
||||
expect(root.children[0]).toMatchObject({
|
||||
@@ -55,14 +58,52 @@ describe('compiler: optimize interpolation', () => {
|
||||
expect(root.children.length).toBe(3)
|
||||
expect(root.children[0].type).toBe(NodeTypes.ELEMENT)
|
||||
expect(root.children[1]).toMatchObject({
|
||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||
children: [
|
||||
{ type: NodeTypes.INTERPOLATION, content: { content: `foo` } },
|
||||
` + `,
|
||||
{ type: NodeTypes.TEXT, content: ` bar ` },
|
||||
` + `,
|
||||
{ type: NodeTypes.INTERPOLATION, content: { content: `baz` } }
|
||||
]
|
||||
// when mixed with elements, should convert it into a text node call
|
||||
type: NodeTypes.TEXT_CALL,
|
||||
codegenNode: {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_VNODE,
|
||||
arguments: [
|
||||
TEXT,
|
||||
`null`,
|
||||
{
|
||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||
children: [
|
||||
{ type: NodeTypes.INTERPOLATION, content: { content: `foo` } },
|
||||
` + `,
|
||||
{ type: NodeTypes.TEXT, content: ` bar ` },
|
||||
` + `,
|
||||
{ type: NodeTypes.INTERPOLATION, content: { content: `baz` } }
|
||||
]
|
||||
},
|
||||
genFlagText(PatchFlags.TEXT)
|
||||
]
|
||||
}
|
||||
})
|
||||
expect(root.children[2].type).toBe(NodeTypes.ELEMENT)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('text between elements (static)', () => {
|
||||
const root = transformWithTextOpt(`<div/>hello<div/>`)
|
||||
expect(root.children.length).toBe(3)
|
||||
expect(root.children[0].type).toBe(NodeTypes.ELEMENT)
|
||||
expect(root.children[1]).toMatchObject({
|
||||
// when mixed with elements, should convert it into a text node call
|
||||
type: NodeTypes.TEXT_CALL,
|
||||
codegenNode: {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_VNODE,
|
||||
arguments: [
|
||||
TEXT,
|
||||
`null`,
|
||||
{
|
||||
type: NodeTypes.TEXT,
|
||||
content: `hello`
|
||||
}
|
||||
// should have no flag
|
||||
]
|
||||
}
|
||||
})
|
||||
expect(root.children[2].type).toBe(NodeTypes.ELEMENT)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
@@ -70,30 +111,47 @@ describe('compiler: optimize interpolation', () => {
|
||||
|
||||
test('consecutive text mixed with elements', () => {
|
||||
const root = transformWithTextOpt(
|
||||
`<div/>{{ foo }} bar {{ baz }}<div/>{{ foo }} bar {{ baz }}<div/>`
|
||||
`<div/>{{ foo }} bar {{ baz }}<div/>hello<div/>`
|
||||
)
|
||||
expect(root.children.length).toBe(5)
|
||||
expect(root.children[0].type).toBe(NodeTypes.ELEMENT)
|
||||
expect(root.children[1]).toMatchObject({
|
||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||
children: [
|
||||
{ type: NodeTypes.INTERPOLATION, content: { content: `foo` } },
|
||||
` + `,
|
||||
{ type: NodeTypes.TEXT, content: ` bar ` },
|
||||
` + `,
|
||||
{ type: NodeTypes.INTERPOLATION, content: { content: `baz` } }
|
||||
]
|
||||
type: NodeTypes.TEXT_CALL,
|
||||
codegenNode: {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_VNODE,
|
||||
arguments: [
|
||||
TEXT,
|
||||
`null`,
|
||||
{
|
||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||
children: [
|
||||
{ type: NodeTypes.INTERPOLATION, content: { content: `foo` } },
|
||||
` + `,
|
||||
{ type: NodeTypes.TEXT, content: ` bar ` },
|
||||
` + `,
|
||||
{ type: NodeTypes.INTERPOLATION, content: { content: `baz` } }
|
||||
]
|
||||
},
|
||||
genFlagText(PatchFlags.TEXT)
|
||||
]
|
||||
}
|
||||
})
|
||||
expect(root.children[2].type).toBe(NodeTypes.ELEMENT)
|
||||
expect(root.children[3]).toMatchObject({
|
||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||
children: [
|
||||
{ type: NodeTypes.INTERPOLATION, content: { content: `foo` } },
|
||||
` + `,
|
||||
{ type: NodeTypes.TEXT, content: ` bar ` },
|
||||
` + `,
|
||||
{ type: NodeTypes.INTERPOLATION, content: { content: `baz` } }
|
||||
]
|
||||
type: NodeTypes.TEXT_CALL,
|
||||
codegenNode: {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_VNODE,
|
||||
arguments: [
|
||||
TEXT,
|
||||
`null`,
|
||||
{
|
||||
type: NodeTypes.TEXT,
|
||||
content: `hello`
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
expect(root.children[4].type).toBe(NodeTypes.ELEMENT)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
Reference in New Issue
Block a user