From: daiwei Date: Wed, 13 Aug 2025 01:05:03 +0000 (+0800) Subject: wip: inject block anchors in ssr vnode-based slot X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=a30e978617a5e849eb60ebaba8d223cb55362152;p=thirdparty%2Fvuejs%2Fcore.git wip: inject block anchors in ssr vnode-based slot --- diff --git a/packages/compiler-ssr/__tests__/ssrVaporAnchors.spec.ts b/packages/compiler-ssr/__tests__/ssrVaporAnchors.spec.ts index a2aab28e74..745bf65f42 100644 --- a/packages/compiler-ssr/__tests__/ssrVaporAnchors.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrVaporAnchors.spec.ts @@ -95,9 +95,11 @@ describe('insertion anchors', () => { _createVNode("div", null, [ _createCommentVNode("[p"), _renderSlot(_ctx.$slots, "foo"), + _createCommentVNode("slot"), _createCommentVNode("p]"), _createCommentVNode("[p"), _renderSlot(_ctx.$slots, "default"), + _createCommentVNode("slot"), _createCommentVNode("p]"), _createVNode("span") ]) @@ -174,10 +176,16 @@ describe('insertion anchors', () => { _createVNode("div", null, [ _createCommentVNode("[p"), (_ctx.foo) - ? (_openBlock(), _createBlock("span", { key: 0 })) + ? (_openBlock(), _createBlock("span", { key: 0 }, [ + _createCommentVNode("if") + ])) : (_ctx.bar) - ? (_openBlock(), _createBlock("span", { key: 1 })) - : (_openBlock(), _createBlock("span", { key: 2 })), + ? (_openBlock(), _createBlock("span", { key: 1 }, [ + _createCommentVNode("if-->\`) - if (_ctx.count === 1) { - _push(\`1\`) - _push(\`\`) - } else if (_ctx.count === 2) { - _push(\`2\`) - _push(\`\`) - } else if (_ctx.count === 3) { - _push(\`3\`) - _push(\`\`) - } else { - _push(\`4\`) - _push(\`\`) - } - _push(\`\`" + _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.tag), null, { + default: _withCtx((_, _push, _parent, _scopeId) => { + if (_push) { + if (_ctx.count === 1) { + _push(\`1\`) + _push(\`\`) + } else if (_ctx.count === 2) { + _push(\`2\`) + _push(\`\`) + } else if (_ctx.count === 3) { + _push(\`3\`) + _push(\`\`) + } else { + _push(\`4\`) + _push(\`\`) + } + } else { + return [ + (_ctx.count === 1) + ? (_openBlock(), _createBlock("span", { key: 0 }, [ + _createTextVNode("1"), + _createCommentVNode("if") + ])) + : (_ctx.count === 2) + ? (_openBlock(), _createBlock("span", { key: 1 }, [ + _createTextVNode("2"), + _createCommentVNode("if-->\`" `) }) - test('if in ssr slot vnode fallback', () => {}) - - test('for', () => {}) - - test('for in ssr slot vnode fallback', () => {}) - - test('slot', () => {}) - - test('slot in ssr slot vnode fallback', () => {}) + test('for', () => { + expect( + getCompiledString( + ` + {{item}} + `, + { + vapor: true, + }, + ), + ).toMatchInlineSnapshot(` + "\`\`) + _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.tag), null, { + default: _withCtx((_, _push, _parent, _scopeId) => { + if (_push) { + _push(\`\`) + _ssrRenderList(_ctx.items, (item) => { + _push(\`\${ + _ssrInterpolate(item) + }\`) + }) + _push(\`\`) + } else { + return [ + (_openBlock(true), _createBlock(_Fragment, null, _renderList(_ctx.items, (item) => { + return (_openBlock(), _createBlock("span", null, _toDisplayString(item), 1 /* TEXT */)) + }), 256 /* UNKEYED_FRAGMENT */)), + _createCommentVNode("for") + ] + } + }), + _: 1 /* STABLE */ + }), _parent) + _push(\`\`" + `) + }) - test('forwarded slot', () => {}) + test('slot', () => { + expect( + getCompiledString( + `
+ + +
`, + { vapor: true }, + ), + ).toMatchInlineSnapshot(` + "\`
\`) + _ssrRenderSlot(_ctx.$slots, "foo", {}, null, _push, _parent) + _push(\`\`) + _ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent) + _push(\`
\`" + `) + }) - test('dynamic component', () => {}) + test('forwarded slot', () => { + expect( + getCompiledString( + ` + + + `, + { vapor: true }, + ), + ).toMatchInlineSnapshot(` + "\`\`) + _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.tag), null, { + default: _withCtx((_, _push, _parent, _scopeId) => { + if (_push) { + _ssrRenderSlot(_ctx.$slots, "foo", {}, null, _push, _parent, _scopeId) + _push(\`\`) + _ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent, _scopeId) + _push(\`\`) + } else { + return [ + _renderSlot(_ctx.$slots, "foo"), + _createCommentVNode("slot"), + _renderSlot(_ctx.$slots, "default"), + _createCommentVNode("slot") + ] + } + }), + _: 3 /* FORWARDED */ + }), _parent) + _push(\`\`" + `) + }) - test('dynamic in ssr slot vnode fallback', () => {}) + test('dynamic component', () => { + expect( + getCompiledString( + ` +
+ +
+
`, + { vapor: true }, + ), + ).toMatchInlineSnapshot(` + "\`\`) + _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent("tag"), null, { + default: _withCtx((_, _push, _parent, _scopeId) => { + if (_push) { + _push(\`\`) + _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent("foo"), null, null), _parent, _scopeId) + _push(\`\`) + } else { + return [ + _createVNode("div", null, [ + _createCommentVNode("[a"), + (_openBlock(), _createBlock(_resolveDynamicComponent("foo"))), + _createCommentVNode("dynamic-component"), + _createCommentVNode("a]") + ]) + ] + } + }), + _: 1 /* STABLE */ + }), _parent) + _push(\`\`" + `) + }) }) diff --git a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts index beec63151c..53e6355fab 100644 --- a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts +++ b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts @@ -1,6 +1,7 @@ import { CREATE_VNODE, type CallExpression, + type CommentNode, type CompilerOptions, type ComponentNode, DOMDirectiveTransforms, @@ -62,6 +63,9 @@ import { } from './ssrTransformTransitionGroup' import { DYNAMIC_COMPONENT_ANCHOR_LABEL, + FOR_ANCHOR_LABEL, + IF_ANCHOR_LABEL, + SLOT_ANCHOR_LABEL, extend, isArray, isObject, @@ -338,9 +342,8 @@ function createVNodeSlotBranch( if (vFor) { wrapperProps.push(extend({}, vFor)) } - if (parentContext.vapor) { - children = injectVaporInsertionAnchors(children, parent) + children = injectVaporAnchors(children, parent) } const wrapperNode: TemplateNode = { @@ -395,7 +398,7 @@ function subTransform( // - hoists are not enabled for the client branch here } -function injectVaporInsertionAnchors( +function injectVaporAnchors( children: TemplateChildNode[], parent: TemplateChildNode, ): TemplateChildNode[] { @@ -403,26 +406,25 @@ function injectVaporInsertionAnchors( processBlockNodeAnchor(children) } - const newChildren: TemplateChildNode[] = new Array(children.length * 3) - let newIndex = 0 + const newChildren: TemplateChildNode[] = [] for (let i = 0; i < children.length; i++) { const child = children[i] if (child.type !== NodeTypes.ELEMENT) { - newChildren[newIndex++] = child + newChildren.push(child) continue } const { tagType, props } = child - let anchor: string | undefined + let insertionAnchor: string | undefined if ( tagType === ElementTypes.COMPONENT || tagType === ElementTypes.SLOT || tagType === ElementTypes.TEMPLATE ) { - anchor = child.anchor + insertionAnchor = child.anchor } else if (tagType === ElementTypes.ELEMENT) { let hasIf = false let hasFor = false @@ -437,49 +439,73 @@ function injectVaporInsertionAnchors( } if (hasIf) { - anchor = (child as any as IfNode).anchor - if (anchor) { - // find sibling else-if/else branches - // inject anchor after else-if/else branch if founded - // otherwise inject after if node - const lastBranchIndex = findLastIfBranchIndex(children, i) - if (lastBranchIndex > i) { - // inject anchor before if node - newChildren[newIndex++] = createAnchorComment(`[${anchor}`) - - // copy branch nodes - for (let j = i; j <= lastBranchIndex; j++) { - const node = children[j] as PlainElementNode - newChildren[newIndex++] = node - - node.children = injectVaporInsertionAnchors(node.children, node) + insertionAnchor = (child as any as IfNode).anchor + // find sibling else-if/else branches + // inject anchor after else-if/else branch if founded + // otherwise inject after if node + const lastBranchIndex = findLastIfBranchIndex(children, i) + if (lastBranchIndex > i) { + // inject anchor before if node + if (insertionAnchor) { + newChildren.push(createAnchor(`[${insertionAnchor}`)) + } + + // copy branch nodes + for (let j = i; j <= lastBranchIndex; j++) { + const node = children[j] as PlainElementNode + newChildren.push(node) + + // inject block anchor + const blockAnchorLabel = getBlockAnchorLabel(node) + if (blockAnchorLabel) { + const isElse = node.props.some(p => p.name === 'else') + const repeatCount = j - i - (isElse ? 1 : 0) + 1 + node.children.push( + createAnchor( + ``.repeat(repeatCount).slice(4, -3), + ), + ) } - // inject anchor after branch nodes - newChildren[newIndex++] = createAnchorComment(`${anchor}]`) + node.children = injectVaporAnchors(node.children, node) + } - i = lastBranchIndex - continue + // inject anchor after branch nodes + if (insertionAnchor) { + newChildren.push(createAnchor(`${insertionAnchor}]`)) } + + i = lastBranchIndex + continue } } else if (hasFor) { - anchor = (child as any as ForNode).anchor + insertionAnchor = (child as any as ForNode).anchor } } // inject anchor before and after the child - if (anchor) newChildren[newIndex++] = createAnchorComment(`[${anchor}`) - newChildren[newIndex++] = child - if (anchor) newChildren[newIndex++] = createAnchorComment(`${anchor}]`) + if (insertionAnchor) { + newChildren.push(createAnchor(`[${insertionAnchor}`)) + } + + newChildren.push(child) - child.children = injectVaporInsertionAnchors(child.children, child) + // inject block anchor + const blockAnchorLabel = getBlockAnchorLabel(child) + if (blockAnchorLabel) newChildren.push(createAnchor(blockAnchorLabel)) + + // inject insertion anchor + if (insertionAnchor) { + newChildren.push(createAnchor(`${insertionAnchor}]`)) + } + + child.children = injectVaporAnchors(child.children, child) } - newChildren.length = newIndex return newChildren } -function createAnchorComment(content: string): TemplateChildNode { +function createAnchor(content: string): CommentNode { return { type: NodeTypes.COMMENT, content, @@ -526,6 +552,24 @@ function findLastIfBranchIndex( return lastIndex } +function getBlockAnchorLabel(child: TemplateChildNode): string | undefined { + if (child.type !== NodeTypes.ELEMENT) return + + if (child.tagType === ElementTypes.COMPONENT && child.tag === 'component') { + return DYNAMIC_COMPONENT_ANCHOR_LABEL + } else if (child.tagType === ElementTypes.SLOT) { + return SLOT_ANCHOR_LABEL + } else if ( + child.props.some( + p => p.name === 'if' || p.name === 'else-if' || p.name === 'else', + ) + ) { + return IF_ANCHOR_LABEL + } else if (child.props.some(p => p.name === 'for')) { + return FOR_ANCHOR_LABEL + } +} + function clone(v: any): any { if (isArray(v)) { return v.map(clone)