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:
parent
5e97643c85
commit
d69db0b2fd
@ -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,
|
||||||
|
@ -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', () => {
|
||||||
|
@ -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
|
||||||
],
|
],
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user