From 453cdcd600b9d278de01c91616beea9d69600bdc Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 29 May 2019 16:10:25 +0800 Subject: [PATCH] wip: portal --- packages/runtime-core/src/createRenderer.ts | 78 +++++++++++++++++++-- packages/runtime-core/src/index.ts | 3 +- packages/runtime-core/src/vnode.ts | 6 +- packages/runtime-dom/src/rendererOptions.ts | 11 +-- 4 files changed, 85 insertions(+), 13 deletions(-) diff --git a/packages/runtime-core/src/createRenderer.ts b/packages/runtime-core/src/createRenderer.ts index 2f433290b..3edf4866f 100644 --- a/packages/runtime-core/src/createRenderer.ts +++ b/packages/runtime-core/src/createRenderer.ts @@ -14,7 +14,14 @@ import { createComponentInstance, setupStatefulComponent } from './component' -import { isString, isArray, EMPTY_OBJ, EMPTY_ARR } from '@vue/shared' +import { + isString, + isArray, + isFunction, + isObject, + EMPTY_OBJ, + EMPTY_ARR +} from '@vue/shared' import { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags' import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler' import { effect, stop, ReactiveEffectOptions } from '@vue/observer' @@ -69,6 +76,7 @@ export interface RendererOptions { setElementText(node: HostNode, text: string): void parentNode(node: HostNode): HostNode | null nextSibling(node: HostNode): HostNode | null + querySelector(selector: string): HostNode | null } export function createRenderer(options: RendererOptions) { @@ -82,7 +90,8 @@ export function createRenderer(options: RendererOptions) { setText: hostSetText, setElementText: hostSetElementText, parentNode: hostParentNode, - nextSibling: hostNextSibling + nextSibling: hostNextSibling, + querySelector: hostQuerySelector } = options function patch( @@ -111,12 +120,15 @@ export function createRenderer(options: RendererOptions) { processFragment(n1, n2, container, anchor, optimized) break case Portal: - // TODO + processPortal(n1, n2, container, anchor, optimized) break default: if (isString(type)) { processElement(n1, n2, container, anchor, optimized) } else { + if (__DEV__ && !isFunction(type) && !isObject(type)) { + // TODO warn invalid node type + } processComponent(n1, n2, container, anchor) } break @@ -340,6 +352,61 @@ export function createRenderer(options: RendererOptions) { } } + function processPortal( + n1: VNode | null, + n2: VNode, + container: HostNode, + anchor?: HostNode, + optimized?: boolean + ) { + const targetSelector = n2.props && n2.props.target + if (n1 == null) { + const children = n2.children + const target = (n2.target = isString(targetSelector) + ? hostQuerySelector(targetSelector) + : null) + if (target != null) { + if (isString(children)) { + hostSetElementText(target, children) + } else if (children != null) { + mountChildren(children, target) + } + } else { + // TODO warn missing or invalid target + } + } else { + // update content + const target = (n2.target = n1.target) + if (n2.patchFlag === TEXT) { + hostSetElementText(target, n2.children as string) + } else if (!optimized) { + patchChildren(n1, n2, target) + } + // target changed + if (targetSelector !== (n1.props && n1.props.target)) { + const nextTarget = (n2.target = isString(targetSelector) + ? hostQuerySelector(targetSelector) + : null) + if (nextTarget != null) { + // move content + const children = n2.children + if (isString(children)) { + hostSetElementText(target, '') + hostSetElementText(nextTarget, children) + } else if (children != null) { + for (let i = 0; i < children.length; i++) { + move(children[i] as VNode, nextTarget, null) + } + } + } else { + // TODO warn missing or invalid target + } + } + } + // insert an empty node as the placeholder for the portal + processEmptyNode(n1, n2, container, anchor) + } + function processComponent( n1: VNode | null, n2: VNode, @@ -409,8 +476,9 @@ export function createRenderer(options: RendererOptions) { patch( prevTree, nextTree, - container || hostParentNode(prevTree.el), - anchor || getNextHostNode(prevTree) + // may have moved + hostParentNode(prevTree.el), + getNextHostNode(prevTree) ) if (next != null) { next.el = nextTree.el diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index d212e935f..9a40d995a 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -4,8 +4,7 @@ export { createBlock, createVNode, Fragment, - Text, - Empty + Portal } from './vnode' export { diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 8e5afa4b7..6432b9811 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -1,4 +1,4 @@ -import { isArray, isFunction } from '@vue/shared' +import { isArray, isFunction, EMPTY_ARR } from '@vue/shared' import { ComponentInstance } from './component' import { HostNode } from './createRenderer' @@ -29,6 +29,7 @@ export interface VNode { // DOM el: HostNode | null anchor: HostNode | null // fragment anchor + target: HostNode | null // portal target // optimization only patchFlag: number | null @@ -59,7 +60,7 @@ export function createBlock( shouldTrack = true const trackedNodes = blockStack.pop() vnode.dynamicChildren = - trackedNodes && trackedNodes.length ? trackedNodes : null + trackedNodes && trackedNodes.length ? trackedNodes : EMPTY_ARR // a block is always going to be patched trackDynamicNode(vnode) return vnode @@ -81,6 +82,7 @@ export function createVNode( component: null, el: null, anchor: null, + target: null, patchFlag, dynamicProps, dynamicChildren: null diff --git a/packages/runtime-dom/src/rendererOptions.ts b/packages/runtime-dom/src/rendererOptions.ts index 7ac294385..af2325d05 100644 --- a/packages/runtime-dom/src/rendererOptions.ts +++ b/packages/runtime-dom/src/rendererOptions.ts @@ -1,6 +1,7 @@ import { RendererOptions } from '@vue/runtime-core' import { patchProp } from './patchProp' +const doc = document const svgNS = 'http://www.w3.org/2000/svg' export const DOMRendererOptions: RendererOptions = { @@ -23,11 +24,11 @@ export const DOMRendererOptions: RendererOptions = { }, createElement: (tag: string, isSVG?: boolean): Element => - isSVG ? document.createElementNS(svgNS, tag) : document.createElement(tag), + isSVG ? doc.createElementNS(svgNS, tag) : doc.createElement(tag), - createText: (text: string): Text => document.createTextNode(text), + createText: (text: string): Text => doc.createTextNode(text), - createComment: (text: string): Comment => document.createComment(text), + createComment: (text: string): Comment => doc.createComment(text), setText: (node: Text, text: string) => { node.nodeValue = text @@ -39,5 +40,7 @@ export const DOMRendererOptions: RendererOptions = { parentNode: (node: Node): Node | null => node.parentNode, - nextSibling: (node: Node): Node | null => node.nextSibling + nextSibling: (node: Node): Node | null => node.nextSibling, + + querySelector: (selector: string): Node | null => doc.querySelector(selector) }