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.
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\\"])),
`;
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
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\\"])),
`;
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
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\\"])),
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', () => {
nodeTransforms: [
transformIf,
transformFor,
- optimizeText,
+ transformText,
transformSlotOutlet,
transformElement
]
+++ /dev/null
-// 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)
-}"
-`;
--- /dev/null
+// 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)
+}"
+`;
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,
// 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]
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
return ast
}
-describe('compiler: optimize interpolation', () => {
+describe('compiler: transform text', () => {
test('no consecutive text', () => {
const root = transformWithTextOpt(`{{ foo }}`)
expect(root.children[0]).toMatchObject({
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()
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()
IF,
IF_BRANCH,
FOR,
+ TEXT_CALL,
// codegen
JS_CALL_EXPRESSION,
JS_OBJECT_EXPRESSION,
| CommentNode
| IfNode
| ForNode
+ | TextCallNode
export interface RootNode extends Node {
type: NodeTypes.ROOT
codegenNode: ForCodegenNode
}
+export interface TextCallNode extends Node {
+ type: NodeTypes.TEXT_CALL
+ content: TextNode | InterpolationNode | CompoundExpressionNode
+ codegenNode: CallExpression
+}
+
// We also include a number of JavaScript AST nodes for code generation.
// The AST is an intentionally minimal subset just to meet the exact needs of
// Vue render function generation.
case NodeTypes.INTERPOLATION:
genInterpolation(node, context)
break
+ case NodeTypes.TEXT_CALL:
+ genNode(node.codegenNode, context)
+ break
case NodeTypes.COMPOUND_EXPRESSION:
genCompoundExpression(node, context)
break
import { transformBind } from './transforms/vBind'
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
import { trackSlotScopes, trackVForSlotScopes } from './transforms/vSlot'
-import { optimizeText } from './transforms/optimizeText'
+import { transformText } from './transforms/transformText'
import { transformOnce } from './transforms/vOnce'
import { transformModel } from './transforms/vModel'
transformSlotOutlet,
transformElement,
trackSlotScopes,
- optimizeText,
+ transformText,
...(options.nodeTransforms || []) // user transforms
],
directiveTransforms: {
case NodeTypes.FOR:
return false
case NodeTypes.INTERPOLATION:
+ case NodeTypes.TEXT_CALL:
return isStaticNode(node.content, resultCache)
case NodeTypes.SIMPLE_EXPRESSION:
return node.isConstant
TemplateChildNode,
TextNode,
InterpolationNode,
- CompoundExpressionNode
+ CompoundExpressionNode,
+ createCallExpression
} from '../ast'
+import { TEXT, CREATE_VNODE } from '../runtimeHelpers'
+import { PatchFlags, PatchFlagNames } from '@vue/shared'
const isText = (
node: TemplateChildNode
// Merge adjacent text nodes and expressions into a single expression
// e.g. <div>abc {{ d }} {{ e }}</div> should have a single expression node as child.
-export const optimizeText: NodeTransform = node => {
+export const transformText: NodeTransform = (node, context) => {
if (node.type === NodeTypes.ROOT || node.type === NodeTypes.ELEMENT) {
// perform the transform on node exit so that all expressions have already
// been processed.
return () => {
const children = node.children
let currentContainer: CompoundExpressionNode | undefined = undefined
+ let hasText = false
+
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (isText(child)) {
+ hasText = true
for (let j = i + 1; j < children.length; j++) {
const next = children[j]
if (isText(next)) {
}
}
}
+
+ if (hasText && children.length > 1) {
+ // when an element has mixed text/element children, convert text nodes
+ // into createVNode(Text) calls.
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i]
+ if (isText(child) || child.type === NodeTypes.COMPOUND_EXPRESSION) {
+ const callArgs = [context.helper(TEXT), `null`, child]
+ if (child.type !== NodeTypes.TEXT) {
+ callArgs.push(
+ `${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`
+ )
+ }
+ children[i] = {
+ type: NodeTypes.TEXT_CALL,
+ content: child,
+ loc: child.loc,
+ codegenNode: createCallExpression(
+ context.helper(CREATE_VNODE),
+ callArgs
+ )
+ }
+ }
+ }
+ }
}
}
}
case NodeTypes.COMPOUND_EXPRESSION:
return node.children.some(c => isObject(c) && hasScopeRef(c, ids))
case NodeTypes.INTERPOLATION:
+ case NodeTypes.TEXT_CALL:
return hasScopeRef(node.content, ids)
+ case NodeTypes.TEXT:
+ case NodeTypes.COMMENT:
+ return false
default:
- // TextNode or CommentNode
+ if (__DEV__) {
+ const exhaustiveCheck: never = node
+ exhaustiveCheck
+ }
return false
}
}
}
return // terminal
}
- } else if (!optimized) {
+ } else if (!optimized && dynamicChildren == null) {
// unoptimized, full diff
patchProps(
el,