diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap index 3d1368b74..2edfdd249 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap @@ -5,7 +5,7 @@ exports[`compiler: v-for > array de-structured value 1`] = ` const t0 = _template("
") export function render(_ctx) { - const n0 = _createFor(() => (_ctx.list), _withDestructure((_state, [[id, ...other], index] = _state) => [id, other, index], (_ctx0) => { + const n0 = _createFor(() => (_ctx.list), _withDestructure(([[id, ...other], index]) => [id, other, index], (_ctx0) => { const n2 = t0() _renderEffect(() => _setText(n2, _ctx0[0] + _ctx0[1] + _ctx0[2])) return n2 @@ -53,9 +53,9 @@ const t1 = _template("
") export function render(_ctx) { const n0 = _createFor(() => (_ctx.list), (_ctx0) => { const n5 = t1() - const n2 = _createFor(() => (_ctx0[0]), (_ctx2) => { + const n2 = _createFor(() => (_ctx0[0]), (_ctx1) => { const n4 = t0() - _renderEffect(() => _setText(n4, _ctx2[0]+_ctx0[0])) + _renderEffect(() => _setText(n4, _ctx1[0]+_ctx0[0])) return n4 }) _insert(n2, n5) @@ -70,7 +70,7 @@ exports[`compiler: v-for > object de-structured value 1`] = ` const t0 = _template("
") export function render(_ctx) { - const n0 = _createFor(() => (_ctx.list), _withDestructure((_state, [{ id, ...other }, index] = _state) => [id, other, index], (_ctx0) => { + const n0 = _createFor(() => (_ctx.list), _withDestructure(([{ id, ...other }, index]) => [id, other, index], (_ctx0) => { const n2 = t0() _renderEffect(() => _setText(n2, _ctx0[0] + _ctx0[1] + _ctx0[2])) return n2 @@ -84,7 +84,7 @@ exports[`compiler: v-for > v-for aliases w/ complex expressions 1`] = ` const t0 = _template("
") export function render(_ctx) { - const n0 = _createFor(() => (_ctx.list), _withDestructure((_state, [{ foo = bar, baz: [qux = quux] }] = _state) => [foo, qux], (_ctx0) => { + const n0 = _createFor(() => (_ctx.list), _withDestructure(([{ foo = bar, baz: [qux = quux] }]) => [foo, qux], (_ctx0) => { const n2 = t0() _renderEffect(() => _setText(n2, _ctx0[0] + _ctx.bar + _ctx.baz + _ctx0[1] + _ctx.quux)) return n2 diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap index b5075babe..05b8f1cc2 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap @@ -18,17 +18,17 @@ export function render(_ctx) { `; exports[`compiler: transform slot > dynamic slots name w/ v-for 1`] = ` -"import { resolveComponent as _resolveComponent, createComponent as _createComponent, createForSlots as _createForSlots, template as _template } from 'vue/vapor'; +"import { resolveComponent as _resolveComponent, createComponent as _createComponent, withDestructure as _withDestructure, createForSlots as _createForSlots, template as _template } from 'vue/vapor'; const t0 = _template("foo") export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n2 = _createComponent(_component_Comp, null, null, () => [_createForSlots(_ctx.list, (item) => ({ name: item, - fn: () => { + fn: _withDestructure(({ bar }) => [bar], (_ctx0) => { const n0 = t0() return n0 - } + }) }))], true) return n2 }" @@ -52,7 +52,7 @@ export function render(_ctx) { `; exports[`compiler: transform slot > dynamic slots name w/ v-if / v-else[-if] 1`] = ` -"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor'; +"import { resolveComponent as _resolveComponent, createComponent as _createComponent, withDestructure as _withDestructure, template as _template } from 'vue/vapor'; const t0 = _template("condition slot") const t1 = _template("another condition") const t2 = _template("else condition") @@ -71,10 +71,10 @@ export function render(_ctx) { : _ctx.anotherCondition ? { name: "condition", - fn: () => { + fn: _withDestructure(({ foo, bar }) => [foo, bar], (_ctx0) => { const n2 = t1() return n2 - }, + }), key: "1" } : { @@ -126,20 +126,64 @@ export function render(_ctx) { }" `; -exports[`compiler: transform slot > nested slots 1`] = ` -"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor'; -const t0 = _template("
") +exports[`compiler: transform slot > nested slots scoping 1`] = ` +"import { resolveComponent as _resolveComponent, createComponent as _createComponent, createTextNode as _createTextNode, withDestructure as _withDestructure, template as _template } from 'vue/vapor'; +const t0 = _template(" ") export function render(_ctx) { - const _component_Bar = _resolveComponent("Bar") - const _component_Foo = _resolveComponent("Foo") - const n3 = _createComponent(_component_Foo, null, { one: () => { - const n1 = _createComponent(_component_Bar, null, { default: () => { - const n0 = t0() + const _component_Inner = _resolveComponent("Inner") + const _component_Comp = _resolveComponent("Comp") + const n5 = _createComponent(_component_Comp, null, { default: _withDestructure(({ foo }) => [foo], (_ctx0) => { + const n2 = t0() + const n1 = _createComponent(_component_Inner, null, { default: _withDestructure(({ bar }) => [bar], (_ctx1) => { + const n0 = _createTextNode(() => [_ctx0[0] + _ctx1[0] + _ctx.baz]) return n0 - } }) - return n1 - } }, null, true) - return n3 + }) }) + const n3 = _createTextNode(() => [_ctx0[0] + _ctx.bar + _ctx.baz]) + return [n1, n2, n3] + }) }, null, true) + return n5 +}" +`; + +exports[`compiler: transform slot > on component dynamically named slot 1`] = ` +"import { resolveComponent as _resolveComponent, createComponent as _createComponent, createTextNode as _createTextNode, withDestructure as _withDestructure } from 'vue/vapor'; + +export function render(_ctx) { + const _component_Comp = _resolveComponent("Comp") + const n1 = _createComponent(_component_Comp, null, { }, () => [{ + name: _ctx.named, + fn: _withDestructure(({ foo }) => [foo], (_ctx0) => { + const n0 = _createTextNode(() => [_ctx0[0] + _ctx.bar]) + return n0 + }) + }], true) + return n1 +}" +`; + +exports[`compiler: transform slot > on component named slot 1`] = ` +"import { resolveComponent as _resolveComponent, createComponent as _createComponent, createTextNode as _createTextNode, withDestructure as _withDestructure } from 'vue/vapor'; + +export function render(_ctx) { + const _component_Comp = _resolveComponent("Comp") + const n1 = _createComponent(_component_Comp, null, { named: _withDestructure(({ foo }) => [foo], (_ctx0) => { + const n0 = _createTextNode(() => [_ctx0[0] + _ctx.bar]) + return n0 + }) }, null, true) + return n1 +}" +`; + +exports[`compiler: transform slot > on-component default slot 1`] = ` +"import { resolveComponent as _resolveComponent, createComponent as _createComponent, createTextNode as _createTextNode, withDestructure as _withDestructure } from 'vue/vapor'; + +export function render(_ctx) { + const _component_Comp = _resolveComponent("Comp") + const n1 = _createComponent(_component_Comp, null, { default: _withDestructure(({ foo }) => [foo], (_ctx0) => { + const n0 = _createTextNode(() => [_ctx0[0] + _ctx.bar]) + return n0 + }) }, null, true) + return n1 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts b/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts index f3fa72a47..9aca84243 100644 --- a/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts @@ -93,8 +93,8 @@ describe('compiler: v-for', () => { ) expect(code).matchSnapshot() expect(code).contains(`_createFor(() => (_ctx.list), (_ctx0) => {`) - expect(code).contains(`_createFor(() => (_ctx0[0]), (_ctx2) => {`) - expect(code).contains(`_ctx2[0]+_ctx0[0]`) + expect(code).contains(`_createFor(() => (_ctx0[0]), (_ctx1) => {`) + expect(code).contains(`_ctx1[0]+_ctx0[0]`) expect(ir.template).toEqual(['', '
']) expect(ir.block.operation).toMatchObject([ { @@ -129,9 +129,7 @@ describe('compiler: v-for', () => { `
{{ id + other + index }}
`, ) expect(code).matchSnapshot() - expect(code).contains( - `(_state, [{ id, ...other }, index] = _state) => [id, other, index]`, - ) + expect(code).contains(`([{ id, ...other }, index]) => [id, other, index]`) expect(code).contains(`_ctx0[0] + _ctx0[1] + _ctx0[2]`) expect(ir.block.operation[0]).toMatchObject({ type: IRNodeTypes.FOR, @@ -164,9 +162,7 @@ describe('compiler: v-for', () => { `
{{ id + other + index }}
`, ) expect(code).matchSnapshot() - expect(code).contains( - `(_state, [[id, ...other], index] = _state) => [id, other, index]`, - ) + expect(code).contains(`([[id, ...other], index]) => [id, other, index]`) expect(code).contains(`_ctx0[0] + _ctx0[1] + _ctx0[2]`) expect(ir.block.operation[0]).toMatchObject({ type: IRNodeTypes.FOR, @@ -201,9 +197,7 @@ describe('compiler: v-for', () => { `, ) expect(code).matchSnapshot() - expect(code).contains( - `(_state, [{ foo = bar, baz: [qux = quux] }] = _state) => [foo, qux]`, - ) + expect(code).contains(`([{ foo = bar, baz: [qux = quux] }]) => [foo, qux]`) expect(code).contains( `_ctx0[0] + _ctx.bar + _ctx.baz + _ctx0[1] + _ctx.quux`, ) diff --git a/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts index ce09fb646..d6a021d31 100644 --- a/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts @@ -58,6 +58,98 @@ describe('compiler: transform slot', () => { }) }) + test('on-component default slot', () => { + const { ir, code, vaporHelpers } = compileWithSlots( + `{{ foo + bar }}`, + ) + expect(code).toMatchSnapshot() + + expect(vaporHelpers).contains('withDestructure') + expect(code).contains(`({ foo }) => [foo]`) + expect(code).contains(`_ctx0[0] + _ctx.bar`) + + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.CREATE_COMPONENT_NODE, + tag: 'Comp', + props: [[]], + slots: { + default: { + type: IRNodeTypes.BLOCK, + props: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: '{ foo }', + ast: { + type: 'ArrowFunctionExpression', + params: [{ type: 'ObjectPattern' }], + }, + }, + }, + }, + }, + ]) + }) + + test('on component named slot', () => { + const { ir, code } = compileWithSlots( + `{{ foo + bar }}`, + ) + expect(code).toMatchSnapshot() + + expect(code).contains(`({ foo }) => [foo]`) + expect(code).contains(`_ctx0[0] + _ctx.bar`) + + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.CREATE_COMPONENT_NODE, + tag: 'Comp', + slots: { + named: { + type: IRNodeTypes.BLOCK, + props: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: '{ foo }', + }, + }, + }, + }, + ]) + }) + + test('on component dynamically named slot', () => { + const { ir, code, vaporHelpers } = compileWithSlots( + `{{ foo + bar }}`, + ) + expect(code).toMatchSnapshot() + + expect(vaporHelpers).contains('withDestructure') + expect(code).contains(`({ foo }) => [foo]`) + expect(code).contains(`_ctx0[0] + _ctx.bar`) + + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.CREATE_COMPONENT_NODE, + tag: 'Comp', + dynamicSlots: [ + { + name: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'named', + isStatic: false, + }, + fn: { + type: IRNodeTypes.BLOCK, + props: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: '{ foo }', + }, + }, + }, + ], + }, + ]) + }) + test('named slots w/ implicit default slot', () => { const { ir, code } = compileWithSlots( ` @@ -91,13 +183,56 @@ describe('compiler: transform slot', () => { ]) }) - test('nested slots', () => { - const { code } = compileWithSlots( - ` - - `, + test('nested slots scoping', () => { + const { ir, code, vaporHelpers } = compileWithSlots( + ` + + `, ) expect(code).toMatchSnapshot() + + expect(vaporHelpers).contains('withDestructure') + expect(code).contains(`({ foo }) => [foo]`) + expect(code).contains(`({ bar }) => [bar]`) + expect(code).contains(`_ctx0[0] + _ctx1[0] + _ctx.baz`) + expect(code).contains(`_ctx0[0] + _ctx.bar + _ctx.baz`) + + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.CREATE_COMPONENT_NODE, + tag: 'Comp', + props: [[]], + slots: { + default: { + type: IRNodeTypes.BLOCK, + props: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: '{ foo }', + }, + }, + }, + }, + ]) + expect( + (ir.block.operation[0] as any).slots.default.operation[0], + ).toMatchObject({ + type: IRNodeTypes.CREATE_COMPONENT_NODE, + tag: 'Inner', + slots: { + default: { + type: IRNodeTypes.BLOCK, + props: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: '{ bar }', + }, + }, + }, + }) }) test('dynamic slots name', () => { @@ -128,12 +263,16 @@ describe('compiler: transform slot', () => { }) test('dynamic slots name w/ v-for', () => { - const { ir, code } = compileWithSlots( + const { ir, code, vaporHelpers } = compileWithSlots( ` - + `, ) expect(code).toMatchSnapshot() + + expect(vaporHelpers).contains('withDestructure') + expect(code).contains(`({ bar }) => [bar]`) + expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE) expect(ir.block.operation).toMatchObject([ { @@ -196,14 +335,18 @@ describe('compiler: transform slot', () => { }) test('dynamic slots name w/ v-if / v-else[-if]', () => { - const { ir, code } = compileWithSlots( + const { ir, code, vaporHelpers } = compileWithSlots( ` - + `, ) expect(code).toMatchSnapshot() + + expect(vaporHelpers).contains('withDestructure') + expect(code).contains(`({ foo, bar }) => [foo, bar]`) + expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE) expect(ir.block.operation).toMatchObject([ { @@ -277,5 +420,49 @@ describe('compiler: transform slot', () => { }, }) }) + + test('error on invalid mixed slot usage', () => { + const onError = vi.fn() + const source = `` + compileWithSlots(source, { onError }) + const index = source.lastIndexOf('v-slot="foo"') + expect(onError.mock.calls[0][0]).toMatchObject({ + code: ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, + loc: { + start: { + offset: index, + line: 1, + column: index + 1, + }, + end: { + offset: index + 12, + line: 1, + column: index + 13, + }, + }, + }) + }) + + test('error on v-slot usage on plain elements', () => { + const onError = vi.fn() + const source = `
` + compileWithSlots(source, { onError }) + const index = source.indexOf('v-slot') + expect(onError.mock.calls[0][0]).toMatchObject({ + code: ErrorCodes.X_V_SLOT_MISPLACED, + loc: { + start: { + offset: index, + line: 1, + column: index + 1, + }, + end: { + offset: index + 6, + line: 1, + column: index + 7, + }, + }, + }) + }) }) }) diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts index f6f4b463f..bb36a3e2b 100644 --- a/packages/compiler-vapor/src/generate.ts +++ b/packages/compiler-vapor/src/generate.ts @@ -59,6 +59,11 @@ export class CodegenContext { return () => (this.block = parent) } + scopeLevel: number = 0 + enterScope() { + return [this.scopeLevel++, () => this.scopeLevel--] as const + } + constructor( public ir: RootIRNode, options: CodegenOptions, diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts index 1a3f9a6f6..b491b48f8 100644 --- a/packages/compiler-vapor/src/generators/component.ts +++ b/packages/compiler-vapor/src/generators/component.ts @@ -5,6 +5,7 @@ import { type ComponentConditionalDynamicSlot, type ComponentDynamicSlot, type ComponentLoopDynamicSlot, + type ComponentSlotBlockIRNode, type ComponentSlots, type CreateComponentIRNode, DynamicSlotType, @@ -27,7 +28,11 @@ import { } from './utils' import { genExpression } from './expression' import { genPropKey } from './prop' -import { createSimpleExpression, toValidAssetId } from '@vue/compiler-dom' +import { + createSimpleExpression, + toValidAssetId, + walkIdentifiers, +} from '@vue/compiler-core' import { genEventHandler } from './event' import { genDirectiveModifiers, genDirectivesForElement } from './directive' import { genModelHandler } from './modelValue' @@ -151,7 +156,11 @@ function genSlots(slots: ComponentSlots, context: CodegenContext) { const names = Object.keys(slots) return genMulti( names.length > 1 ? DELIMITERS_OBJECT_NEWLINE : DELIMITERS_OBJECT, - ...names.map(name => [name, ': ', ...genBlock(slots[name], context)]), + ...names.map(name => [ + name, + ': ', + ...genSlotBlockWithProps(slots[name], context), + ]), ) } @@ -188,7 +197,7 @@ function genBasicDynamicSlot( return genMulti( DELIMITERS_OBJECT_NEWLINE, ['name: ', ...genExpression(name, context)], - ['fn: ', ...genBlock(fn, context)], + ['fn: ', ...genSlotBlockWithProps(fn, context)], ...(key !== undefined ? [`key: "${key}"`] : []), ) } @@ -210,7 +219,10 @@ function genLoopSlot( const slotExpr = genMulti( DELIMITERS_OBJECT_NEWLINE, ['name: ', ...context.withId(() => genExpression(name, context), idMap)], - ['fn: ', ...context.withId(() => genBlock(fn, context), idMap)], + [ + 'fn: ', + ...context.withId(() => genSlotBlockWithProps(fn, context), idMap), + ], ) return [ ...genCall( @@ -248,3 +260,58 @@ function genConditionalSlot( INDENT_END, ] } + +function genSlotBlockWithProps( + oper: ComponentSlotBlockIRNode, + context: CodegenContext, +) { + let isDestructureAssignment = false + let rawProps: string | undefined + let propsName: string | undefined + let exitScope: (() => void) | undefined + let depth: number | undefined + const { props } = oper + const idsOfProps = new Set() + + if (props) { + rawProps = props.content + if ((isDestructureAssignment = !!props.ast)) { + ;[depth, exitScope] = context.enterScope() + propsName = `_ctx${depth}` + walkIdentifiers( + props.ast, + (id, _, __, ___, isLocal) => { + if (isLocal) idsOfProps.add(id.name) + }, + true, + ) + } else { + idsOfProps.add((propsName = rawProps)) + } + } + + const idMap: Record = {} + + Array.from(idsOfProps).forEach( + (id, idIndex) => + (idMap[id] = isDestructureAssignment ? `${propsName}[${idIndex}]` : null), + ) + let blockFn = context.withId( + () => genBlock(oper, context, [propsName]), + idMap, + ) + exitScope && exitScope() + + if (isDestructureAssignment) { + const idMap: Record = {} + idsOfProps.forEach(id => (idMap[id] = null)) + + blockFn = genCall( + context.vaporHelper('withDestructure'), + ['(', rawProps, ') => ', ...genMulti(DELIMITERS_ARRAY, ...idsOfProps)], + blockFn, + ) + } + + return blockFn +} diff --git a/packages/compiler-vapor/src/generators/for.ts b/packages/compiler-vapor/src/generators/for.ts index c8d74342c..20627c9e5 100644 --- a/packages/compiler-vapor/src/generators/for.ts +++ b/packages/compiler-vapor/src/generators/for.ts @@ -41,7 +41,8 @@ export function genFor( } } - const propsName = `_ctx${id}` + const [depth, exitScope] = context.enterScope() + const propsName = `_ctx${depth}` const idMap: Record = {} Array.from(idsOfValue).forEach( (id, idIndex) => (idMap[id] = `${propsName}[${idIndex}]`), @@ -53,6 +54,7 @@ export function genFor( () => genBlock(render, context, [propsName]), idMap, ) + exitScope() let getKeyFn: CodeFragment[] | false = false if (keyProp) { @@ -81,14 +83,14 @@ export function genFor( if (rawKey) idMap[rawKey] = null if (rawIndex) idMap[rawIndex] = null const destructureAssignmentFn: CodeFragment[] = [ - '(_state, ', + '(', ...genMulti( DELIMITERS_ARRAY, rawValue ? rawValue : rawKey || rawIndex ? '_' : undefined, rawKey ? rawKey : rawIndex ? '__' : undefined, rawIndex, ), - ' = _state) => ', + ') => ', ...genMulti(DELIMITERS_ARRAY, ...idsOfValue, rawKey, rawIndex), ] @@ -101,7 +103,7 @@ export function genFor( return [ NEWLINE, - `const n${oper.id} = `, + `const n${id} = `, ...genCall( vaporHelper('createFor'), sourceExpr, diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index 83e03126f..0cbe6e772 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -208,7 +208,7 @@ export interface WithDirectiveIRNode extends BaseIRNode { } export interface ComponentSlotBlockIRNode extends BlockIRNode { - // TODO slot props + props?: SimpleExpressionNode } export type ComponentSlots = Record diff --git a/packages/compiler-vapor/src/transforms/vSlot.ts b/packages/compiler-vapor/src/transforms/vSlot.ts index 7aeb2abc9..7af6d4b37 100644 --- a/packages/compiler-vapor/src/transforms/vSlot.ts +++ b/packages/compiler-vapor/src/transforms/vSlot.ts @@ -11,9 +11,9 @@ import { import type { NodeTransform, TransformContext } from '../transform' import { newBlock } from './utils' import { - type BlockIRNode, type ComponentBasicDynamicSlot, type ComponentConditionalDynamicSlot, + type ComponentSlotBlockIRNode, DynamicFlag, DynamicSlotType, type IRFor, @@ -25,19 +25,22 @@ import { findDir, resolveExpression } from '../utils' export const transformVSlot: NodeTransform = (node, context) => { if (node.type !== NodeTypes.ELEMENT) return - let dir: VaporDirectiveNode | undefined + const dir = findDir(node, 'slot', true) const { tagType, children } = node const { parent } = context - const isDefaultSlot = tagType === ElementTypes.COMPONENT && children.length + const isComponent = tagType === ElementTypes.COMPONENT const isSlotTemplate = isTemplateNode(node) && parent && parent.node.type === NodeTypes.ELEMENT && parent.node.tagType === ElementTypes.COMPONENT - if (isDefaultSlot) { - const defaultChildren = children.filter( + if (isComponent && children.length) { + const arg = dir && dir.arg + const slotName = arg ? arg.content : 'default' + + const nonSlotTemplateChildren = children.filter( n => isNonWhitespaceContent(node) && !(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot)), @@ -45,6 +48,7 @@ export const transformVSlot: NodeTransform = (node, context) => { const [block, onExit] = createSlotBlock( node, + dir, context as TransformContext, ) @@ -54,25 +58,44 @@ export const transformVSlot: NodeTransform = (node, context) => { return () => { onExit() - if (defaultChildren.length) { + let hasOtherSlots = !!Object.keys(slots).length + + if (dir && (hasOtherSlots || dynamicSlots.length)) { + // already has on-component slot - this is incorrect usage. + context.options.onError( + createCompilerError(ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, dir.loc), + ) + // discarding other slots, referenced how compiler-core implements + Object.keys(slots).forEach(slotName => delete slots[slotName]) + dynamicSlots.length = 0 + hasOtherSlots = false + } + + if (nonSlotTemplateChildren.length) { if (slots.default) { context.options.onError( createCompilerError( ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN, - defaultChildren[0].loc, + nonSlotTemplateChildren[0].loc, ), ) + } else if (!arg || arg.isStatic) { + slots[slotName] = block } else { - slots.default = block + dynamicSlots.push({ + slotType: DynamicSlotType.BASIC, + name: arg, + fn: block, + }) } context.slots = slots - } else if (Object.keys(slots).length) { + } else if (hasOtherSlots) { context.slots = slots } if (dynamicSlots.length) context.dynamicSlots = dynamicSlots } - } else if (isSlotTemplate && (dir = findDir(node, 'slot', true))) { + } else if (isSlotTemplate && dir) { let { arg } = dir context.dynamic.flags |= DynamicFlag.NON_TEMPLATE @@ -85,6 +108,7 @@ export const transformVSlot: NodeTransform = (node, context) => { const [block, onExit] = createSlotBlock( node, + dir, context as TransformContext, ) @@ -173,16 +197,22 @@ export const transformVSlot: NodeTransform = (node, context) => { } return () => onExit() + } else if (!isComponent && dir) { + context.options.onError( + createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, dir.loc), + ) } } function createSlotBlock( slotNode: ElementNode, + dir: VaporDirectiveNode | undefined, context: TransformContext, -): [BlockIRNode, () => void] { - const branch: BlockIRNode = newBlock(slotNode) - const exitBlock = context.enterBlock(branch) - return [branch, exitBlock] +): [ComponentSlotBlockIRNode, () => void] { + const block: ComponentSlotBlockIRNode = newBlock(slotNode) + block.props = dir && dir.exp + const exitBlock = context.enterBlock(block) + return [block, exitBlock] } function isNonWhitespaceContent(node: TemplateChildNode): boolean {