diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts index dc581f0f09d..048a5693e60 100644 --- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts @@ -346,18 +346,26 @@ describe('compiler: element transform', () => { test('should handle ', () => { function assert(tag: string) { - const { root, node } = parseWithElementTransform( - `<${tag}>` - ) + const root = parse(`
<${tag}>
`) + transform(root, { + nodeTransforms: [transformElement, transformText] + }) expect(root.components.length).toBe(0) expect(root.helpers).toContain(KEEP_ALIVE) - expect(node.callee).toBe(CREATE_VNODE) - expect(node.arguments).toMatchObject([ - KEEP_ALIVE, - `null`, - // keep-alive should not compile content to slots - [{ type: NodeTypes.ELEMENT, tag: 'span' }] - ]) + const node = (root.children[0] as any).children[0].codegenNode + expect(node.type).toBe(NodeTypes.JS_SEQUENCE_EXPRESSION) + expect(node.expressions[1]).toMatchObject({ + type: NodeTypes.JS_CALL_EXPRESSION, + callee: CREATE_BLOCK, // should be forced into a block + arguments: [ + KEEP_ALIVE, + `null`, + // keep-alive should not compile content to slots + [{ type: NodeTypes.ELEMENT, tag: 'span' }], + // should get a dynamic slots flag to force updates + genFlagText(PatchFlags.DYNAMIC_SLOTS) + ] + }) } assert(`keep-alive`) diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index 1d685dce59b..f12f2204938 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -67,7 +67,7 @@ export const transformElement: NodeTransform = (node, context) => { // updates inside get proper isSVG flag at runtime. (#639, #643) // This is technically web-specific, but splitting the logic out of core // leads to too much unnecessary complexity. - const shouldUseBlock = + let shouldUseBlock = !isComponent && (tag === 'svg' || tag === 'foreignObject') const nodeType = isComponent @@ -101,21 +101,35 @@ export const transformElement: NodeTransform = (node, context) => { args.push(`null`) } - if (__DEV__ && nodeType === KEEP_ALIVE && node.children.length > 1) { - context.onError( - createCompilerError(ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN, { - start: node.children[0].loc.start, - end: node.children[node.children.length - 1].loc.end, - source: '' - }) - ) + if (nodeType === KEEP_ALIVE) { + // Although a built-in component, we compile KeepAlive with raw children + // instead of slot functions so that it can be used inside Transition + // or other Transition-wrapping HOCs. + // To ensure correct updates with block optimizations, we need to: + // 1. Force keep-alive into a block. This avoids its children being + // collected by a parent block. + shouldUseBlock = true + // 2. Force keep-alive to always be updated, since it uses raw children. + patchFlag |= PatchFlags.DYNAMIC_SLOTS + if (__DEV__ && node.children.length > 1) { + context.onError( + createCompilerError(ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN, { + start: node.children[0].loc.start, + end: node.children[node.children.length - 1].loc.end, + source: '' + }) + ) + } } - // Portal & KeepAlive should have normal children instead of slots - // Portal is not a real component has dedicated handling in the renderer - // KeepAlive should not track its own deps so that it can be used inside - // Transition - if (isComponent && nodeType !== PORTAL && nodeType !== KEEP_ALIVE) { + const shouldBuildAsSlots = + isComponent && + // Portal is not a real component has dedicated handling in the renderer + nodeType !== PORTAL && + // explained above. + nodeType !== KEEP_ALIVE + + if (shouldBuildAsSlots) { const { slots, hasDynamicSlots } = buildSlots(node, context) args.push(slots) if (hasDynamicSlots) {