mirror of https://github.com/vuejs/core.git
wip: refactor
This commit is contained in:
parent
3e7f093519
commit
04eadd859a
|
@ -398,7 +398,7 @@ describe('ssr: element', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('dynamic anchor', () => {
|
describe('dynamic anchor', () => {
|
||||||
test('consecutive components', () => {
|
test('two consecutive components', () => {
|
||||||
expect(
|
expect(
|
||||||
getCompiledString(`
|
getCompiledString(`
|
||||||
<div>
|
<div>
|
||||||
|
@ -409,12 +409,37 @@ describe('ssr: element', () => {
|
||||||
</div>
|
</div>
|
||||||
`),
|
`),
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
"\`<div><div></div><!--[[-->\`)
|
"\`<div><div></div>\`)
|
||||||
_push(_ssrRenderComponent(_component_Comp1, null, null, _parent))
|
_push(_ssrRenderComponent(_component_Comp1, null, null, _parent))
|
||||||
_push(\`<!--]]--><!--[[-->\`)
|
_push(\`<!--[[-->\`)
|
||||||
_push(_ssrRenderComponent(_component_Comp2, null, null, _parent))
|
_push(_ssrRenderComponent(_component_Comp2, null, null, _parent))
|
||||||
_push(\`<!--]]--><div></div></div>\`"
|
_push(\`<!--]]--><div></div></div>\`"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('multiple consecutive components', () => {
|
||||||
|
expect(
|
||||||
|
getCompiledString(`
|
||||||
|
<div>
|
||||||
|
<div/>
|
||||||
|
<Comp1/>
|
||||||
|
<Comp2/>
|
||||||
|
<Comp3/>
|
||||||
|
<Comp4/>
|
||||||
|
<div/>
|
||||||
|
</div>
|
||||||
|
`),
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"\`<div><div></div>\`)
|
||||||
|
_push(_ssrRenderComponent(_component_Comp1, null, null, _parent))
|
||||||
|
_push(\`<!--[[-->\`)
|
||||||
|
_push(_ssrRenderComponent(_component_Comp2, null, null, _parent))
|
||||||
|
_push(\`<!--]]--><!--[[-->\`)
|
||||||
|
_push(_ssrRenderComponent(_component_Comp3, null, null, _parent))
|
||||||
|
_push(\`<!--]]-->\`)
|
||||||
|
_push(_ssrRenderComponent(_component_Comp4, null, null, _parent))
|
||||||
|
_push(\`<div></div></div>\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
type IfStatement,
|
type IfStatement,
|
||||||
type JSChildNode,
|
type JSChildNode,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
|
type PlainElementNode,
|
||||||
type RootNode,
|
type RootNode,
|
||||||
type TemplateChildNode,
|
type TemplateChildNode,
|
||||||
type TemplateLiteral,
|
type TemplateLiteral,
|
||||||
|
@ -166,10 +167,14 @@ export function processChildren(
|
||||||
context.pushStringPart(`<!--[-->`)
|
context.pushStringPart(`<!--[-->`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { children } = parent
|
const { children, type, tagType } = parent as PlainElementNode
|
||||||
|
const inElement =
|
||||||
|
type === NodeTypes.ELEMENT && tagType === ElementTypes.ELEMENT
|
||||||
|
if (inElement) processChildrenDynamicInfo(children)
|
||||||
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
const child = children[i]
|
const child = children[i]
|
||||||
if (shouldProcessAsDynamic(parent, child)) {
|
if (inElement && shouldProcessChildAsDynamic(parent, child)) {
|
||||||
processChildren(
|
processChildren(
|
||||||
{ children: [child] },
|
{ children: [child] },
|
||||||
context,
|
context,
|
||||||
|
@ -274,87 +279,127 @@ const isStaticChildNode = (c: TemplateChildNode): boolean =>
|
||||||
c.type === NodeTypes.TEXT ||
|
c.type === NodeTypes.TEXT ||
|
||||||
c.type === NodeTypes.COMMENT
|
c.type === NodeTypes.COMMENT
|
||||||
|
|
||||||
|
interface DynamicInfo {
|
||||||
|
hasStaticPrevious: boolean
|
||||||
|
hasStaticNext: boolean
|
||||||
|
prevDynamicCount: number
|
||||||
|
nextDynamicCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function processChildrenDynamicInfo(
|
||||||
|
children: (TemplateChildNode & { _ssrDynamicInfo?: DynamicInfo })[],
|
||||||
|
): void {
|
||||||
|
const filteredChildren = children.filter(
|
||||||
|
child => !(child.type === NodeTypes.TEXT && !child.content.trim()),
|
||||||
|
)
|
||||||
|
|
||||||
|
for (let i = 0; i < filteredChildren.length; i++) {
|
||||||
|
const child = filteredChildren[i]
|
||||||
|
if (isStaticChildNode(child)) continue
|
||||||
|
|
||||||
|
child._ssrDynamicInfo = {
|
||||||
|
hasStaticPrevious: false,
|
||||||
|
hasStaticNext: false,
|
||||||
|
prevDynamicCount: 0,
|
||||||
|
nextDynamicCount: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = child._ssrDynamicInfo
|
||||||
|
|
||||||
|
// Calculate the previous static and dynamic node counts
|
||||||
|
let foundStaticPrev = false
|
||||||
|
let dynamicCountPrev = 0
|
||||||
|
for (let j = i - 1; j >= 0; j--) {
|
||||||
|
const prevChild = filteredChildren[j]
|
||||||
|
if (isStaticChildNode(prevChild)) {
|
||||||
|
foundStaticPrev = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// if the previous child has dynamic info, use it
|
||||||
|
else if (prevChild._ssrDynamicInfo) {
|
||||||
|
foundStaticPrev = prevChild._ssrDynamicInfo.hasStaticPrevious
|
||||||
|
dynamicCountPrev = prevChild._ssrDynamicInfo.prevDynamicCount + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
dynamicCountPrev++
|
||||||
|
}
|
||||||
|
info.hasStaticPrevious = foundStaticPrev
|
||||||
|
info.prevDynamicCount = dynamicCountPrev
|
||||||
|
|
||||||
|
// Calculate the number of static and dynamic nodes afterwards
|
||||||
|
let foundStaticNext = false
|
||||||
|
let dynamicCountNext = 0
|
||||||
|
for (let j = i + 1; j < filteredChildren.length; j++) {
|
||||||
|
const nextChild = filteredChildren[j]
|
||||||
|
if (isStaticChildNode(nextChild)) {
|
||||||
|
foundStaticNext = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// if the next child has dynamic info, use it
|
||||||
|
else if (nextChild._ssrDynamicInfo) {
|
||||||
|
foundStaticNext = nextChild._ssrDynamicInfo.hasStaticNext
|
||||||
|
dynamicCountNext = nextChild._ssrDynamicInfo.nextDynamicCount + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
dynamicCountNext++
|
||||||
|
}
|
||||||
|
info.hasStaticNext = foundStaticNext
|
||||||
|
info.nextDynamicCount = dynamicCountNext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a node should be processed as dynamic.
|
* Check if a node should be processed as dynamic.
|
||||||
* This is primarily used in Vapor mode hydration to wrap dynamic parts
|
* This is primarily used in Vapor mode hydration to wrap dynamic parts
|
||||||
* with markers (`<!--[[-->` and `<!--]]-->`).
|
* with markers (`<!--[[-->` and `<!--]]-->`).
|
||||||
|
* The purpose is to distinguish the boundaries of nodes during hydration
|
||||||
*
|
*
|
||||||
|
* 1. two consecutive dynamic nodes should only wrap the second one
|
||||||
* <element>
|
* <element>
|
||||||
* <element/> // Static previous sibling
|
* <element/> // Static node
|
||||||
* <Comp/> // Dynamic node (current)
|
* <Comp/> // Dynamic node -> should NOT be wrapped
|
||||||
* <Comp/> // Dynamic next sibling
|
* <Comp/> // Dynamic node -> should be wrapped
|
||||||
* <element/> // Static next sibling
|
* <element/> // Static node
|
||||||
* </element>
|
* </element>
|
||||||
|
*
|
||||||
|
* 2. three or more consecutive dynamic nodes should only wrap the
|
||||||
|
* middle nodes, leaving the first and last static.
|
||||||
|
* <element>
|
||||||
|
* <element/> // Static node
|
||||||
|
* <Comp/> // Dynamic node -> should NOT be wrapped
|
||||||
|
* <Comp/> // Dynamic node -> should be wrapped
|
||||||
|
* <Comp/> // Dynamic node -> should be wrapped
|
||||||
|
* <Comp/> // Dynamic node -> should NOT be wrapped
|
||||||
|
* <element/> // Static node
|
||||||
*/
|
*/
|
||||||
function shouldProcessAsDynamic(
|
function shouldProcessChildAsDynamic(
|
||||||
parent: { tag?: string; children: TemplateChildNode[] },
|
parent: { tag?: string; children: TemplateChildNode[] },
|
||||||
node: TemplateChildNode,
|
node: TemplateChildNode & { _ssrDynamicInfo?: DynamicInfo },
|
||||||
): boolean {
|
): boolean {
|
||||||
// 1. Must be a dynamic node type
|
// must be inside a parent element
|
||||||
if (isStaticChildNode(node)) return false
|
|
||||||
// 2. Must be inside a parent element
|
|
||||||
if (!parent.tag) return false
|
if (!parent.tag) return false
|
||||||
|
|
||||||
const children = parent.children.filter(
|
// must has dynamic info
|
||||||
child => !(child.type === NodeTypes.TEXT && !child.content.trim()),
|
const { _ssrDynamicInfo: info } = node
|
||||||
)
|
if (!info) return false
|
||||||
const len = children.length
|
|
||||||
const index = children.indexOf(node)
|
|
||||||
|
|
||||||
// 3. Check for a static previous sibling
|
const {
|
||||||
let hasStaticPreviousSibling = false
|
hasStaticPrevious,
|
||||||
if (index > 0) {
|
hasStaticNext,
|
||||||
for (let i = index - 1; i >= 0; i--) {
|
prevDynamicCount,
|
||||||
if (isStaticChildNode(children[i])) {
|
nextDynamicCount,
|
||||||
hasStaticPreviousSibling = true
|
} = info
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hasStaticPreviousSibling) return false
|
|
||||||
|
|
||||||
// 4. Check for a static next sibling
|
// must have static nodes on both sides
|
||||||
let hasStaticNextSibling = false
|
if (!hasStaticPrevious || !hasStaticNext) return false
|
||||||
if (index > -1 && index < len - 1) {
|
|
||||||
for (let i = index + 1; i < len; i++) {
|
|
||||||
if (isStaticChildNode(children[i])) {
|
|
||||||
hasStaticNextSibling = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hasStaticNextSibling) return false
|
|
||||||
|
|
||||||
// 5. Calculate the number and location of continuous dynamic nodes
|
const dynamicNodeCount = 1 + prevDynamicCount + nextDynamicCount
|
||||||
let dynamicNodeCount = 1 // The current node is counted as one
|
|
||||||
let prevDynamicCount = 0
|
|
||||||
let nextDynamicCount = 0
|
|
||||||
|
|
||||||
// Count consecutive dynamic nodes forward
|
// For two consecutive dynamic nodes, mark the second one as dynamic
|
||||||
for (let i = index - 1; i >= 0; i--) {
|
|
||||||
if (!isStaticChildNode(children[i])) {
|
|
||||||
prevDynamicCount++
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count consecutive dynamic nodes backwards
|
|
||||||
for (let i = index + 1; i < len; i++) {
|
|
||||||
if (!isStaticChildNode(children[i])) {
|
|
||||||
nextDynamicCount++
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamicNodeCount = 1 + prevDynamicCount + nextDynamicCount
|
|
||||||
|
|
||||||
// For two consecutive dynamic nodes, mark both as dynamic
|
|
||||||
if (dynamicNodeCount === 2) {
|
if (dynamicNodeCount === 2) {
|
||||||
return prevDynamicCount > 0 || nextDynamicCount > 0
|
return prevDynamicCount > 0
|
||||||
}
|
}
|
||||||
// For three or more dynamic nodes, only mark the intermediate nodes as dynamic
|
// For three or more dynamic nodes, mark the intermediate node as dynamic
|
||||||
else if (dynamicNodeCount >= 3) {
|
else if (dynamicNodeCount >= 3) {
|
||||||
return prevDynamicCount > 0 && nextDynamicCount > 0
|
return prevDynamicCount > 0 && nextDynamicCount > 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -1844,19 +1844,33 @@ describe('SSR hydration', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('dynamic anchor', () => {
|
describe('dynamic anchor', () => {
|
||||||
test('consecutive components', () => {
|
test('two consecutive components', () => {
|
||||||
const Comp = {
|
const Comp = {
|
||||||
render() {
|
render() {
|
||||||
return createTextVNode('foo')
|
return createTextVNode('foo')
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const { vnode, container } = mountWithHydration(
|
const { vnode, container } = mountWithHydration(
|
||||||
`<div><span></span><!--[[-->foo<!--]]--><!--[[-->foo<!--]]--><span></span></div>`,
|
`<div><span></span>foo<!--[[-->foo<!--]]--><span></span></div>`,
|
||||||
() => h('div', null, [h('span'), h(Comp), h(Comp), h('span')]),
|
() => h('div', null, [h('span'), h(Comp), h(Comp), h('span')]),
|
||||||
)
|
)
|
||||||
expect(vnode.el).toBe(container.firstChild)
|
expect(vnode.el).toBe(container.firstChild)
|
||||||
expect(`Hydration children mismatch`).not.toHaveBeenWarned()
|
expect(`Hydration children mismatch`).not.toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('multiple consecutive components', () => {
|
||||||
|
const Comp = {
|
||||||
|
render() {
|
||||||
|
return createTextVNode('foo')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const { vnode, container } = mountWithHydration(
|
||||||
|
`<div><span></span>foo<!--[[-->foo<!--]]-->foo<span></span></div>`,
|
||||||
|
() => h('div', null, [h('span'), h(Comp), h(Comp), h(Comp), h('span')]),
|
||||||
|
)
|
||||||
|
expect(vnode.el).toBe(container.firstChild)
|
||||||
|
expect(`Hydration children mismatch`).not.toHaveBeenWarned()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('mismatch handling', () => {
|
describe('mismatch handling', () => {
|
||||||
|
|
|
@ -280,13 +280,13 @@ describe('Vapor Mode hydration', () => {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
expect(container.innerHTML).toMatchInlineSnapshot(
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||||
`"<div><span></span><!--[[-->foo<!--]]--><!--[[-->foo<!--]]--><span></span></div>"`,
|
`"<div><span></span>foo<!--[[-->foo<!--]]--><span></span></div>"`,
|
||||||
)
|
)
|
||||||
|
|
||||||
data.value = 'bar'
|
data.value = 'bar'
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(container.innerHTML).toMatchInlineSnapshot(
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||||
`"<div><span></span><!--[[-->bar<!--]]--><!--[[-->bar<!--]]--><span></span></div>"`,
|
`"<div><span></span>bar<!--[[-->bar<!--]]--><span></span></div>"`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -385,13 +385,13 @@ describe('Vapor Mode hydration', () => {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
expect(container.innerHTML).toMatchInlineSnapshot(
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||||
`"<div><span></span><!--[[--><!--[--><div>foo</div>-foo<!--]--><!--]]--><!--[[--><!--[--><div>foo</div>-foo<!--]--><!--]]--><span></span></div>"`,
|
`"<div><span></span><!--[--><div>foo</div>-foo<!--]--><!--[[--><!--[--><div>foo</div>-foo<!--]--><!--]]--><span></span></div>"`,
|
||||||
)
|
)
|
||||||
|
|
||||||
data.value = 'bar'
|
data.value = 'bar'
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(container.innerHTML).toMatchInlineSnapshot(
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||||
`"<div><span></span><!--[[--><!--[--><div>bar</div>-bar<!--]--><!--]]--><!--[[--><!--[--><div>bar</div>-bar<!--]--><!--]]--><span></span></div>"`,
|
`"<div><span></span><!--[--><div>bar</div>-bar<!--]--><!--[[--><!--[--><div>bar</div>-bar<!--]--><!--]]--><span></span></div>"`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -42,8 +42,13 @@ function _next(node: Node): Node {
|
||||||
|
|
||||||
/*! #__NO_SIDE_EFFECTS__ */
|
/*! #__NO_SIDE_EFFECTS__ */
|
||||||
function __next(node: Node): Node {
|
function __next(node: Node): Node {
|
||||||
// process fragment as a single node
|
// treat dynamic node (<!--[[-->...<!--]]-->) as a single node
|
||||||
if (node && isComment(node, '[')) {
|
if (node && isComment(node, '[[')) {
|
||||||
|
node = locateEndAnchor(node, '[[', ']]')!
|
||||||
|
}
|
||||||
|
|
||||||
|
// treat dynamic node (<!--[-->...<!--]-->) as a single node
|
||||||
|
else if (node && isComment(node, '[')) {
|
||||||
node = locateEndAnchor(node)!
|
node = locateEndAnchor(node)!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue