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:
Evan You
2019-10-21 15:52:29 -04:00
parent a0d570b16d
commit 052febc127
13 changed files with 236 additions and 112 deletions

View File

@@ -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\\"])),

View File

@@ -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
]

View File

@@ -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)
}"
`;

View File

@@ -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)
}"
`;

View File

@@ -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]

View File

@@ -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()