diff --git a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap index 99b7dd6c0..9bf7229e5 100644 --- a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap @@ -5,7 +5,7 @@ exports[`compiler: integration tests function mode 1`] = ` return function render() { with (this) { - const { createVNode: _createVNode, toString: _toString, openBlock: _openBlock, createBlock: _createBlock, Empty: _Empty, Fragment: _Fragment, renderList: _renderList } = _Vue + const { toString: _toString, openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Empty: _Empty, Fragment: _Fragment, renderList: _renderList } = _Vue return _createVNode(\\"div\\", { id: \\"foo\\", @@ -19,7 +19,7 @@ return function render() { ])), _createVNode(_Fragment, null, _renderList(list, (value, index) => { return (_openBlock(), _createBlock(\\"div\\", null, [ - _createVNode(\\"span\\", null, _toString(value + index)) + _createVNode(\\"span\\", null, _toString(value + index), 1 /* TEXT */) ])) }), 128 /* UNKEYED_FRAGMENT */) ], 2 /* CLASS */) @@ -28,7 +28,7 @@ return function render() { `; exports[`compiler: integration tests function mode w/ prefixIdentifiers: true 1`] = ` -"const { createVNode, toString, openBlock, createBlock, Empty, Fragment, renderList } = Vue +"const { toString, openBlock, createVNode, createBlock, Empty, Fragment, renderList } = Vue return function render() { const _ctx = this @@ -44,7 +44,7 @@ return function render() { ])), createVNode(Fragment, null, renderList(_ctx.list, (value, index) => { return (openBlock(), createBlock(\\"div\\", null, [ - createVNode(\\"span\\", null, toString(value + index)) + createVNode(\\"span\\", null, toString(value + index), 1 /* TEXT */) ])) }), 128 /* UNKEYED_FRAGMENT */) ], 2 /* CLASS */) @@ -52,7 +52,7 @@ return function render() { `; exports[`compiler: integration tests module mode 1`] = ` -"import { createVNode, toString, openBlock, createBlock, Empty, Fragment, renderList } from \\"vue\\" +"import { toString, openBlock, createVNode, createBlock, Empty, Fragment, renderList } from \\"vue\\" export default function render() { const _ctx = this @@ -68,7 +68,7 @@ export default function render() { ])), createVNode(Fragment, null, renderList(_ctx.list, (value, index) => { return (openBlock(), createBlock(\\"div\\", null, [ - createVNode(\\"span\\", null, _toString(value + index)) + createVNode(\\"span\\", null, _toString(value + index), 1 /* TEXT */) ])) }), 128 /* UNKEYED_FRAGMENT */) ], 2 /* CLASS */) diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap index 03ab9da4b..d1810ae7a 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`compiler: transform component slots dynamically named slots 1`] = ` -"const { resolveComponent, createVNode, toString } = Vue +"const { toString, resolveComponent, createVNode } = Vue return function render() { const _ctx = this @@ -21,7 +21,7 @@ return function render() { `; exports[`compiler: transform component slots explicit default slot 1`] = ` -"const { resolveComponent, createVNode, toString } = Vue +"const { toString, resolveComponent, createVNode } = Vue return function render() { const _ctx = this @@ -37,7 +37,7 @@ return function render() { `; exports[`compiler: transform component slots implicit default slot 1`] = ` -"const { resolveComponent, createVNode } = Vue +"const { createVNode, resolveComponent } = Vue return function render() { const _ctx = this @@ -52,7 +52,7 @@ return function render() { `; exports[`compiler: transform component slots named slots 1`] = ` -"const { resolveComponent, createVNode, toString } = Vue +"const { toString, resolveComponent, createVNode } = Vue return function render() { const _ctx = this @@ -72,12 +72,12 @@ return function render() { `; exports[`compiler: transform component slots nested slots scoping 1`] = ` -"const { resolveComponent, createVNode, toString } = Vue +"const { toString, resolveComponent, createVNode } = Vue return function render() { const _ctx = this - const _component_Comp = resolveComponent(\\"Comp\\") const _component_Inner = resolveComponent(\\"Inner\\") + const _component_Comp = resolveComponent(\\"Comp\\") return createVNode(_component_Comp, null, { default: ({ foo }) => [ diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts index 7d2793c9b..f2aed954e 100644 --- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts @@ -26,6 +26,7 @@ import { transformStyle } from '../../src/transforms/transformStyle' import { transformBind } from '../../src/transforms/vBind' import { PatchFlags } from '@vue/shared' import { createObjectMatcher } from '../testUtils' +import { optimizeText } from '../../src/transforms/optimizeText' function parseWithElementTransform( template: string, @@ -36,7 +37,7 @@ function parseWithElementTransform( } { const ast = parse(template, options) transform(ast, { - nodeTransforms: [transformElement], + nodeTransforms: [optimizeText, transformElement], ...options }) const codegenNode = (ast.children[0] as ElementNode) @@ -562,6 +563,20 @@ describe('compiler: element transform', () => { }) } + test('TEXT', () => { + const { node } = parseWithBind(`
foo
`) + expect(node.arguments.length).toBe(3) + + const { node: node2 } = parseWithBind(`
{{ foo }}
`) + expect(node2.arguments.length).toBe(4) + expect(node2.arguments[3]).toBe(`${PatchFlags.TEXT} /* TEXT */`) + + // multiple nodes, merged with optimze text + const { node: node3 } = parseWithBind(`
foo {{ bar }} baz
`) + expect(node3.arguments.length).toBe(4) + expect(node3.arguments[3]).toBe(`${PatchFlags.TEXT} /* TEXT */`) + }) + test('CLASS', () => { const { node } = parseWithBind(`
`) expect(node.arguments.length).toBe(4) diff --git a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts index 93fc5329c..f2f2ad817 100644 --- a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts @@ -123,9 +123,8 @@ describe('compiler: transform component slots', () => { one: { type: NodeTypes.JS_FUNCTION_EXPRESSION, params: { - type: NodeTypes.SIMPLE_EXPRESSION, - content: `{ foo }`, - isStatic: false + type: NodeTypes.COMPOUND_EXPRESSION, + children: [`{ `, { content: `foo` }, ` }`] }, returns: [ { @@ -145,9 +144,8 @@ describe('compiler: transform component slots', () => { two: { type: NodeTypes.JS_FUNCTION_EXPRESSION, params: { - type: NodeTypes.SIMPLE_EXPRESSION, - content: `{ bar }`, - isStatic: false + type: NodeTypes.COMPOUND_EXPRESSION, + children: [`{ `, { content: `bar` }, ` }`] }, returns: [ { @@ -186,9 +184,8 @@ describe('compiler: transform component slots', () => { '[_ctx.one]': { type: NodeTypes.JS_FUNCTION_EXPRESSION, params: { - type: NodeTypes.SIMPLE_EXPRESSION, - content: `{ foo }`, - isStatic: false + type: NodeTypes.COMPOUND_EXPRESSION, + children: [`{ `, { content: `foo` }, ` }`] }, returns: [ { @@ -208,9 +205,8 @@ describe('compiler: transform component slots', () => { '[_ctx.two]': { type: NodeTypes.JS_FUNCTION_EXPRESSION, params: { - type: NodeTypes.SIMPLE_EXPRESSION, - content: `{ bar }`, - isStatic: false + type: NodeTypes.COMPOUND_EXPRESSION, + children: [`{ `, { content: `bar` }, ` }`] }, returns: [ { @@ -249,9 +245,8 @@ describe('compiler: transform component slots', () => { default: { type: NodeTypes.JS_FUNCTION_EXPRESSION, params: { - type: NodeTypes.SIMPLE_EXPRESSION, - content: `{ foo }`, - isStatic: false + type: NodeTypes.COMPOUND_EXPRESSION, + children: [`{ `, { content: `foo` }, ` }`] }, returns: [ { diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index 26d10f77e..70733b3f8 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -38,65 +38,72 @@ export const transformElement: NodeTransform = (node, context) => { node.tagType === ElementTypes.ELEMENT || node.tagType === ElementTypes.COMPONENT ) { - const isComponent = node.tagType === ElementTypes.COMPONENT - let hasProps = node.props.length > 0 - const hasChildren = node.children.length > 0 - let patchFlag: number = 0 - let runtimeDirectives: DirectiveNode[] | undefined - let dynamicPropNames: string[] | undefined - let componentIdentifier: string | undefined + // perform the work on exit, after all child expressions have been + // processed and merged. + return () => { + const isComponent = node.tagType === ElementTypes.COMPONENT + let hasProps = node.props.length > 0 + const hasChildren = node.children.length > 0 + let patchFlag: number = 0 + let runtimeDirectives: DirectiveNode[] | undefined + let dynamicPropNames: string[] | undefined + let componentIdentifier: string | undefined - if (isComponent) { - componentIdentifier = `_component_${toValidId(node.tag)}` - context.statements.add( - `const ${componentIdentifier} = ${context.helper( - RESOLVE_COMPONENT - )}(${JSON.stringify(node.tag)})` - ) - } - - const args: CallExpression['arguments'] = [ - isComponent ? componentIdentifier! : `"${node.tag}"` - ] - // props - if (hasProps) { - const propsBuildResult = buildProps( - node.props, - node.loc, - context, - isComponent - ) - patchFlag = propsBuildResult.patchFlag - dynamicPropNames = propsBuildResult.dynamicPropNames - runtimeDirectives = propsBuildResult.directives - if (!propsBuildResult.props) { - hasProps = false - } else { - args.push(propsBuildResult.props) - } - } - // children - if (hasChildren) { - if (!hasProps) { - args.push(`null`) - } if (isComponent) { - const { slots, hasDynamicSlotName } = buildSlots(node, context) - args.push(slots) - if (hasDynamicSlotName) { - patchFlag |= PatchFlags.DYNAMIC_SLOTS + componentIdentifier = `_component_${toValidId(node.tag)}` + context.statements.add( + `const ${componentIdentifier} = ${context.helper( + RESOLVE_COMPONENT + )}(${JSON.stringify(node.tag)})` + ) + } + + const args: CallExpression['arguments'] = [ + isComponent ? componentIdentifier! : `"${node.tag}"` + ] + // props + if (hasProps) { + const propsBuildResult = buildProps( + node.props, + node.loc, + context, + isComponent + ) + patchFlag = propsBuildResult.patchFlag + dynamicPropNames = propsBuildResult.dynamicPropNames + runtimeDirectives = propsBuildResult.directives + if (!propsBuildResult.props) { + hasProps = false + } else { + args.push(propsBuildResult.props) } - } else { - if (node.children.length === 1) { + } + // children + if (hasChildren) { + if (!hasProps) { + args.push(`null`) + } + if (isComponent) { + const { slots, hasDynamicSlotName } = buildSlots(node, context) + args.push(slots) + if (hasDynamicSlotName) { + patchFlag |= PatchFlags.DYNAMIC_SLOTS + } + } else if (node.children.length === 1) { const child = node.children[0] const type = child.type + const hasDynamicTextChild = + type === NodeTypes.INTERPOLATION || + type === NodeTypes.COMPOUND_EXPRESSION + if (hasDynamicTextChild) { + patchFlag |= PatchFlags.TEXT + } // pass directly if the only child is one of: // - text (plain / interpolation / expression) // - outlet (already an array) if ( type === NodeTypes.TEXT || - type === NodeTypes.INTERPOLATION || - type === NodeTypes.COMPOUND_EXPRESSION || + hasDynamicTextChild || (type === NodeTypes.ELEMENT && (child as ElementNode).tagType === ElementTypes.SLOT) ) { @@ -108,54 +115,54 @@ export const transformElement: NodeTransform = (node, context) => { args.push(node.children) } } - } - // patchFlag & dynamicPropNames - if (patchFlag !== 0) { - if (!hasChildren) { - if (!hasProps) { + // patchFlag & dynamicPropNames + if (patchFlag !== 0) { + if (!hasChildren) { + if (!hasProps) { + args.push(`null`) + } args.push(`null`) } - args.push(`null`) - } - if (__DEV__) { - const flagNames = Object.keys(PatchFlagNames) - .filter(n => patchFlag & Number(n)) - .map(n => PatchFlagNames[n as any]) - .join(`, `) - args.push(patchFlag + ` /* ${flagNames} */`) - } else { - args.push(patchFlag + '') - } - if (dynamicPropNames && dynamicPropNames.length) { - args.push( - `[${dynamicPropNames.map(n => JSON.stringify(n)).join(`, `)}]` - ) - } - } - - const { loc } = node - const vnode = createCallExpression( - context.helper(CREATE_VNODE), - args, - loc - ) - - if (runtimeDirectives && runtimeDirectives.length) { - node.codegenNode = createCallExpression( - context.helper(APPLY_DIRECTIVES), - [ - vnode, - createArrayExpression( - runtimeDirectives.map(dir => { - return createDirectiveArgs(dir, context) - }), - loc + if (__DEV__) { + const flagNames = Object.keys(PatchFlagNames) + .filter(n => patchFlag & Number(n)) + .map(n => PatchFlagNames[n as any]) + .join(`, `) + args.push(patchFlag + ` /* ${flagNames} */`) + } else { + args.push(patchFlag + '') + } + if (dynamicPropNames && dynamicPropNames.length) { + args.push( + `[${dynamicPropNames.map(n => JSON.stringify(n)).join(`, `)}]` ) - ], + } + } + + const { loc } = node + const vnode = createCallExpression( + context.helper(CREATE_VNODE), + args, loc ) - } else { - node.codegenNode = vnode + + if (runtimeDirectives && runtimeDirectives.length) { + node.codegenNode = createCallExpression( + context.helper(APPLY_DIRECTIVES), + [ + vnode, + createArrayExpression( + runtimeDirectives.map(dir => { + return createDirectiveArgs(dir, context) + }), + loc + ) + ], + loc + ) + } else { + node.codegenNode = vnode + } } } }