feat(compiler-vapor): add support for forwarded slots

This commit is contained in:
daiwei 2025-05-29 17:04:49 +08:00
parent 1ef6e6edb7
commit dde7076120
7 changed files with 57 additions and 4 deletions

View File

@ -18,6 +18,7 @@ import {
genCall, genCall,
} from './generators/utils' } from './generators/utils'
import { setTemplateRefIdent } from './generators/templateRef' import { setTemplateRefIdent } from './generators/templateRef'
import { createForwardedSlotIdent } from './generators/slotOutlet'
export type CodegenOptions = Omit<BaseCodegenOptions, 'optimizeImports'> export type CodegenOptions = Omit<BaseCodegenOptions, 'optimizeImports'>
@ -129,6 +130,12 @@ export function generate(
`const ${setTemplateRefIdent} = ${context.helper('createTemplateRefSetter')}()`, `const ${setTemplateRefIdent} = ${context.helper('createTemplateRefSetter')}()`,
) )
} }
if (ir.hasForwardedSlot) {
push(
NEWLINE,
`const ${createForwardedSlotIdent} = ${context.helper('forwardedSlotCreator')}()`,
)
}
push(...genBlockContent(ir.block, context, true)) push(...genBlockContent(ir.block, context, true))
push(INDENT_END, NEWLINE) push(INDENT_END, NEWLINE)

View File

@ -5,12 +5,14 @@ import { genExpression } from './expression'
import { type CodeFragment, NEWLINE, buildCodeFragment, genCall } from './utils' import { type CodeFragment, NEWLINE, buildCodeFragment, genCall } from './utils'
import { genRawProps } from './component' import { genRawProps } from './component'
export const createForwardedSlotIdent = `_createForwardedSlot`
export function genSlotOutlet( export function genSlotOutlet(
oper: SlotOutletIRNode, oper: SlotOutletIRNode,
context: CodegenContext, context: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {
const { helper } = context const { helper } = context
const { id, name, fallback } = oper const { id, name, fallback, forwarded } = oper
const [frag, push] = buildCodeFragment() const [frag, push] = buildCodeFragment()
const nameExpr = name.isStatic const nameExpr = name.isStatic
@ -26,7 +28,7 @@ export function genSlotOutlet(
NEWLINE, NEWLINE,
`const n${id} = `, `const n${id} = `,
...genCall( ...genCall(
helper('createSlot'), forwarded ? createForwardedSlotIdent : helper('createSlot'),
nameExpr, nameExpr,
genRawProps(oper.props, context) || 'null', genRawProps(oper.props, context) || 'null',
fallbackArg, fallbackArg,

View File

@ -66,6 +66,7 @@ export interface RootIRNode {
directive: Set<string> directive: Set<string>
block: BlockIRNode block: BlockIRNode
hasTemplateRef: boolean hasTemplateRef: boolean
hasForwardedSlot: boolean
} }
export interface IfIRNode extends BaseIRNode { export interface IfIRNode extends BaseIRNode {
@ -209,6 +210,7 @@ export interface SlotOutletIRNode extends BaseIRNode {
name: SimpleExpressionNode name: SimpleExpressionNode
props: IRProps[] props: IRProps[]
fallback?: BlockIRNode fallback?: BlockIRNode
forwarded?: boolean
parent?: number parent?: number
anchor?: number anchor?: number
} }

View File

@ -230,6 +230,7 @@ export function transform(
directive: new Set(), directive: new Set(),
block: newBlock(node), block: newBlock(node),
hasTemplateRef: false, hasTemplateRef: false,
hasForwardedSlot: false,
} }
const context = new TransformContext(ir, node, options) const context = new TransformContext(ir, node, options)

View File

@ -5,6 +5,7 @@ import {
ErrorCodes, ErrorCodes,
NodeTypes, NodeTypes,
type SimpleExpressionNode, type SimpleExpressionNode,
type TemplateChildNode,
createCompilerError, createCompilerError,
createSimpleExpression, createSimpleExpression,
isStaticArgOf, isStaticArgOf,
@ -99,6 +100,13 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
} }
return () => { return () => {
let forwarded = false
const slotNode = context.block.node
if (slotNode.type === NodeTypes.ELEMENT) {
forwarded = hasForwardedSlots(slotNode.children)
}
if (forwarded) context.ir.hasForwardedSlot = true
exitBlock && exitBlock() exitBlock && exitBlock()
context.dynamic.operation = { context.dynamic.operation = {
type: IRNodeTypes.SLOT_OUTLET_NODE, type: IRNodeTypes.SLOT_OUTLET_NODE,
@ -106,6 +114,7 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
name: slotName, name: slotName,
props: irProps, props: irProps,
fallback, fallback,
forwarded,
} }
} }
} }
@ -131,3 +140,21 @@ function createFallback(
context.reference() context.reference()
return [fallback, exitBlock] return [fallback, exitBlock]
} }
// TODO
function hasForwardedSlots(children: TemplateChildNode[]): boolean {
for (let i = 0; i < children.length; i++) {
const child = children[i]
switch (child.type) {
case NodeTypes.ELEMENT:
if (
child.tagType === ElementTypes.SLOT ||
hasForwardedSlots(child.children)
) {
return true
}
break
}
}
return false
}

View File

@ -87,10 +87,24 @@ export function getSlot(
} }
} }
export function forwardedSlotCreator(): (
name: string | (() => string),
rawProps?: LooseRawProps | null,
fallback?: VaporSlot,
) => Block {
const instance = currentInstance as VaporComponentInstance
return (
name: string | (() => string),
rawProps?: LooseRawProps | null,
fallback?: VaporSlot,
) => createSlot(name, rawProps, fallback, instance)
}
export function createSlot( export function createSlot(
name: string | (() => string), name: string | (() => string),
rawProps?: LooseRawProps | null, rawProps?: LooseRawProps | null,
fallback?: VaporSlot, fallback?: VaporSlot,
i?: VaporComponentInstance,
): Block { ): Block {
const _insertionParent = insertionParent const _insertionParent = insertionParent
const _insertionAnchor = insertionAnchor const _insertionAnchor = insertionAnchor
@ -98,7 +112,7 @@ export function createSlot(
locateHydrationNode() locateHydrationNode()
} }
const instance = currentInstance as VaporComponentInstance const instance = i || (currentInstance as VaporComponentInstance)
const rawSlots = instance.rawSlots const rawSlots = instance.rawSlots
const slotProps = rawProps const slotProps = rawProps
? new Proxy(rawProps, rawPropsProxyHandlers) ? new Proxy(rawProps, rawPropsProxyHandlers)

View File

@ -9,7 +9,7 @@ export { insert, prepend, remove, isFragment, VaporFragment } from './block'
export { setInsertionState } from './insertionState' export { setInsertionState } from './insertionState'
export { createComponent, createComponentWithFallback } from './component' export { createComponent, createComponentWithFallback } from './component'
export { renderEffect } from './renderEffect' export { renderEffect } from './renderEffect'
export { createSlot } from './componentSlots' export { createSlot, forwardedSlotCreator } from './componentSlots'
export { template } from './dom/template' export { template } from './dom/template'
export { createTextNode, child, nthChild, next } from './dom/node' export { createTextNode, child, nthChild, next } from './dom/node'
export { export {