feat(compiler): generate TEXT patchFlag

This commit is contained in:
Evan You 2019-10-02 00:19:23 -04:00
parent 3a95a2f148
commit fe36555d9e
5 changed files with 137 additions and 120 deletions

View File

@ -5,7 +5,7 @@ exports[`compiler: integration tests function mode 1`] = `
return function render() { return function render() {
with (this) { 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\\", { return _createVNode(\\"div\\", {
id: \\"foo\\", id: \\"foo\\",
@ -19,7 +19,7 @@ return function render() {
])), ])),
_createVNode(_Fragment, null, _renderList(list, (value, index) => { _createVNode(_Fragment, null, _renderList(list, (value, index) => {
return (_openBlock(), _createBlock(\\"div\\", null, [ return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"span\\", null, _toString(value + index)) _createVNode(\\"span\\", null, _toString(value + index), 1 /* TEXT */)
])) ]))
}), 128 /* UNKEYED_FRAGMENT */) }), 128 /* UNKEYED_FRAGMENT */)
], 2 /* CLASS */) ], 2 /* CLASS */)
@ -28,7 +28,7 @@ return function render() {
`; `;
exports[`compiler: integration tests function mode w/ prefixIdentifiers: true 1`] = ` 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() { return function render() {
const _ctx = this const _ctx = this
@ -44,7 +44,7 @@ return function render() {
])), ])),
createVNode(Fragment, null, renderList(_ctx.list, (value, index) => { createVNode(Fragment, null, renderList(_ctx.list, (value, index) => {
return (openBlock(), createBlock(\\"div\\", null, [ return (openBlock(), createBlock(\\"div\\", null, [
createVNode(\\"span\\", null, toString(value + index)) createVNode(\\"span\\", null, toString(value + index), 1 /* TEXT */)
])) ]))
}), 128 /* UNKEYED_FRAGMENT */) }), 128 /* UNKEYED_FRAGMENT */)
], 2 /* CLASS */) ], 2 /* CLASS */)
@ -52,7 +52,7 @@ return function render() {
`; `;
exports[`compiler: integration tests module mode 1`] = ` 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() { export default function render() {
const _ctx = this const _ctx = this
@ -68,7 +68,7 @@ export default function render() {
])), ])),
createVNode(Fragment, null, renderList(_ctx.list, (value, index) => { createVNode(Fragment, null, renderList(_ctx.list, (value, index) => {
return (openBlock(), createBlock(\\"div\\", null, [ return (openBlock(), createBlock(\\"div\\", null, [
createVNode(\\"span\\", null, _toString(value + index)) createVNode(\\"span\\", null, _toString(value + index), 1 /* TEXT */)
])) ]))
}), 128 /* UNKEYED_FRAGMENT */) }), 128 /* UNKEYED_FRAGMENT */)
], 2 /* CLASS */) ], 2 /* CLASS */)

View File

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`compiler: transform component slots dynamically named slots 1`] = ` exports[`compiler: transform component slots dynamically named slots 1`] = `
"const { resolveComponent, createVNode, toString } = Vue "const { toString, resolveComponent, createVNode } = Vue
return function render() { return function render() {
const _ctx = this const _ctx = this
@ -21,7 +21,7 @@ return function render() {
`; `;
exports[`compiler: transform component slots explicit default slot 1`] = ` exports[`compiler: transform component slots explicit default slot 1`] = `
"const { resolveComponent, createVNode, toString } = Vue "const { toString, resolveComponent, createVNode } = Vue
return function render() { return function render() {
const _ctx = this const _ctx = this
@ -37,7 +37,7 @@ return function render() {
`; `;
exports[`compiler: transform component slots implicit default slot 1`] = ` exports[`compiler: transform component slots implicit default slot 1`] = `
"const { resolveComponent, createVNode } = Vue "const { createVNode, resolveComponent } = Vue
return function render() { return function render() {
const _ctx = this const _ctx = this
@ -52,7 +52,7 @@ return function render() {
`; `;
exports[`compiler: transform component slots named slots 1`] = ` exports[`compiler: transform component slots named slots 1`] = `
"const { resolveComponent, createVNode, toString } = Vue "const { toString, resolveComponent, createVNode } = Vue
return function render() { return function render() {
const _ctx = this const _ctx = this
@ -72,12 +72,12 @@ return function render() {
`; `;
exports[`compiler: transform component slots nested slots scoping 1`] = ` exports[`compiler: transform component slots nested slots scoping 1`] = `
"const { resolveComponent, createVNode, toString } = Vue "const { toString, resolveComponent, createVNode } = Vue
return function render() { return function render() {
const _ctx = this const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
const _component_Inner = resolveComponent(\\"Inner\\") const _component_Inner = resolveComponent(\\"Inner\\")
const _component_Comp = resolveComponent(\\"Comp\\")
return createVNode(_component_Comp, null, { return createVNode(_component_Comp, null, {
default: ({ foo }) => [ default: ({ foo }) => [

View File

@ -26,6 +26,7 @@ import { transformStyle } from '../../src/transforms/transformStyle'
import { transformBind } from '../../src/transforms/vBind' import { transformBind } from '../../src/transforms/vBind'
import { PatchFlags } from '@vue/shared' import { PatchFlags } from '@vue/shared'
import { createObjectMatcher } from '../testUtils' import { createObjectMatcher } from '../testUtils'
import { optimizeText } from '../../src/transforms/optimizeText'
function parseWithElementTransform( function parseWithElementTransform(
template: string, template: string,
@ -36,7 +37,7 @@ function parseWithElementTransform(
} { } {
const ast = parse(template, options) const ast = parse(template, options)
transform(ast, { transform(ast, {
nodeTransforms: [transformElement], nodeTransforms: [optimizeText, transformElement],
...options ...options
}) })
const codegenNode = (ast.children[0] as ElementNode) const codegenNode = (ast.children[0] as ElementNode)
@ -562,6 +563,20 @@ describe('compiler: element transform', () => {
}) })
} }
test('TEXT', () => {
const { node } = parseWithBind(`<div>foo</div>`)
expect(node.arguments.length).toBe(3)
const { node: node2 } = parseWithBind(`<div>{{ foo }}</div>`)
expect(node2.arguments.length).toBe(4)
expect(node2.arguments[3]).toBe(`${PatchFlags.TEXT} /* TEXT */`)
// multiple nodes, merged with optimze text
const { node: node3 } = parseWithBind(`<div>foo {{ bar }} baz</div>`)
expect(node3.arguments.length).toBe(4)
expect(node3.arguments[3]).toBe(`${PatchFlags.TEXT} /* TEXT */`)
})
test('CLASS', () => { test('CLASS', () => {
const { node } = parseWithBind(`<div :class="foo" />`) const { node } = parseWithBind(`<div :class="foo" />`)
expect(node.arguments.length).toBe(4) expect(node.arguments.length).toBe(4)

View File

@ -123,9 +123,8 @@ describe('compiler: transform component slots', () => {
one: { one: {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: { params: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
content: `{ foo }`, children: [`{ `, { content: `foo` }, ` }`]
isStatic: false
}, },
returns: [ returns: [
{ {
@ -145,9 +144,8 @@ describe('compiler: transform component slots', () => {
two: { two: {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: { params: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
content: `{ bar }`, children: [`{ `, { content: `bar` }, ` }`]
isStatic: false
}, },
returns: [ returns: [
{ {
@ -186,9 +184,8 @@ describe('compiler: transform component slots', () => {
'[_ctx.one]': { '[_ctx.one]': {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: { params: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
content: `{ foo }`, children: [`{ `, { content: `foo` }, ` }`]
isStatic: false
}, },
returns: [ returns: [
{ {
@ -208,9 +205,8 @@ describe('compiler: transform component slots', () => {
'[_ctx.two]': { '[_ctx.two]': {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: { params: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
content: `{ bar }`, children: [`{ `, { content: `bar` }, ` }`]
isStatic: false
}, },
returns: [ returns: [
{ {
@ -249,9 +245,8 @@ describe('compiler: transform component slots', () => {
default: { default: {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: { params: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
content: `{ foo }`, children: [`{ `, { content: `foo` }, ` }`]
isStatic: false
}, },
returns: [ returns: [
{ {

View File

@ -38,65 +38,72 @@ export const transformElement: NodeTransform = (node, context) => {
node.tagType === ElementTypes.ELEMENT || node.tagType === ElementTypes.ELEMENT ||
node.tagType === ElementTypes.COMPONENT node.tagType === ElementTypes.COMPONENT
) { ) {
const isComponent = node.tagType === ElementTypes.COMPONENT // perform the work on exit, after all child expressions have been
let hasProps = node.props.length > 0 // processed and merged.
const hasChildren = node.children.length > 0 return () => {
let patchFlag: number = 0 const isComponent = node.tagType === ElementTypes.COMPONENT
let runtimeDirectives: DirectiveNode[] | undefined let hasProps = node.props.length > 0
let dynamicPropNames: string[] | undefined const hasChildren = node.children.length > 0
let componentIdentifier: string | undefined 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) { if (isComponent) {
const { slots, hasDynamicSlotName } = buildSlots(node, context) componentIdentifier = `_component_${toValidId(node.tag)}`
args.push(slots) context.statements.add(
if (hasDynamicSlotName) { `const ${componentIdentifier} = ${context.helper(
patchFlag |= PatchFlags.DYNAMIC_SLOTS 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 child = node.children[0]
const type = child.type 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: // pass directly if the only child is one of:
// - text (plain / interpolation / expression) // - text (plain / interpolation / expression)
// - <slot> outlet (already an array) // - <slot> outlet (already an array)
if ( if (
type === NodeTypes.TEXT || type === NodeTypes.TEXT ||
type === NodeTypes.INTERPOLATION || hasDynamicTextChild ||
type === NodeTypes.COMPOUND_EXPRESSION ||
(type === NodeTypes.ELEMENT && (type === NodeTypes.ELEMENT &&
(child as ElementNode).tagType === ElementTypes.SLOT) (child as ElementNode).tagType === ElementTypes.SLOT)
) { ) {
@ -108,54 +115,54 @@ export const transformElement: NodeTransform = (node, context) => {
args.push(node.children) args.push(node.children)
} }
} }
} // patchFlag & dynamicPropNames
// patchFlag & dynamicPropNames if (patchFlag !== 0) {
if (patchFlag !== 0) { if (!hasChildren) {
if (!hasChildren) { if (!hasProps) {
if (!hasProps) { args.push(`null`)
}
args.push(`null`) args.push(`null`)
} }
args.push(`null`) if (__DEV__) {
} const flagNames = Object.keys(PatchFlagNames)
if (__DEV__) { .filter(n => patchFlag & Number(n))
const flagNames = Object.keys(PatchFlagNames) .map(n => PatchFlagNames[n as any])
.filter(n => patchFlag & Number(n)) .join(`, `)
.map(n => PatchFlagNames[n as any]) args.push(patchFlag + ` /* ${flagNames} */`)
.join(`, `) } else {
args.push(patchFlag + ` /* ${flagNames} */`) args.push(patchFlag + '')
} else { }
args.push(patchFlag + '') if (dynamicPropNames && dynamicPropNames.length) {
} args.push(
if (dynamicPropNames && dynamicPropNames.length) { `[${dynamicPropNames.map(n => JSON.stringify(n)).join(`, `)}]`
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
) )
], }
}
const { loc } = node
const vnode = createCallExpression(
context.helper(CREATE_VNODE),
args,
loc 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
}
} }
} }
} }