From: daiwei Date: Tue, 12 Aug 2025 02:49:46 +0000 (+0800) Subject: fix: special handing inject insertion anchors of if node in ssr vnode-based slot X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=e6d037d8fe9b146bf9576f29f48e9a8a9effc4ac;p=thirdparty%2Fvuejs%2Fcore.git fix: special handing inject insertion anchors of if node in ssr vnode-based slot --- diff --git a/packages/compiler-ssr/__tests__/ssrVaporAnchors.spec.ts b/packages/compiler-ssr/__tests__/ssrVaporAnchors.spec.ts index 9fd5fed6ac..d29be57cfe 100644 --- a/packages/compiler-ssr/__tests__/ssrVaporAnchors.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrVaporAnchors.spec.ts @@ -1,8 +1,3 @@ -// import { -// BLOCK_APPEND_ANCHOR_LABEL, -// BLOCK_INSERTION_ANCHOR_LABEL, -// BLOCK_PREPEND_ANCHOR_LABEL, -// } from '@vue/shared' import { getCompiledString } from './utils' describe('insertion anchors', () => { @@ -109,29 +104,46 @@ describe('insertion anchors', () => { `) }) - test('prepend anchor with v-if', () => { + test('prepend anchor with v-if/else-if/else', () => { expect( - getCompiledString('
', { - vapor: true, - }), + getCompiledString( + `
+ + + + +
`, + { + vapor: true, + }, + ), ).toMatchInlineSnapshot(` "\`
\`) if (_ctx.foo) { _push(\`\`) _push(\`\`) + } else if (_ctx.bar) { + _push(\`\`) + _push(\`\`) } else { - _push(\`\`) + _push(\`\`) + _push(\`\`) } _push(\`
\`" `) }) - test('prepend anchor with v-if in ssr slot vnode fallback', () => { + test('prepend anchor with v-if/else-if/else in ssr slot vnode fallback', () => { expect( getCompiledString( ` -
-
`, +
+ + + + +
+ `, { vapor: true }, ), ).toMatchInlineSnapshot(` @@ -143,8 +155,12 @@ describe('insertion anchors', () => { if (_ctx.foo) { _push(\`\`) _push(\`\`) + } else if (_ctx.bar) { + _push(\`\`) + _push(\`\`) } else { - _push(\`\`) + _push(\`\`) + _push(\`\`) } _push(\`\`) } else { @@ -153,7 +169,170 @@ describe('insertion anchors', () => { _createCommentVNode("[p"), (_ctx.foo) ? (_openBlock(), _createBlock("span", { key: 0 })) - : _createCommentVNode("v-if", true), + : (_ctx.bar) + ? (_openBlock(), _createBlock("span", { key: 1 })) + : (_openBlock(), _createBlock("span", { key: 2 })), + _createCommentVNode("p]"), + _createVNode("span") + ]) + ] + } + }), + _: 1 /* STABLE */ + }), _parent) + _push(\`\`" + `) + }) + + test('prepend anchor with nested v-if', () => { + expect( + getCompiledString( + `
+ + + + + + + + + + + + + +
`, + { + vapor: true, + }, + ), + ).toMatchInlineSnapshot(` + "\`
\`) + if (_ctx.foo) { + _push(\`\`) + if (_ctx.foo1) { + _push(\`\`) + _push(\`\`) + } else { + _push(\`\`) + } + _push(\`\`) + _push(\`\`) + } else if (_ctx.bar) { + _push(\`\`) + if (_ctx.bar1) { + _push(\`\`) + _push(\`\`) + } else { + _push(\`\`) + } + _push(\`\`) + _push(\`\`) + } else { + _push(\`\`) + if (_ctx.bar2) { + _push(\`\`) + _push(\`\`) + } else { + _push(\`\`) + } + _push(\`\`) + _push(\`\`) + } + _push(\`
\`" + `) + }) + + test('prepend anchor with nested v-if in ssr slot vnode fallback', () => { + expect( + getCompiledString( + ` +
+ + + + + + + + + + + + + +
+
`, + { vapor: true }, + ), + ).toMatchInlineSnapshot(` + "\`\`) + _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent('div'), null, { + default: _withCtx((_, _push, _parent, _scopeId) => { + if (_push) { + _push(\`\`) + if (_ctx.foo) { + _push(\`\`) + if (_ctx.foo1) { + _push(\`\`) + _push(\`\`) + } else { + _push(\`\`) + } + _push(\`\`) + _push(\`\`) + } else if (_ctx.bar) { + _push(\`\`) + if (_ctx.bar1) { + _push(\`\`) + _push(\`\`) + } else { + _push(\`\`) + } + _push(\`\`) + _push(\`\`) + } else { + _push(\`\`) + if (_ctx.bar2) { + _push(\`\`) + _push(\`\`) + } else { + _push(\`\`) + } + _push(\`\`) + _push(\`\`) + } + _push(\`\`) + } else { + return [ + _createVNode("div", null, [ + _createCommentVNode("[p"), + (_ctx.foo) + ? (_openBlock(), _createBlock("span", { key: 0 }, [ + _createCommentVNode("[p"), + (_ctx.foo1) + ? (_openBlock(), _createBlock("span", { key: 0 })) + : _createCommentVNode("v-if", true), + _createCommentVNode("p]"), + _createVNode("span") + ])) + : (_ctx.bar) + ? (_openBlock(), _createBlock("span", { key: 1 }, [ + _createCommentVNode("[p"), + (_ctx.bar1) + ? (_openBlock(), _createBlock("span", { key: 0 })) + : _createCommentVNode("v-if", true), + _createCommentVNode("p]"), + _createVNode("span") + ])) + : (_openBlock(), _createBlock("span", { key: 2 }, [ + _createCommentVNode("[p"), + (_ctx.bar2) + ? (_openBlock(), _createBlock("span", { key: 0 })) + : _createCommentVNode("v-if", true), + _createCommentVNode("p]"), + _createVNode("span") + ])), _createCommentVNode("p]"), _createVNode("span") ]) diff --git a/packages/compiler-ssr/src/ssrCodegenTransform.ts b/packages/compiler-ssr/src/ssrCodegenTransform.ts index 30ec8050ed..fc32850e7e 100644 --- a/packages/compiler-ssr/src/ssrCodegenTransform.ts +++ b/packages/compiler-ssr/src/ssrCodegenTransform.ts @@ -1,8 +1,11 @@ import { + type AttributeNode, type BlockStatement, type CallExpression, type CompilerError, type CompilerOptions, + type DirectiveNode, + type ElementNode, ElementTypes, type IfStatement, type JSChildNode, @@ -169,12 +172,11 @@ export function processChildren( context.pushStringPart(``) } - const { children, type, tagType } = parent as PlainElementNode + const { children } = parent if ( context.options.vapor && - type === NodeTypes.ELEMENT && - tagType === ElementTypes.ELEMENT + isElementWithChildren(parent as PlainElementNode) ) { processBlockNodeAnchor(children) } @@ -308,6 +310,12 @@ export function processBlockNodeAnchor(children: TemplateChildNode[]): void { } } +export function hasBlockDir( + props: Array, +): boolean { + return props.some(p => p.name === 'if' || p.name === 'for') +} + function isBlockNode(child: TemplateChildNode): boolean { return ( child.type === NodeTypes.IF || @@ -315,13 +323,7 @@ function isBlockNode(child: TemplateChildNode): boolean { (child.type === NodeTypes.ELEMENT && (child.tagType === ElementTypes.COMPONENT || child.tagType === ElementTypes.SLOT || - child.props.some( - p => - p.name === 'if' || - p.name === 'else-if' || - p.name === 'else' || - p.name === 'for', - ))) + hasBlockDir(child.props))) ) } @@ -332,12 +334,16 @@ function isStaticNode(child: TemplateChildNode): boolean { child.type === NodeTypes.COMMENT || (child.type === NodeTypes.ELEMENT && child.tagType === ElementTypes.ELEMENT && - !child.props.some( - p => - p.name === 'if' || - p.name === 'else-if' || - p.name === 'else' || - p.name === 'for', - )) + !hasBlockDir(child.props)) + ) +} + +export function isElementWithChildren( + node: TemplateChildNode, +): node is ElementNode { + return ( + node.type === NodeTypes.ELEMENT && + node.tagType === ElementTypes.ELEMENT && + node.children.length > 0 ) } diff --git a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts index 5af6041480..db63235caf 100644 --- a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts +++ b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts @@ -8,7 +8,9 @@ import { type DirectiveNode, ElementTypes, type ExpressionNode, + type ForNode, type FunctionExpression, + type IfNode, type JSChildNode, Namespaces, type NodeTransform, @@ -43,6 +45,7 @@ import { import { SSR_RENDER_COMPONENT, SSR_RENDER_VNODE } from '../runtimeHelpers' import { type SSRTransformContext, + isElementWithChildren, processBlockNodeAnchor, processChildren, processChildrenAsStatement, @@ -333,6 +336,11 @@ function createVNodeSlotBranch( if (vFor) { wrapperProps.push(extend({}, vFor)) } + + if (parentContext.vapor) { + children = injectVaporInsertionAnchors(children) + } + const wrapperNode: TemplateNode = { type: NodeTypes.ELEMENT, ns: Namespaces.HTML, @@ -344,10 +352,6 @@ function createVNodeSlotBranch( codegenNode: undefined, } - if (parentContext.vapor) { - injectVaporInsertionAnchors(children) - } - subTransform(wrapperNode, subOptions, parentContext) return createReturnStatement(children) } @@ -389,69 +393,131 @@ function subTransform( // - hoists are not enabled for the client branch here } -function injectVaporInsertionAnchors(children: TemplateChildNode[]) { +function injectVaporInsertionAnchors( + children: TemplateChildNode[], +): TemplateChildNode[] { processBlockNodeAnchor(children) + const newChildren: TemplateChildNode[] = new Array(children.length * 3) + let newIndex = 0 + for (let i = 0; i < children.length; i++) { const child = children[i] - switch (child.type) { - case NodeTypes.ELEMENT: - switch (child.tagType) { - case ElementTypes.COMPONENT: - case ElementTypes.SLOT: - if (child.anchor) { - children.splice(i, 0, { - type: NodeTypes.COMMENT, - content: `[${child.anchor}`, - loc: locStub, - }) - children.splice(i + 2, 0, { - type: NodeTypes.COMMENT, - content: `${child.anchor}]`, - loc: locStub, - }) - i += 2 - } - break - default: { - const { props } = child - if ( - props.some( - p => - p.name === 'if' || - p.name === 'else-if' || - p.name === 'else' || - p.name === 'for', - ) - ) { - // @ts-expect-error - if (child.anchor) { - children.splice(i, 0, { - type: NodeTypes.COMMENT, - // @ts-expect-error - content: `[${child.anchor}`, - loc: locStub, - }) - children.splice(i + 2, 0, { - type: NodeTypes.COMMENT, - // @ts-expect-error - content: `${child.anchor}]`, - loc: locStub, - }) + + if (child.type !== NodeTypes.ELEMENT) { + newChildren[newIndex++] = child + continue + } + + const { tagType, props } = child + let anchor: string | undefined + + if (tagType === ElementTypes.COMPONENT || tagType === ElementTypes.SLOT) { + anchor = child.anchor + } else if (tagType === ElementTypes.ELEMENT) { + let hasIf = false + let hasFor = false + + for (const prop of props) { + if (prop.name === 'if') { + hasIf = true + break + } else if (prop.name === 'for') { + hasFor = true + } + } + + 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] + newChildren[newIndex++] = node + + if (isElementWithChildren(node)) { + node.children = injectVaporInsertionAnchors(node.children) } - i += 2 - break } + + // inject anchor after branch nodes + newChildren[newIndex++] = createAnchorComment(`${anchor}]`) + + i = lastBranchIndex + continue } } + } else if (hasFor) { + anchor = (child as any as ForNode).anchor + } } - if ( - child.type === NodeTypes.ELEMENT && - child.tagType === ElementTypes.ELEMENT - ) { - injectVaporInsertionAnchors(child.children) + // inject anchor before and after the child + if (anchor) newChildren[newIndex++] = createAnchorComment(`[${anchor}`) + newChildren[newIndex++] = child + if (anchor) newChildren[newIndex++] = createAnchorComment(`${anchor}]`) + + if (isElementWithChildren(child)) { + child.children = injectVaporInsertionAnchors(child.children) + } + } + + newChildren.length = newIndex + return newChildren +} + +function createAnchorComment(content: string): TemplateChildNode { + return { + type: NodeTypes.COMMENT, + content, + loc: locStub, + } +} + +function findLastIfBranchIndex( + children: TemplateChildNode[], + ifIndex: number, +): number { + let lastIndex = ifIndex + + for (let i = ifIndex + 1; i < children.length; i++) { + const sibling = children[i] + + if (sibling.type !== NodeTypes.ELEMENT) { + continue + } + + let hasElseIf = false + let hasElse = false + + for (const prop of sibling.props) { + if (prop.name === 'else-if') { + hasElseIf = true + break + } else if (prop.name === 'else') { + hasElse = true + break + } + } + + if (hasElseIf || hasElse) { + lastIndex = i + if (hasElse) { + break + } + } else { + break } } + + return lastIndex } function clone(v: any): any {