feat(compiler-core/v-slot): only force dynamic slots when referencing scope vars

This feature is only applied with prefixIdentifiers: true.
This commit is contained in:
Evan You 2019-10-16 15:30:21 -04:00
parent 5e97643c85
commit d69db0b2fd
4 changed files with 123 additions and 12 deletions

View File

@ -25,8 +25,8 @@ function parseWithVModel(template: string, options: CompilerOptions = {}) {
nodeTransforms: [ nodeTransforms: [
transformFor, transformFor,
transformExpression, transformExpression,
trackSlotScopes, transformElement,
transformElement trackSlotScopes
], ],
directiveTransforms: { directiveTransforms: {
...options.directiveTransforms, ...options.directiveTransforms,

View File

@ -7,7 +7,8 @@ import {
NodeTypes, NodeTypes,
ErrorCodes, ErrorCodes,
ForNode, ForNode,
CallExpression CallExpression,
ComponentNode
} from '../../src' } from '../../src'
import { transformElement } from '../../src/transforms/transformElement' import { transformElement } from '../../src/transforms/transformElement'
import { transformOn } from '../../src/transforms/vOn' import { transformOn } from '../../src/transforms/vOn'
@ -32,8 +33,8 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
...(options.prefixIdentifiers ...(options.prefixIdentifiers
? [trackVForSlotScopes, transformExpression] ? [trackVForSlotScopes, transformExpression]
: []), : []),
trackSlotScopes, transformElement,
transformElement trackSlotScopes
], ],
directiveTransforms: { directiveTransforms: {
on: transformOn, on: transformOn,
@ -347,7 +348,60 @@ describe('compiler: transform component slots', () => {
const div = ((root.children[0] as ForNode).children[0] as ElementNode) const div = ((root.children[0] as ForNode).children[0] as ElementNode)
.codegenNode as any .codegenNode as any
const comp = div.arguments[2][0] const comp = div.arguments[2][0]
expect(comp.codegenNode.arguments[3]).toMatch(PatchFlags.DYNAMIC_SLOTS + '') expect(comp.codegenNode.arguments[3]).toBe(
genFlagText(PatchFlags.DYNAMIC_SLOTS)
)
})
test('should only force dynamic slots when actually using scope vars w/ prefixIdentifiers: true', () => {
function assertDynamicSlots(template: string, shouldForce: boolean) {
const { root } = parseWithSlots(template, { prefixIdentifiers: true })
let flag: any
if (root.children[0].type === NodeTypes.FOR) {
const div = (root.children[0].children[0] as ElementNode)
.codegenNode as any
const comp = div.arguments[2][0]
flag = comp.codegenNode.arguments[3]
} else {
const innerComp = (root.children[0] as ComponentNode)
.children[0] as ComponentNode
flag = innerComp.codegenNode!.arguments[3]
}
if (shouldForce) {
expect(flag).toBe(genFlagText(PatchFlags.DYNAMIC_SLOTS))
} else {
expect(flag).toBeUndefined()
}
}
assertDynamicSlots(
`<div v-for="i in list">
<Comp v-slot="bar">foo</Comp>
</div>`,
false
)
assertDynamicSlots(
`<div v-for="i in list">
<Comp v-slot="bar">{{ i }}</Comp>
</div>`,
true
)
// reference the component's own slot variable should not force dynamic slots
assertDynamicSlots(
`<Comp v-slot="foo">
<Comp v-slot="bar">{{ bar }}</Comp>
</Comp>`,
false
)
assertDynamicSlots(
`<Comp v-slot="foo">
<Comp v-slot="bar">{{ foo }}</Comp>
</Comp>`,
true
)
}) })
test('named slot with v-if', () => { test('named slot with v-if', () => {

View File

@ -53,9 +53,9 @@ export function baseCompile(
transformExpression transformExpression
] ]
: []), : []),
trackSlotScopes,
transformSlotOutlet, transformSlotOutlet,
transformElement, transformElement,
trackSlotScopes,
optimizeText, optimizeText,
...(options.nodeTransforms || []) // user transforms ...(options.nodeTransforms || []) // user transforms
], ],

View File

@ -19,13 +19,21 @@ import {
FunctionExpression, FunctionExpression,
CallExpression, CallExpression,
createCallExpression, createCallExpression,
createArrayExpression createArrayExpression,
IfBranchNode
} from '../ast' } from '../ast'
import { TransformContext, NodeTransform } from '../transform' import { TransformContext, NodeTransform } from '../transform'
import { createCompilerError, ErrorCodes } from '../errors' import { createCompilerError, ErrorCodes } from '../errors'
import { findDir, isTemplateNode, assert, isVSlot } from '../utils' import {
findDir,
isTemplateNode,
assert,
isVSlot,
isSimpleIdentifier
} from '../utils'
import { CREATE_SLOTS, RENDER_LIST } from '../runtimeHelpers' import { CREATE_SLOTS, RENDER_LIST } from '../runtimeHelpers'
import { parseForExpression, createForLoopParams } from './vFor' import { parseForExpression, createForLoopParams } from './vFor'
import { isObject } from '@vue/shared'
const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode => const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic
@ -108,9 +116,12 @@ export function buildSlots(
// If the slot is inside a v-for or another v-slot, force it to be dynamic // If the slot is inside a v-for or another v-slot, force it to be dynamic
// since it likely uses a scope variable. // since it likely uses a scope variable.
// TODO: This can be further optimized to only make it dynamic when the slot let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0
// actually uses the scope variables. // with `prefixIdentifiers: true`, this can be further optimized to make
let hasDynamicSlots = context.scopes.vSlot > 1 || context.scopes.vFor > 0 // it dynamic only when the slot actually uses the scope variables.
if (!__BROWSER__ && context.prefixIdentifiers) {
hasDynamicSlots = hasScopeRef(node, context.identifiers)
}
// 1. Check for default slot with slotProps on component itself. // 1. Check for default slot with slotProps on component itself.
// <Comp v-slot="{ prop }"/> // <Comp v-slot="{ prop }"/>
@ -326,3 +337,49 @@ function buildDynamicSlot(
createObjectProperty(`fn`, fn) createObjectProperty(`fn`, fn)
]) ])
} }
function hasScopeRef(
node: TemplateChildNode | IfBranchNode | SimpleExpressionNode | undefined,
ids: TransformContext['identifiers']
): boolean {
if (!node) {
return false
}
switch (node.type) {
case NodeTypes.ELEMENT:
for (let i = 0; i < node.props.length; i++) {
const p = node.props[i]
if (
p.type === NodeTypes.DIRECTIVE &&
(hasScopeRef(p.arg, ids) || hasScopeRef(p.exp, ids))
) {
return true
}
}
return node.children.some(c => hasScopeRef(c, ids))
case NodeTypes.FOR:
if (hasScopeRef(node.source, ids)) {
return true
}
return node.children.some(c => hasScopeRef(c, ids))
case NodeTypes.IF:
return node.branches.some(b => hasScopeRef(b, ids))
case NodeTypes.IF_BRANCH:
if (hasScopeRef(node.condition, ids)) {
return true
}
return node.children.some(c => hasScopeRef(c, ids))
case NodeTypes.SIMPLE_EXPRESSION:
return (
!node.isStatic &&
isSimpleIdentifier(node.content) &&
!!ids[node.content]
)
case NodeTypes.COMPOUND_EXPRESSION:
return node.children.some(c => isObject(c) && hasScopeRef(c, ids))
case NodeTypes.INTERPOLATION:
return hasScopeRef(node.content, ids)
default:
return false
}
}