feat(compiler): force dynamicSlots flag when inside v-for or v-slot
This commit is contained in:
parent
4dea23f79e
commit
c2fc7e3347
@ -159,7 +159,7 @@ return function render() {
|
|||||||
createVNode(_component_Inner, null, {
|
createVNode(_component_Inner, null, {
|
||||||
default: ({ bar }) => [toString(foo), toString(bar), toString(_ctx.baz)],
|
default: ({ bar }) => [toString(foo), toString(bar), toString(_ctx.baz)],
|
||||||
_compiled: true
|
_compiled: true
|
||||||
}),
|
}, 256 /* DYNAMIC_SLOTS */),
|
||||||
toString(foo),
|
toString(foo),
|
||||||
toString(_ctx.bar),
|
toString(_ctx.bar),
|
||||||
toString(_ctx.baz)
|
toString(_ctx.baz)
|
||||||
|
@ -5,7 +5,8 @@ import {
|
|||||||
generate,
|
generate,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
ErrorCodes
|
ErrorCodes,
|
||||||
|
ForNode
|
||||||
} 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'
|
||||||
@ -17,15 +18,20 @@ import {
|
|||||||
} from '../../src/transforms/vSlot'
|
} from '../../src/transforms/vSlot'
|
||||||
import { CREATE_SLOTS, RENDER_LIST } from '../../src/runtimeConstants'
|
import { CREATE_SLOTS, RENDER_LIST } from '../../src/runtimeConstants'
|
||||||
import { createObjectMatcher } from '../testUtils'
|
import { createObjectMatcher } from '../testUtils'
|
||||||
import { PatchFlags } from '@vue/shared'
|
import { PatchFlags, PatchFlagNames } from '@vue/shared'
|
||||||
|
import { transformFor } from '../../src/transforms/vFor'
|
||||||
|
import { transformIf } from '../../src/transforms/vIf'
|
||||||
|
|
||||||
function parseWithSlots(template: string, options: CompilerOptions = {}) {
|
function parseWithSlots(template: string, options: CompilerOptions = {}) {
|
||||||
const ast = parse(template)
|
const ast = parse(template)
|
||||||
transform(ast, {
|
transform(ast, {
|
||||||
nodeTransforms: [
|
nodeTransforms: [
|
||||||
|
transformIf,
|
||||||
|
transformFor,
|
||||||
...(options.prefixIdentifiers
|
...(options.prefixIdentifiers
|
||||||
? [trackVForSlotScopes, transformExpression, trackSlotScopes]
|
? [trackVForSlotScopes, transformExpression]
|
||||||
: []),
|
: []),
|
||||||
|
trackSlotScopes,
|
||||||
transformElement
|
transformElement
|
||||||
],
|
],
|
||||||
directiveTransforms: {
|
directiveTransforms: {
|
||||||
@ -36,7 +42,10 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
|
|||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
root: ast,
|
root: ast,
|
||||||
slots: (ast.children[0] as ElementNode).codegenNode!.arguments[2]
|
slots:
|
||||||
|
ast.children[0].type === NodeTypes.ELEMENT
|
||||||
|
? ast.children[0].codegenNode!.arguments[2]
|
||||||
|
: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -295,7 +304,12 @@ describe('compiler: transform component slots', () => {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
|
// nested slot should be forced dynamic, since scope variables
|
||||||
|
// are not tracked as dependencies of the slot.
|
||||||
|
`${PatchFlags.DYNAMIC_SLOTS} /* ${
|
||||||
|
PatchFlagNames[PatchFlags.DYNAMIC_SLOTS]
|
||||||
|
} */`
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -325,6 +339,18 @@ describe('compiler: transform component slots', () => {
|
|||||||
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should force dynamic when inside v-for', () => {
|
||||||
|
const { root } = parseWithSlots(
|
||||||
|
`<div v-for="i in list">
|
||||||
|
<Comp v-slot="bar">foo</Comp>
|
||||||
|
</div>`
|
||||||
|
)
|
||||||
|
const div = ((root.children[0] as ForNode).children[0] as ElementNode)
|
||||||
|
.codegenNode as any
|
||||||
|
const comp = div.arguments[2][0]
|
||||||
|
expect(comp.codegenNode.arguments[3]).toMatch(PatchFlags.DYNAMIC_SLOTS + '')
|
||||||
|
})
|
||||||
|
|
||||||
test('named slot with v-if', () => {
|
test('named slot with v-if', () => {
|
||||||
const { root, slots } = parseWithSlots(
|
const { root, slots } = parseWithSlots(
|
||||||
`<Comp>
|
`<Comp>
|
||||||
|
@ -49,10 +49,10 @@ export function baseCompile(
|
|||||||
? [
|
? [
|
||||||
// order is important
|
// order is important
|
||||||
trackVForSlotScopes,
|
trackVForSlotScopes,
|
||||||
transformExpression,
|
transformExpression
|
||||||
trackSlotScopes
|
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
|
trackSlotScopes,
|
||||||
optimizeText,
|
optimizeText,
|
||||||
transformStyle,
|
transformStyle,
|
||||||
transformSlotOutlet,
|
transformSlotOutlet,
|
||||||
|
@ -59,6 +59,12 @@ export interface TransformContext extends Required<TransformOptions> {
|
|||||||
statements: Set<string>
|
statements: Set<string>
|
||||||
hoists: JSChildNode[]
|
hoists: JSChildNode[]
|
||||||
identifiers: { [name: string]: number | undefined }
|
identifiers: { [name: string]: number | undefined }
|
||||||
|
scopes: {
|
||||||
|
vFor: number
|
||||||
|
vSlot: number
|
||||||
|
vPre: number
|
||||||
|
vOnce: number
|
||||||
|
}
|
||||||
parent: ParentNode | null
|
parent: ParentNode | null
|
||||||
childIndex: number
|
childIndex: number
|
||||||
currentNode: RootNode | TemplateChildNode | null
|
currentNode: RootNode | TemplateChildNode | null
|
||||||
@ -86,6 +92,12 @@ function createTransformContext(
|
|||||||
statements: new Set(),
|
statements: new Set(),
|
||||||
hoists: [],
|
hoists: [],
|
||||||
identifiers: {},
|
identifiers: {},
|
||||||
|
scopes: {
|
||||||
|
vFor: 0,
|
||||||
|
vSlot: 0,
|
||||||
|
vPre: 0,
|
||||||
|
vOnce: 0
|
||||||
|
},
|
||||||
prefixIdentifiers,
|
prefixIdentifiers,
|
||||||
nodeTransforms,
|
nodeTransforms,
|
||||||
directiveTransforms,
|
directiveTransforms,
|
||||||
|
@ -46,7 +46,7 @@ export const transformFor = createStructuralDirectiveTransform(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (parseResult) {
|
if (parseResult) {
|
||||||
const { helper, addIdentifiers, removeIdentifiers } = context
|
const { helper, addIdentifiers, removeIdentifiers, scopes } = context
|
||||||
const { source, value, key, index } = parseResult
|
const { source, value, key, index } = parseResult
|
||||||
|
|
||||||
// create the loop render function expression now, and add the
|
// create the loop render function expression now, and add the
|
||||||
@ -79,6 +79,8 @@ export const transformFor = createStructuralDirectiveTransform(
|
|||||||
codegenNode
|
codegenNode
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// bookkeeping
|
||||||
|
scopes.vFor++
|
||||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||||
// scope management
|
// scope management
|
||||||
// inject identifiers to context
|
// inject identifiers to context
|
||||||
@ -88,6 +90,7 @@ export const transformFor = createStructuralDirectiveTransform(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
scopes.vFor--
|
||||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||||
value && removeIdentifiers(value)
|
value && removeIdentifiers(value)
|
||||||
key && removeIdentifiers(key)
|
key && removeIdentifiers(key)
|
||||||
|
@ -32,22 +32,33 @@ const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
|
|||||||
|
|
||||||
const defaultFallback = createSimpleExpression(`undefined`, false)
|
const defaultFallback = createSimpleExpression(`undefined`, false)
|
||||||
|
|
||||||
// A NodeTransform that tracks scope identifiers for scoped slots so that they
|
// A NodeTransform that:
|
||||||
// don't get prefixed by transformExpression. This transform is only applied
|
// 1. Tracks scope identifiers for scoped slots so that they don't get prefixed
|
||||||
// in non-browser builds with { prefixIdentifiers: true }
|
// by transformExpression. This is only applied in non-browser builds with
|
||||||
|
// { prefixIdentifiers: true }.
|
||||||
|
// 2. Track v-slot depths so that we know a slot is inside another slot.
|
||||||
|
// Note the exit callback is executed before buildSlots() on the same node,
|
||||||
|
// so only nested slots see positive numbers.
|
||||||
export const trackSlotScopes: NodeTransform = (node, context) => {
|
export const trackSlotScopes: NodeTransform = (node, context) => {
|
||||||
if (
|
if (
|
||||||
node.type === NodeTypes.ELEMENT &&
|
node.type === NodeTypes.ELEMENT &&
|
||||||
(node.tagType === ElementTypes.COMPONENT ||
|
(node.tagType === ElementTypes.COMPONENT ||
|
||||||
node.tagType === ElementTypes.TEMPLATE)
|
node.tagType === ElementTypes.TEMPLATE)
|
||||||
) {
|
) {
|
||||||
|
// We are only checking non-empty v-slot here
|
||||||
|
// since we only care about slots that introduce scope variables.
|
||||||
const vSlot = findDir(node, 'slot')
|
const vSlot = findDir(node, 'slot')
|
||||||
if (vSlot) {
|
if (vSlot) {
|
||||||
const { addIdentifiers, removeIdentifiers } = context
|
|
||||||
const slotProps = vSlot.exp
|
const slotProps = vSlot.exp
|
||||||
slotProps && addIdentifiers(slotProps)
|
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||||
|
slotProps && context.addIdentifiers(slotProps)
|
||||||
|
}
|
||||||
|
context.scopes.vSlot++
|
||||||
return () => {
|
return () => {
|
||||||
slotProps && removeIdentifiers(slotProps)
|
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||||
|
slotProps && context.removeIdentifiers(slotProps)
|
||||||
|
}
|
||||||
|
context.scopes.vSlot--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,7 +105,12 @@ export function buildSlots(
|
|||||||
const { children, loc } = node
|
const { children, loc } = node
|
||||||
const slotsProperties: Property[] = []
|
const slotsProperties: Property[] = []
|
||||||
const dynamicSlots: (ConditionalExpression | CallExpression)[] = []
|
const dynamicSlots: (ConditionalExpression | CallExpression)[] = []
|
||||||
let hasDynamicSlots = false
|
|
||||||
|
// If the slot is inside a v-for or another v-slot, force it to be dynamic
|
||||||
|
// since it likely uses a scope variable.
|
||||||
|
// TODO: This can be further optimized to only make it dynamic when the slot
|
||||||
|
// actually uses the scope variables.
|
||||||
|
let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0
|
||||||
|
|
||||||
// 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 }"/>
|
||||||
|
Loading…
Reference in New Issue
Block a user