diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap
index 1e5e4cf4f..9e401d0ba 100644
--- a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap
@@ -29,15 +29,16 @@ const t0 = _template("
")
export function render(_ctx) {
const _component_Bar = _resolveComponent("Bar")
const _component_Comp = _resolveComponent("Comp")
- const n0 = _createIf(() => (true), () => {
- const n3 = t0()
- const n2 = _createComponent(_component_Bar)
- _withDirectives(n2, [[_resolveDirective("vHello"), void 0, void 0, { world: true }]])
- _insert(n2, n3)
- return n3
- })
- _insert(n0, n4)
- const n4 = _createComponent(_component_Comp, null, true)
+ const n4 = _createComponent(_component_Comp, null, { default: () => {
+ const n0 = _createIf(() => (true), () => {
+ const n3 = t0()
+ const n2 = _createComponent(_component_Bar)
+ _withDirectives(n2, [[_resolveDirective("vHello"), void 0, void 0, { world: true }]])
+ _insert(n2, n3)
+ return n3
+ })
+ return n0
+ } }, null, true)
_withDirectives(n4, [[_resolveDirective("vTest")]])
return n4
}"
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
index 75195b027..2d3bc2f23 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
@@ -5,7 +5,7 @@ exports[`compiler: element transform > component > do not resolve component from
export function render(_ctx) {
const _component_Example = _resolveComponent("Example")
- const n0 = _createComponent(_component_Example, null, true)
+ const n0 = _createComponent(_component_Example, null, null, null, true)
return n0
}"
`;
@@ -25,7 +25,7 @@ exports[`compiler: element transform > component > generate single root componen
"import { createComponent as _createComponent } from 'vue/vapor';
export function render(_ctx) {
- const n0 = _createComponent(_ctx.Comp, null, true)
+ const n0 = _createComponent(_ctx.Comp, null, null, null, true)
return n0
}"
`;
@@ -35,21 +35,21 @@ exports[`compiler: element transform > component > import + resolve component 1`
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
- const n0 = _createComponent(_component_Foo, null, true)
+ const n0 = _createComponent(_component_Foo, null, null, null, true)
return n0
}"
`;
exports[`compiler: element transform > component > resolve component from setup bindings (inline const) 1`] = `
"(() => {
- const n0 = _createComponent(Example, null, true)
+ const n0 = _createComponent(Example, null, null, null, true)
return n0
})()"
`;
exports[`compiler: element transform > component > resolve component from setup bindings (inline) 1`] = `
"(() => {
- const n0 = _createComponent(_unref(Example), null, true)
+ const n0 = _createComponent(_unref(Example), null, null, null, true)
return n0
})()"
`;
@@ -58,14 +58,14 @@ exports[`compiler: element transform > component > resolve component from setup
"import { createComponent as _createComponent } from 'vue/vapor';
export function render(_ctx) {
- const n0 = _createComponent(_ctx.Example, null, true)
+ const n0 = _createComponent(_ctx.Example, null, null, null, true)
return n0
}"
`;
exports[`compiler: element transform > component > resolve namespaced component from props bindings (inline) 1`] = `
"(() => {
- const n0 = _createComponent(Foo.Example, null, true)
+ const n0 = _createComponent(Foo.Example, null, null, null, true)
return n0
})()"
`;
@@ -74,14 +74,14 @@ exports[`compiler: element transform > component > resolve namespaced component
"import { createComponent as _createComponent } from 'vue/vapor';
export function render(_ctx) {
- const n0 = _createComponent(_ctx.Foo.Example, null, true)
+ const n0 = _createComponent(_ctx.Foo.Example, null, null, null, true)
return n0
}"
`;
exports[`compiler: element transform > component > resolve namespaced component from setup bindings (inline const) 1`] = `
"(() => {
- const n0 = _createComponent(Foo.Example, null, true)
+ const n0 = _createComponent(Foo.Example, null, null, null, true)
return n0
})()"
`;
@@ -90,7 +90,7 @@ exports[`compiler: element transform > component > resolve namespaced component
"import { createComponent as _createComponent } from 'vue/vapor';
export function render(_ctx) {
- const n0 = _createComponent(_ctx.Foo.Example, null, true)
+ const n0 = _createComponent(_ctx.Foo.Example, null, null, null, true)
return n0
}"
`;
@@ -102,7 +102,7 @@ export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, [
{ onBar: () => $event => (_ctx.handleBar($event)) }
- ], true)
+ ], null, null, true)
return n0
}"
`;
@@ -117,7 +117,7 @@ export function render(_ctx) {
id: () => ("foo"),
class: () => ("bar")
}
- ], true)
+ ], null, null, true)
return n0
}"
`;
@@ -129,7 +129,7 @@ export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, [
() => (_ctx.obj)
- ], true)
+ ], null, null, true)
return n0
}"
`;
@@ -142,7 +142,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Foo, [
{ id: () => ("foo") },
() => (_ctx.obj)
- ], true)
+ ], null, null, true)
return n0
}"
`;
@@ -155,7 +155,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Foo, [
() => (_ctx.obj),
{ id: () => ("foo") }
- ], true)
+ ], null, null, true)
return n0
}"
`;
@@ -169,7 +169,7 @@ export function render(_ctx) {
{ id: () => ("foo") },
() => (_ctx.obj),
{ class: () => ("bar") }
- ], true)
+ ], null, null, true)
return n0
}"
`;
@@ -181,7 +181,7 @@ export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, [
() => (_toHandlers(_ctx.obj))
- ], true)
+ ], null, null, true)
return n0
}"
`;
@@ -195,7 +195,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Foo, [
() => ({ [_toHandlerKey(_ctx.foo-_ctx.bar)]: () => _ctx.bar }),
() => ({ [_toHandlerKey(_ctx.baz)]: () => _ctx.qux })
- ], true)
+ ], null, null, true)
return n0
}"
`;
@@ -208,7 +208,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Foo, [
() => ({ [_ctx.foo-_ctx.bar]: _ctx.bar }),
() => ({ [_ctx.baz]: _ctx.qux })
- ], true)
+ ], null, null, true)
return n0
}"
`;
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
index 5f44f54cf..62e0ece59 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
@@ -9,7 +9,7 @@ export function render(_ctx) {
{ modelValue: () => (_ctx.foo),
"onUpdate:modelValue": () => $event => (_ctx.foo = $event),
modelModifiers: () => ({ trim: true, "bar-baz": true }) }
- ], true)
+ ], null, null, true)
return n0
}"
`;
@@ -22,7 +22,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Comp, [
{ modelValue: () => (_ctx.foo),
"onUpdate:modelValue": () => $event => (_ctx.foo = $event) }
- ], true)
+ ], null, null, true)
return n0
}"
`;
@@ -41,7 +41,7 @@ export function render(_ctx) {
"onUpdate:bar": () => $event => (_ctx.bar = $event),
barModifiers: () => ({ number: true })
}
- ], true)
+ ], null, null, true)
return n0
}"
`;
@@ -54,7 +54,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Comp, [
{ bar: () => (_ctx.foo),
"onUpdate:bar": () => $event => (_ctx.foo = $event) }
- ], true)
+ ], null, null, true)
return n0
}"
`;
@@ -71,7 +71,7 @@ export function render(_ctx) {
() => ({ [_ctx.bar]: _ctx.bar,
["onUpdate:" + _ctx.bar]: () => $event => (_ctx.bar = $event),
[_ctx.bar + "Modifiers"]: () => ({ number: true }) })
- ], true)
+ ], null, null, true)
return n0
}"
`;
@@ -84,7 +84,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Comp, [
() => ({ [_ctx.arg]: _ctx.foo,
["onUpdate:" + _ctx.arg]: () => $event => (_ctx.foo = $event) })
- ], true)
+ ], null, null, true)
return n0
}"
`;
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
new file mode 100644
index 000000000..ff692bbf6
--- /dev/null
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
@@ -0,0 +1,73 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`compiler: transform slot > dynamic slots name 1`] = `
+"import { resolveComponent as _resolveComponent, createComponent as _createComponent, 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, () => [{
+ name: _ctx.name,
+ fn: () => {
+ const n0 = t0()
+ return n0
+ }
+ }], true)
+ return n2
+}"
+`;
+
+exports[`compiler: transform slot > implicit default slot 1`] = `
+"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor';
+const t0 = _template("")
+
+export function render(_ctx) {
+ const _component_Comp = _resolveComponent("Comp")
+ const n1 = _createComponent(_component_Comp, null, { default: () => {
+ const n0 = t0()
+ return n0
+ } }, null, true)
+ return n1
+}"
+`;
+
+exports[`compiler: transform slot > named slots w/ implicit default slot 1`] = `
+"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor';
+const t0 = _template("foo")
+const t1 = _template("bar")
+const t2 = _template("")
+
+export function render(_ctx) {
+ const _component_Comp = _resolveComponent("Comp")
+ const n4 = _createComponent(_component_Comp, null, {
+ one: () => {
+ const n0 = t0()
+ return n0
+ },
+ default: () => {
+ const n2 = t1()
+ const n3 = t2()
+ return [n2, n3]
+ }
+ }, null, true)
+ return n4
+}"
+`;
+
+exports[`compiler: transform slot > nested slots 1`] = `
+"import { resolveComponent as _resolveComponent, createComponent as _createComponent, 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()
+ return n0
+ } })
+ return n1
+ } }, null, true)
+ return n3
+}"
+`;
diff --git a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts
index 2f5be196e..248241246 100644
--- a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts
+++ b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts
@@ -182,7 +182,9 @@ describe('compiler: element transform', () => {
bindingMetadata: { Comp: BindingTypes.SETUP_CONST },
})
expect(code).toMatchSnapshot()
- expect(code).contains('_createComponent(_ctx.Comp, null, true)')
+ expect(code).contains(
+ '_createComponent(_ctx.Comp, null, null, null, true)',
+ )
})
test('generate multi root component', () => {
diff --git a/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts
new file mode 100644
index 000000000..c98b75538
--- /dev/null
+++ b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts
@@ -0,0 +1,174 @@
+import { ErrorCodes, NodeTypes } from '@vue/compiler-core'
+import {
+ IRNodeTypes,
+ transformChildren,
+ transformElement,
+ transformSlotOutlet,
+ transformText,
+ transformVBind,
+ transformVFor,
+ transformVIf,
+ transformVOn,
+ transformVSlot,
+} from '../../src'
+import { makeCompile } from './_utils'
+
+const compileWithSlots = makeCompile({
+ nodeTransforms: [
+ transformText,
+ transformVIf,
+ transformVFor,
+ transformSlotOutlet,
+ transformElement,
+ transformVSlot,
+ transformChildren,
+ ],
+ directiveTransforms: {
+ bind: transformVBind,
+ on: transformVOn,
+ },
+})
+
+describe('compiler: transform slot', () => {
+ test('implicit default slot', () => {
+ const { ir, code } = compileWithSlots(``)
+ expect(code).toMatchSnapshot()
+
+ expect(ir.template).toEqual([''])
+ expect(ir.block.operation).toMatchObject([
+ {
+ type: IRNodeTypes.CREATE_COMPONENT_NODE,
+ id: 1,
+ tag: 'Comp',
+ props: [[]],
+ slots: {
+ default: {
+ type: IRNodeTypes.BLOCK,
+ dynamic: {
+ children: [{ template: 0 }],
+ },
+ },
+ },
+ },
+ ])
+ expect(ir.block.returns).toEqual([1])
+ expect(ir.block.dynamic).toMatchObject({
+ children: [{ id: 1 }],
+ })
+ })
+
+ test('named slots w/ implicit default slot', () => {
+ const { ir, code } = compileWithSlots(
+ `
+ foobar
+ `,
+ )
+ expect(code).toMatchSnapshot()
+
+ expect(ir.template).toEqual(['foo', 'bar', ''])
+ expect(ir.block.operation).toMatchObject([
+ {
+ type: IRNodeTypes.CREATE_COMPONENT_NODE,
+ id: 4,
+ tag: 'Comp',
+ props: [[]],
+ slots: {
+ one: {
+ type: IRNodeTypes.BLOCK,
+ dynamic: {
+ children: [{ template: 0 }],
+ },
+ },
+ default: {
+ type: IRNodeTypes.BLOCK,
+ dynamic: {
+ children: [{}, { template: 1 }, { template: 2 }],
+ },
+ },
+ },
+ },
+ ])
+ })
+
+ test('nested slots', () => {
+ const { code } = compileWithSlots(
+ `
+
+ `,
+ )
+ expect(code).toMatchSnapshot()
+ })
+
+ test('dynamic slots name', () => {
+ const { ir, code } = compileWithSlots(
+ `
+ foo
+ `,
+ )
+ expect(code).toMatchSnapshot()
+ expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
+ expect(ir.block.operation).toMatchObject([
+ {
+ type: IRNodeTypes.CREATE_COMPONENT_NODE,
+ tag: 'Comp',
+ slots: undefined,
+ dynamicSlots: [
+ {
+ name: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: 'name',
+ isStatic: false,
+ },
+ fn: { type: IRNodeTypes.BLOCK },
+ },
+ ],
+ },
+ ])
+ })
+
+ describe('errors', () => {
+ test('error on extraneous children w/ named default slot', () => {
+ const onError = vi.fn()
+ const source = `foobar`
+ compileWithSlots(source, { onError })
+ const index = source.indexOf('bar')
+ expect(onError.mock.calls[0][0]).toMatchObject({
+ code: ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
+ loc: {
+ start: {
+ offset: index,
+ line: 1,
+ column: index + 1,
+ },
+ end: {
+ offset: index + 3,
+ line: 1,
+ column: index + 4,
+ },
+ },
+ })
+ })
+
+ test('error on duplicated slot names', () => {
+ const onError = vi.fn()
+ const source = ``
+ compileWithSlots(source, { onError })
+ const index = source.lastIndexOf('#foo')
+ expect(onError.mock.calls[0][0]).toMatchObject({
+ code: ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES,
+ loc: {
+ start: {
+ offset: index,
+ line: 1,
+ column: index + 1,
+ },
+ end: {
+ offset: index + 4,
+ line: 1,
+ column: index + 5,
+ },
+ },
+ })
+ })
+ })
+})
diff --git a/packages/compiler-vapor/src/compile.ts b/packages/compiler-vapor/src/compile.ts
index 25790d65c..25a27f23d 100644
--- a/packages/compiler-vapor/src/compile.ts
+++ b/packages/compiler-vapor/src/compile.ts
@@ -28,6 +28,7 @@ import { transformVIf } from './transforms/vIf'
import { transformVFor } from './transforms/vFor'
import { transformComment } from './transforms/transformComment'
import { transformSlotOutlet } from './transforms/transformSlotOutlet'
+import { transformVSlot } from './transforms/vSlot'
import type { HackOptions } from './ir'
export { wrapTemplate } from './transforms/utils'
@@ -108,6 +109,7 @@ export function getBaseTransformPreset(
transformTemplateRef,
transformText,
transformElement,
+ transformVSlot,
transformComment,
transformChildren,
],
diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts
index 3256e514c..5b91a8446 100644
--- a/packages/compiler-vapor/src/generators/component.ts
+++ b/packages/compiler-vapor/src/generators/component.ts
@@ -1,6 +1,8 @@
import { camelize, extend, isArray } from '@vue/shared'
import type { CodegenContext } from '../generate'
import {
+ type ComponentDynamicSlot,
+ type ComponentSlots,
type CreateComponentIRNode,
IRDynamicPropsKind,
type IRProp,
@@ -10,6 +12,7 @@ import {
import {
type CodeFragment,
NEWLINE,
+ SEGMENTS_ARRAY,
SEGMENTS_ARRAY_NEWLINE,
SEGMENTS_OBJECT,
SEGMENTS_OBJECT_NEWLINE,
@@ -22,8 +25,8 @@ 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(
oper: CreateComponentIRNode,
context: CodegenContext,
@@ -31,7 +34,7 @@ export function genCreateComponent(
const { vaporHelper } = context
const tag = genTag()
- const isRoot = oper.root
+ const { root, slots, dynamicSlots } = oper
const rawProps = genRawProps(oper.props, context)
return [
@@ -40,8 +43,14 @@ export function genCreateComponent(
...genCall(
vaporHelper('createComponent'),
tag,
- rawProps || (isRoot ? 'null' : false),
- isRoot && 'true',
+ rawProps || (slots || dynamicSlots || root ? 'null' : false),
+ slots ? genSlots(slots, context) : dynamicSlots || root ? 'null' : false,
+ dynamicSlots
+ ? genDynamicSlots(dynamicSlots, context)
+ : root
+ ? 'null'
+ : false,
+ root && 'true',
),
...genDirectivesForElement(oper.id, context),
]
@@ -134,3 +143,28 @@ function genModelModifiers(
const modifiersVal = genDirectiveModifiers(modelModifiers)
return [',', NEWLINE, ...modifiersKey, `: () => ({ ${modifiersVal} })`]
}
+
+function genSlots(slots: ComponentSlots, context: CodegenContext) {
+ const slotList = Object.entries(slots)
+ return genMulti(
+ slotList.length > 1 ? SEGMENTS_OBJECT_NEWLINE : SEGMENTS_OBJECT,
+ ...slotList.map(([name, slot]) => [name, ': ', ...genBlock(slot, context)]),
+ )
+}
+
+function genDynamicSlots(
+ dynamicSlots: ComponentDynamicSlot[],
+ context: CodegenContext,
+) {
+ const slotsExpr = genMulti(
+ dynamicSlots.length > 1 ? SEGMENTS_ARRAY_NEWLINE : SEGMENTS_ARRAY,
+ ...dynamicSlots.map(({ name, fn }) =>
+ genMulti(
+ SEGMENTS_OBJECT_NEWLINE,
+ ['name: ', ...genExpression(name, context)],
+ ['fn: ', ...genBlock(fn, context)],
+ ),
+ ),
+ )
+ return ['() => ', ...slotsExpr]
+}
diff --git a/packages/compiler-vapor/src/index.ts b/packages/compiler-vapor/src/index.ts
index e222fadee..6ee58bc54 100644
--- a/packages/compiler-vapor/src/index.ts
+++ b/packages/compiler-vapor/src/index.ts
@@ -49,3 +49,4 @@ export { transformVFor } from './transforms/vFor'
export { transformVModel } from './transforms/vModel'
export { transformComment } from './transforms/transformComment'
export { transformSlotOutlet } from './transforms/transformSlotOutlet'
+export { transformVSlot } from './transforms/vSlot'
diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts
index bba2f3cfc..23e3114ec 100644
--- a/packages/compiler-vapor/src/ir.ts
+++ b/packages/compiler-vapor/src/ir.ts
@@ -199,12 +199,24 @@ export interface WithDirectiveIRNode extends BaseIRNode {
builtin?: VaporHelper
}
+export interface ComponentSlotBlockIRNode extends BlockIRNode {
+ // TODO slot props
+}
+export type ComponentSlots = Record
+export interface ComponentDynamicSlot {
+ name: SimpleExpressionNode
+ fn: ComponentSlotBlockIRNode
+ key?: string
+}
+
export interface CreateComponentIRNode extends BaseIRNode {
type: IRNodeTypes.CREATE_COMPONENT_NODE
id: number
tag: string
props: IRProps[]
- // TODO slots
+
+ slots?: ComponentSlots
+ dynamicSlots?: ComponentDynamicSlot[]
resolve: boolean
root: boolean
diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts
index 4399fbf90..893efce1f 100644
--- a/packages/compiler-vapor/src/transform.ts
+++ b/packages/compiler-vapor/src/transform.ts
@@ -16,6 +16,8 @@ import {
import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared'
import {
type BlockIRNode,
+ type ComponentDynamicSlot,
+ type ComponentSlots,
DynamicFlag,
type HackOptions,
type IRDynamicInfo,
@@ -77,11 +79,13 @@ export class TransformContext {
comment: CommentNode[] = []
component: Set = this.ir.component
+ slots?: ComponentSlots
+ dynamicSlots?: ComponentDynamicSlot[]
private globalId = 0
constructor(
- private ir: RootIRNode,
+ public ir: RootIRNode,
public node: T,
options: TransformOptions = {},
) {
@@ -90,11 +94,14 @@ export class TransformContext {
}
enterBlock(ir: BlockIRNode, isVFor: boolean = false): () => void {
- const { block, template, dynamic, childrenTemplate } = this
+ const { block, template, dynamic, childrenTemplate, slots, dynamicSlots } =
+ this
this.block = ir
this.dynamic = ir.dynamic
this.template = ''
this.childrenTemplate = []
+ this.slots = undefined
+ this.dynamicSlots = undefined
isVFor && this.inVFor++
return () => {
// exit
@@ -103,6 +110,8 @@ export class TransformContext {
this.template = template
this.dynamic = dynamic
this.childrenTemplate = childrenTemplate
+ this.slots = slots
+ this.dynamicSlots = dynamicSlots
isVFor && this.inVFor--
}
}
diff --git a/packages/compiler-vapor/src/transforms/transformChildren.ts b/packages/compiler-vapor/src/transforms/transformChildren.ts
index da007fc1b..68e63efea 100644
--- a/packages/compiler-vapor/src/transforms/transformChildren.ts
+++ b/packages/compiler-vapor/src/transforms/transformChildren.ts
@@ -15,7 +15,9 @@ import { DynamicFlag, type IRDynamicInfo, IRNodeTypes } from '../ir'
export const transformChildren: NodeTransform = (node, context) => {
const isFragment =
node.type === NodeTypes.ROOT ||
- (node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.TEMPLATE)
+ (node.type === NodeTypes.ELEMENT &&
+ (node.tagType === ElementTypes.TEMPLATE ||
+ node.tagType === ElementTypes.COMPONENT))
if (!isFragment && node.type !== NodeTypes.ELEMENT) return
diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts
index e45428af4..c16ddd2e7 100644
--- a/packages/compiler-vapor/src/transforms/transformElement.ts
+++ b/packages/compiler-vapor/src/transforms/transformElement.ts
@@ -104,7 +104,11 @@ function transformComponentElement(
props: propsResult[0] ? propsResult[1] : [propsResult[1]],
resolve,
root,
+ slots: context.slots,
+ dynamicSlots: context.dynamicSlots,
})
+ context.slots = undefined
+ context.dynamicSlots = undefined
}
function resolveSetupReference(name: string, context: TransformContext) {
diff --git a/packages/compiler-vapor/src/transforms/vSlot.ts b/packages/compiler-vapor/src/transforms/vSlot.ts
new file mode 100644
index 000000000..8d4554d9e
--- /dev/null
+++ b/packages/compiler-vapor/src/transforms/vSlot.ts
@@ -0,0 +1,120 @@
+import {
+ type ElementNode,
+ ElementTypes,
+ ErrorCodes,
+ NodeTypes,
+ type TemplateChildNode,
+ createCompilerError,
+ isTemplateNode,
+ isVSlot,
+} from '@vue/compiler-core'
+import type { NodeTransform, TransformContext } from '../transform'
+import { newBlock } from './utils'
+import { type BlockIRNode, DynamicFlag, type VaporDirectiveNode } from '../ir'
+import { findDir, resolveExpression } from '../utils'
+
+// TODO dynamic slots
+export const transformVSlot: NodeTransform = (node, context) => {
+ if (node.type !== NodeTypes.ELEMENT) return
+
+ let dir: VaporDirectiveNode | undefined
+ const { tagType, children } = node
+ const { parent } = context
+
+ const isDefaultSlot = tagType === ElementTypes.COMPONENT && children.length
+ const isSlotTemplate =
+ isTemplateNode(node) &&
+ parent &&
+ parent.node.type === NodeTypes.ELEMENT &&
+ parent.node.tagType === ElementTypes.COMPONENT
+
+ if (isDefaultSlot) {
+ const defaultChildren = children.filter(
+ n =>
+ isNonWhitespaceContent(node) &&
+ !(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot)),
+ )
+
+ const [block, onExit] = createSlotBlock(
+ node,
+ context as TransformContext,
+ )
+
+ const slots = (context.slots ||= {})
+ const dynamicSlots = (context.dynamicSlots ||= [])
+
+ return () => {
+ onExit()
+
+ if (defaultChildren.length) {
+ if (slots.default) {
+ context.options.onError(
+ createCompilerError(
+ ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
+ defaultChildren[0].loc,
+ ),
+ )
+ } else {
+ slots.default = block
+ }
+ context.slots = slots
+ } else if (Object.keys(slots).length) {
+ context.slots = slots
+ }
+
+ if (dynamicSlots.length) context.dynamicSlots = dynamicSlots
+ }
+ } else if (isSlotTemplate && (dir = findDir(node, 'slot', true))) {
+ let { arg } = dir
+
+ context.dynamic.flags |= DynamicFlag.NON_TEMPLATE
+
+ const slots = context.slots!
+ const dynamicSlots = context.dynamicSlots!
+
+ const [block, onExit] = createSlotBlock(
+ node,
+ context as TransformContext,
+ )
+
+ arg &&= resolveExpression(arg)
+
+ if (!arg || arg.isStatic) {
+ const slotName = arg ? arg.content : 'default'
+
+ if (slots[slotName]) {
+ context.options.onError(
+ createCompilerError(
+ ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES,
+ dir.loc,
+ ),
+ )
+ } else {
+ slots[slotName] = block
+ }
+ } else {
+ dynamicSlots.push({
+ name: arg,
+ fn: block,
+ })
+ }
+ return () => onExit()
+ }
+}
+
+function createSlotBlock(
+ slotNode: ElementNode,
+ context: TransformContext,
+): [BlockIRNode, () => void] {
+ const branch: BlockIRNode = newBlock(slotNode)
+ const exitBlock = context.enterBlock(branch)
+ return [branch, exitBlock]
+}
+
+function isNonWhitespaceContent(node: TemplateChildNode): boolean {
+ if (node.type !== NodeTypes.TEXT && node.type !== NodeTypes.TEXT_CALL)
+ return true
+ return node.type === NodeTypes.TEXT
+ ? !!node.content.trim()
+ : isNonWhitespaceContent(node.content)
+}
diff --git a/packages/compiler-vapor/src/utils.ts b/packages/compiler-vapor/src/utils.ts
index 627978538..fdbd101a0 100644
--- a/packages/compiler-vapor/src/utils.ts
+++ b/packages/compiler-vapor/src/utils.ts
@@ -5,6 +5,7 @@ import {
type ElementNode,
NodeTypes,
type SimpleExpressionNode,
+ findDir as _findDir,
findProp as _findProp,
createSimpleExpression,
isLiteralWhitelisted,
@@ -19,6 +20,13 @@ export const findProp = _findProp as (
allowEmpty?: boolean,
) => AttributeNode | VaporDirectiveNode | undefined
+/** find directive */
+export const findDir = _findDir as (
+ node: ElementNode,
+ name: string | RegExp,
+ allowEmpty?: boolean,
+) => VaporDirectiveNode | undefined
+
export function propToExpression(prop: AttributeNode | VaporDirectiveNode) {
return prop.type === NodeTypes.ATTRIBUTE
? prop.value