wip(runtime): support multi-element static vnode in renderer

This commit is contained in:
Evan You 2020-05-15 15:12:26 -04:00
parent cb9444807e
commit dbf627f136
3 changed files with 105 additions and 27 deletions

View File

@ -116,8 +116,7 @@ export interface RendererOptions<
parent: HostElement,
anchor: HostNode | null,
isSVG: boolean
): HostElement
setStaticContent?(node: HostElement, content: string): void
): HostElement[]
}
// Renderer Node can technically be any object in the context of core renderer
@ -333,8 +332,7 @@ function baseCreateRenderer(
nextSibling: hostNextSibling,
setScopeId: hostSetScopeId = NOOP,
cloneNode: hostCloneNode,
insertStaticContent: hostInsertStaticContent,
setStaticContent: hostSetStaticContent
insertStaticContent: hostInsertStaticContent
} = options
// Note: functions inside this closure should use `const xxx = () => {}`
@ -373,11 +371,7 @@ function baseCreateRenderer(
if (n1 == null) {
mountStaticNode(n2, container, anchor, isSVG)
} else if (__DEV__) {
// static nodes are only patched during dev for HMR
n2.el = n1.el
if (n2.children !== n1.children) {
hostSetStaticContent!(n2.el!, n2.children as string)
}
patchStaticNode(n1, n2, container, isSVG)
}
break
case Fragment:
@ -492,11 +486,19 @@ function baseCreateRenderer(
isSVG: boolean
) => {
if (n2.el && hostCloneNode !== undefined) {
hostInsert(hostCloneNode(n2.el), container, anchor)
// static node was already mounted (and reused), or adopted
// server-rendered node during hydration (in this case its children can be
// stripped by SSR optimizations). Clone the dom nodes instead.
let cur: RendererElement | null = n2.el
while (cur && cur !== n2.anchor) {
hostInsert(hostCloneNode(cur), container, anchor)
cur = hostNextSibling(cur)
}
hostInsert(hostCloneNode(n2.anchor!), container, anchor)
} else {
// static nodes are only present when used with compiler-dom/runtime-dom
// which guarantees presence of hostInsertStaticContent.
n2.el = hostInsertStaticContent!(
;[n2.el, n2.anchor] = hostInsertStaticContent!(
n2.children as string,
container,
anchor,
@ -505,6 +507,64 @@ function baseCreateRenderer(
}
}
/**
* Dev / HMR only
*/
const patchStaticNode = (
n1: VNode,
n2: VNode,
container: RendererElement,
isSVG: boolean
) => {
// static nodes are only patched during dev for HMR
if (n2.children !== n1.children) {
const anchor = hostNextSibling(n1.anchor!)
// remove existing
removeStaticNode(n1)
// insert new
;[n2.el, n2.anchor] = hostInsertStaticContent!(
n2.children as string,
container,
anchor,
isSVG
)
} else {
n2.el = n1.el
n2.anchor = n1.anchor
}
}
/**
* Dev / HMR only
*/
const moveStaticNode = (
vnode: VNode,
container: RendererElement,
anchor: RendererNode | null
) => {
let cur = vnode.el
const end = vnode.anchor!
while (cur && cur !== end) {
const next = hostNextSibling(cur)
hostInsert(cur, container, anchor)
cur = next
}
hostInsert(end, container, anchor)
}
/**
* Dev / HMR only
*/
const removeStaticNode = (vnode: VNode) => {
let cur = vnode.el
while (cur && cur !== vnode.anchor) {
const next = hostNextSibling(cur)
hostRemove(cur)
cur = next
}
hostRemove(vnode.anchor!)
}
const processElement = (
n1: VNode | null,
n2: VNode,
@ -1456,7 +1516,7 @@ function baseCreateRenderer(
n1,
n2,
container,
parentAnchor,
null,
parentComponent,
parentSuspense,
isSVG,
@ -1481,7 +1541,7 @@ function baseCreateRenderer(
n1,
n2,
container,
parentAnchor,
null,
parentComponent,
parentSuspense,
isSVG,
@ -1692,6 +1752,12 @@ function baseCreateRenderer(
return
}
// static node move can only happen when force updating HMR
if (__DEV__ && type === Static) {
moveStaticNode(vnode, container, anchor)
return
}
// single nodes
const needTransition =
moveType !== MoveType.REORDER &&
@ -1808,6 +1874,11 @@ function baseCreateRenderer(
return
}
if (__DEV__ && type === Static) {
removeStaticNode(vnode)
return
}
const performRemove = () => {
hostRemove(el!)
if (transition && !transition.persisted && transition.afterLeave) {

View File

@ -127,6 +127,7 @@ export interface VNode<HostNode = RendererNode, HostElement = RendererElement> {
anchor: HostNode | null // fragment anchor
target: HostElement | null // teleport target
targetAnchor: HostNode | null // teleport target anchor
staticCount: number // number of elements contained in a static vnode
// optimization only
shapeFlag: number
@ -368,6 +369,7 @@ function _createVNode(
anchor: null,
target: null,
targetAnchor: null,
staticCount: 0,
shapeFlag,
patchFlag,
dynamicProps,
@ -422,6 +424,7 @@ export function cloneVNode<T, U>(
children: vnode.children,
target: vnode.target,
targetAnchor: vnode.targetAnchor,
staticCount: vnode.staticCount,
shapeFlag: vnode.shapeFlag,
// if the vnode is cloned with extra props, we can no longer assume its
// existing patch flag to be reliable and need to bail out of optimized mode.
@ -459,8 +462,15 @@ export function createTextVNode(text: string = ' ', flag: number = 0): VNode {
/**
* @internal
*/
export function createStaticVNode(content: string): VNode {
return createVNode(Static, null, content)
export function createStaticVNode(
content: string,
numberOfNodes: number
): VNode {
// A static vnode can contain multiple stringified elements, and the number
// of elements is necessary for hydration.
const vnode = createVNode(Static, null, content)
vnode.staticCount = numberOfNodes
return vnode
}
/**

View File

@ -64,17 +64,14 @@ export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
(tempSVGContainer = doc.createElementNS(svgNS, 'svg'))
: tempContainer || (tempContainer = doc.createElement('div'))
temp.innerHTML = content
const node = temp.children[0]
nodeOps.insert(node, parent, anchor)
return node
}
}
if (__DEV__) {
// __UNSAFE__
// Reason: innerHTML.
// same as `insertStaticContent`, but this is also dev only (for HMR).
nodeOps.setStaticContent = (el, content) => {
el.innerHTML = content
const first = temp.firstChild as Element
let node: Element | null = first
let last: Element = node
while (node) {
last = node
nodeOps.insert(node, parent, anchor)
node = temp.firstChild as Element
}
return [first, last]
}
}