diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts index 3256e514c..a7db73a98 100644 --- a/packages/compiler-vapor/src/generators/component.ts +++ b/packages/compiler-vapor/src/generators/component.ts @@ -6,6 +6,7 @@ import { type IRProp, type IRProps, type IRPropsStatic, + type SlotContent, } from '../ir' import { type CodeFragment, @@ -22,6 +23,7 @@ import { createSimpleExpression } from '@vue/compiler-dom' import { genEventHandler } from './event' import { genDirectiveModifiers, genDirectivesForElement } from './directive' import { genModelHandler } from './modelValue' +import { genBlock } from './block' // TODO: generate component slots export function genCreateComponent( @@ -32,6 +34,7 @@ export function genCreateComponent( const tag = genTag() const isRoot = oper.root + const { slots, dynamicSlots } = oper const rawProps = genRawProps(oper.props, context) return [ @@ -41,6 +44,8 @@ export function genCreateComponent( vaporHelper('createComponent'), tag, rawProps || (isRoot ? 'null' : false), + slots ? genSlots(slots, context) : isRoot ? 'null' : false, + dynamicSlots ? genSlots(dynamicSlots, context) : isRoot ? 'null' : false, isRoot && 'true', ), ...genDirectivesForElement(oper.id, context), @@ -134,3 +139,22 @@ function genModelModifiers( const modifiersVal = genDirectiveModifiers(modelModifiers) return [',', NEWLINE, ...modifiersKey, `: () => ({ ${modifiersVal} })`] } + +function genSlots( + slots: SlotContent[], + context: CodegenContext, +): CodeFragment[] { + const frags = genMulti( + SEGMENTS_OBJECT_NEWLINE, + ...slots.map(({ name: key, block }) => { + return [ + ...(key.isStatic + ? [key.content] + : ['[', ...genExpression(key, context), ']']), + ':', + ...genBlock(block, context), + ] + }), + ) + return frags +} diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index bba2f3cfc..06bac4297 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -1,3 +1,4 @@ +import { DynamicSlot } from './../../runtime-vapor/src/componentSlots' import type { BindingTypes, CompoundExpressionNode, @@ -31,6 +32,7 @@ export enum IRNodeTypes { CREATE_TEXT_NODE, CREATE_COMPONENT_NODE, SLOT_OUTLET_NODE, + DYNAMIC_SLOTS, WITH_DIRECTIVE, DECLARE_OLD_REF, // consider make it more general @@ -199,12 +201,23 @@ export interface WithDirectiveIRNode extends BaseIRNode { builtin?: VaporHelper } +export interface SlotContent { + name: SimpleExpressionNode + block: BlockIRNode +} + +export interface DynamicSlotContent extends SlotContent { + key?: SimpleExpressionNode +} + export interface CreateComponentIRNode extends BaseIRNode { type: IRNodeTypes.CREATE_COMPONENT_NODE id: number tag: string props: IRProps[] // TODO slots + slots?: SlotContent[] + dynamicSlots?: DynamicSlotContent[] resolve: boolean root: boolean diff --git a/packages/compiler-vapor/src/transforms/transformChildren.ts b/packages/compiler-vapor/src/transforms/transformChildren.ts index da007fc1b..dd6752ba8 100644 --- a/packages/compiler-vapor/src/transforms/transformChildren.ts +++ b/packages/compiler-vapor/src/transforms/transformChildren.ts @@ -18,6 +18,11 @@ export const transformChildren: NodeTransform = (node, context) => { (node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.TEMPLATE) if (!isFragment && node.type !== NodeTypes.ELEMENT) return + if ( + node.type === NodeTypes.ELEMENT && + node.tagType === ElementTypes.COMPONENT + ) + return for (const [i, child] of node.children.entries()) { const childContext = createContext( diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index e45428af4..ed632144e 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -6,6 +6,7 @@ import { ErrorCodes, NodeTypes, type SimpleExpressionNode, + type TemplateChildNode, createCompilerError, createSimpleExpression, } from '@vue/compiler-dom' @@ -17,22 +18,26 @@ import { isVoidTag, makeMap, } from '@vue/shared' -import type { - DirectiveTransformResult, - NodeTransform, - TransformContext, +import { + type DirectiveTransformResult, + type NodeTransform, + type TransformContext, + transformNode, } from '../transform' import { + type BlockIRNode, DynamicFlag, + type DynamicSlotContent, IRDynamicPropsKind, IRNodeTypes, type IRProp, type IRProps, type IRPropsDynamicAttribute, type IRPropsStatic, + type SlotContent, type VaporDirectiveNode, } from '../ir' -import { EMPTY_EXPRESSION } from './utils' +import { EMPTY_EXPRESSION, newBlock } from './utils' export const isReservedProp = /*#__PURE__*/ makeMap( // the leading comma is intentional so empty string "" is also included @@ -97,6 +102,10 @@ function transformComponentElement( const root = context.root === context.parent && context.parent.node.children.length === 1 + const [slots, dynamicSlots] = buildSlotContent( + context as TransformContext, + ) + context.registerOperation({ type: IRNodeTypes.CREATE_COMPONENT_NODE, id: context.reference(), @@ -104,6 +113,8 @@ function transformComponentElement( props: propsResult[0] ? propsResult[1] : [propsResult[1]], resolve, root, + slots, + dynamicSlots, }) } @@ -350,3 +361,130 @@ function mergePropValues(existing: IRProp, incoming: IRProp) { const newValues = incoming.values existing.values.push(...newValues) } + +function buildSlotContent( + context: TransformContext, +): [slots?: SlotContent[], dynamicSlots?: DynamicSlotContent[]] { + const node = context.node + const slots: SlotContent[] = [] + const dynamicSlots: DynamicSlotContent[] = [] + if (!node.children.length) return [] + let explictlyNamedDefaultSlot = false + const defaultTemplateNodes: TemplateChildNode[] = [] + + for (let i = 0; i < node.children.length; i++) { + const child = node.children[i] + if ( + child.type === NodeTypes.ELEMENT && + child.tagType === ElementTypes.TEMPLATE + ) { + let slotDirective: VaporDirectiveNode | undefined + let slotKey: SimpleExpressionNode | undefined + let isVIf = false + let isVFor = false + for (const prop of child.props as ( + | AttributeNode + | VaporDirectiveNode + )[]) { + if ( + !slotDirective && + prop.type === NodeTypes.DIRECTIVE && + prop.name === 'slot' + ) { + slotDirective = prop + } else if ( + prop.type === NodeTypes.DIRECTIVE && + prop.name === 'if' && + prop.exp + ) { + isVIf = true + } else if ( + prop.type === NodeTypes.DIRECTIVE && + prop.name === 'for' && + prop.exp + ) { + isVFor = true + } else if ( + prop.type === NodeTypes.ATTRIBUTE && + prop.name === 'key' && + prop.value + ) { + slotKey = createSimpleExpression(prop.value?.content, true) + } else if ( + prop.type === NodeTypes.DIRECTIVE && + prop.name === 'bind' && + prop.arg && + prop.arg.content === 'key' && + prop.exp + ) { + slotKey = prop.exp + } + } + const isDynamicSlot = isVIf || (isVFor && !slotDirective?.arg?.isStatic) + const slotArg = slotDirective?.arg + if (slotArg?.content === 'default') explictlyNamedDefaultSlot = true + if (slotArg?.content) { + const slotNode = isDynamicSlot + ? child + : extend({}, child, { + type: NodeTypes.ELEMENT, + tag: 'template', + props: [], + tagType: ElementTypes.TEMPLATE, + children: child.children, + }) + const slotBlock = buildSlotBlock(context, slotNode) + if (isDynamicSlot) { + dynamicSlots.push( + extend( + { + name: slotArg, + block: slotBlock, + }, + slotKey ? { key: slotKey } : {}, + ), + ) + } else { + slots.push({ + name: slotArg, + block: slotBlock, + }) + } + continue + } else if (!explictlyNamedDefaultSlot) { + !isDynamicSlot && defaultTemplateNodes.push(...child.children) + } + } else if (!explictlyNamedDefaultSlot) { + defaultTemplateNodes.push(child) + } + } + + if (!explictlyNamedDefaultSlot) { + const defaultSlotNode = extend({}, node, { + type: NodeTypes.ELEMENT, + tag: 'template', + props: [], + tagType: ElementTypes.TEMPLATE, + children: defaultTemplateNodes, + }) + slots.push({ + name: createSimpleExpression('default', true), + block: buildSlotBlock(context, defaultSlotNode), + }) + } + + return [slots, dynamicSlots] +} + +function buildSlotBlock( + context: TransformContext, + slotNode: ElementNode, +): BlockIRNode { + const block = newBlock(slotNode) + const exit = context.enterBlock(block) + context.node = slotNode + context.reference() + transformNode(context) + exit() + return block +}