diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index 7b34a79827b..3bb9a83fd20 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -35,7 +35,8 @@ import { MERGE_PROPS, TO_HANDLERS, TELEPORT, - KEEP_ALIVE + KEEP_ALIVE, + SUSPENSE } from '../runtimeHelpers' import { getInnerRange, @@ -89,6 +90,8 @@ export const transformElement: NodeTransform = (node, context) => { let shouldUseBlock = // dynamic component may resolve to plain elements isDynamicComponent || + vnodeTag === TELEPORT || + vnodeTag === SUSPENSE || (!isComponent && // and must be forced into blocks so that block // updates inside get proper isSVG flag at runtime. (#639, #643) diff --git a/packages/runtime-core/__tests__/components/Teleport.spec.ts b/packages/runtime-core/__tests__/components/Teleport.spec.ts index 99bea073072..eaa6e227ee3 100644 --- a/packages/runtime-core/__tests__/components/Teleport.spec.ts +++ b/packages/runtime-core/__tests__/components/Teleport.spec.ts @@ -6,9 +6,11 @@ import { Teleport, Text, ref, - nextTick + nextTick, + markRaw } from '@vue/runtime-test' import { createVNode, Fragment } from '../../src/vnode' +import { compile } from 'vue' describe('renderer: teleport', () => { test('should work', () => { @@ -299,4 +301,49 @@ describe('renderer: teleport', () => { ) expect(serializeInner(target)).toBe('') }) + + test('should work with block tree', async () => { + const target = nodeOps.createElement('div') + const root = nodeOps.createElement('div') + const disabled = ref(false) + + const App = { + setup() { + return { + target: markRaw(target), + disabled + } + }, + render: compile(` + +
teleported
{{ disabled }} +
+
root
+ `) + } + render(h(App), root) + expect(serializeInner(root)).toMatchInlineSnapshot( + `"
root
"` + ) + expect(serializeInner(target)).toMatchInlineSnapshot( + `"
teleported
false"` + ) + + disabled.value = true + await nextTick() + expect(serializeInner(root)).toMatchInlineSnapshot( + `"
teleported
true
root
"` + ) + expect(serializeInner(target)).toBe(``) + + // toggle back + disabled.value = false + await nextTick() + expect(serializeInner(root)).toMatchInlineSnapshot( + `"
root
"` + ) + expect(serializeInner(target)).toMatchInlineSnapshot( + `"
teleported
false"` + ) + }) }) diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 58e1725e8e9..c0ea6ab3f3d 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -601,8 +601,6 @@ function finishComponentSetup( if (__DEV__) { endMeasure(instance, `compile`) } - // mark the function as runtime compiled - ;(Component.render as InternalRenderFunction)._rc = true } instance.render = (Component.render || NOOP) as InternalRenderFunction diff --git a/packages/runtime-core/src/components/Teleport.ts b/packages/runtime-core/src/components/Teleport.ts index a4fdd4b505f..8c588df128b 100644 --- a/packages/runtime-core/src/components/Teleport.ts +++ b/packages/runtime-core/src/components/Teleport.ts @@ -139,6 +139,13 @@ export const TeleportImpl = { parentSuspense, isSVG ) + if (n2.patchFlag > 0 && n2.shapeFlag & ShapeFlags.ARRAY_CHILDREN) { + const oldChildren = n1.children as VNode[] + const children = n2.children as VNode[] + for (let i = 0; i < children.length; i++) { + children[i].el = oldChildren[i].el + } + } } else if (!optimized) { patchChildren( n1, diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 09f2ab242b4..df26f87ac71 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -960,7 +960,8 @@ function baseCreateRenderer( // which also requires the correct parent container !isSameVNodeType(oldVNode, newVNode) || // - In the case of a component, it could contain anything. - oldVNode.shapeFlag & ShapeFlags.COMPONENT + oldVNode.shapeFlag & ShapeFlags.COMPONENT || + oldVNode.shapeFlag & ShapeFlags.TELEPORT ? hostParentNode(oldVNode.el!)! : // In other cases, the parent container is not actually used so we // just pass the block element here to avoid a DOM parentNode call. diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 07dafcebac1..353ed863b05 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -411,11 +411,7 @@ function _createVNode( // the EVENTS flag is only for hydration and if it is the only flag, the // vnode should not be considered dynamic due to handler caching. patchFlag !== PatchFlags.HYDRATE_EVENTS && - (patchFlag > 0 || - shapeFlag & ShapeFlags.SUSPENSE || - shapeFlag & ShapeFlags.TELEPORT || - shapeFlag & ShapeFlags.STATEFUL_COMPONENT || - shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT) + (patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) ) { currentBlock.push(vnode) } diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index a9ccf894ef9..c810d03eb92 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -5,6 +5,7 @@ import { compile, CompilerOptions, CompilerError } from '@vue/compiler-dom' import { registerRuntimeCompiler, RenderFunction, warn } from '@vue/runtime-dom' import * as runtimeDom from '@vue/runtime-dom' import { isString, NOOP, generateCodeFrame, extend } from '@vue/shared' +import { InternalRenderFunction } from 'packages/runtime-core/src/component' __DEV__ && initDev() @@ -74,6 +75,10 @@ function compileToFunction( const render = (__GLOBAL__ ? new Function(code)() : new Function('Vue', code)(runtimeDom)) as RenderFunction + + // mark the function as runtime compiled + ;(render as InternalRenderFunction)._rc = true + return (compileCache[key] = render) }