refactor: component slots (#238)

Co-authored-by: Doctor Wu <doctorwu@moego.pet>
Co-authored-by: Rizumu Ayaka <rizumu@ayaka.moe>
This commit is contained in:
Kevin Deng 三咲智子 2024-06-19 01:09:17 +08:00 committed by GitHub
parent bbde386a7c
commit 97f0b3bc33
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 670 additions and 585 deletions

View File

@ -33,7 +33,9 @@ export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp") const _component_Comp = _resolveComponent("Comp")
const _directive_hello = _resolveDirective("hello") const _directive_hello = _resolveDirective("hello")
const _directive_test = _resolveDirective("test") const _directive_test = _resolveDirective("test")
const n4 = _createComponent(_component_Comp, null, { default: () => { const n4 = _createComponent(_component_Comp, null, [
{
default: () => {
const n0 = _createIf(() => (true), () => { const n0 = _createIf(() => (true), () => {
const n3 = t0() const n3 = t0()
const n2 = _createComponent(_component_Bar) const n2 = _createComponent(_component_Bar)
@ -42,7 +44,9 @@ export function render(_ctx) {
return n3 return n3
}) })
return n0 return n0
} }, null, true) }
}
], true)
_withDirectives(n4, [[_directive_test]]) _withDirectives(n4, [[_directive_test]])
return n4 return n4
}" }"

View File

@ -5,7 +5,7 @@ exports[`compiler: element transform > component > do not resolve component from
export function render(_ctx) { export function render(_ctx) {
const _component_Example = _resolveComponent("Example") const _component_Example = _resolveComponent("Example")
const n0 = _createComponent(_component_Example, null, null, null, true) const n0 = _createComponent(_component_Example, null, null, true)
return n0 return n0
}" }"
`; `;
@ -25,7 +25,7 @@ exports[`compiler: element transform > component > generate single root componen
"import { createComponent as _createComponent } from 'vue/vapor'; "import { createComponent as _createComponent } from 'vue/vapor';
export function render(_ctx) { export function render(_ctx) {
const n0 = _createComponent(_ctx.Comp, null, null, null, true) const n0 = _createComponent(_ctx.Comp, null, null, true)
return n0 return n0
}" }"
`; `;
@ -35,21 +35,21 @@ exports[`compiler: element transform > component > import + resolve component 1`
export function render(_ctx) { export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo") const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, null, null, null, true) const n0 = _createComponent(_component_Foo, null, null, true)
return n0 return n0
}" }"
`; `;
exports[`compiler: element transform > component > resolve component from setup bindings (inline const) 1`] = ` exports[`compiler: element transform > component > resolve component from setup bindings (inline const) 1`] = `
"(() => { "(() => {
const n0 = _createComponent(Example, null, null, null, true) const n0 = _createComponent(Example, null, null, true)
return n0 return n0
})()" })()"
`; `;
exports[`compiler: element transform > component > resolve component from setup bindings (inline) 1`] = ` exports[`compiler: element transform > component > resolve component from setup bindings (inline) 1`] = `
"(() => { "(() => {
const n0 = _createComponent(_unref(Example), null, null, null, true) const n0 = _createComponent(_unref(Example), null, null, true)
return n0 return n0
})()" })()"
`; `;
@ -58,14 +58,14 @@ exports[`compiler: element transform > component > resolve component from setup
"import { createComponent as _createComponent } from 'vue/vapor'; "import { createComponent as _createComponent } from 'vue/vapor';
export function render(_ctx) { export function render(_ctx) {
const n0 = _createComponent(_ctx.Example, null, null, null, true) const n0 = _createComponent(_ctx.Example, null, null, true)
return n0 return n0
}" }"
`; `;
exports[`compiler: element transform > component > resolve namespaced component from props bindings (inline) 1`] = ` exports[`compiler: element transform > component > resolve namespaced component from props bindings (inline) 1`] = `
"(() => { "(() => {
const n0 = _createComponent(Foo.Example, null, null, null, true) const n0 = _createComponent(Foo.Example, null, null, true)
return n0 return n0
})()" })()"
`; `;
@ -74,14 +74,14 @@ exports[`compiler: element transform > component > resolve namespaced component
"import { createComponent as _createComponent } from 'vue/vapor'; "import { createComponent as _createComponent } from 'vue/vapor';
export function render(_ctx) { export function render(_ctx) {
const n0 = _createComponent(_ctx.Foo.Example, null, null, null, true) const n0 = _createComponent(_ctx.Foo.Example, null, null, true)
return n0 return n0
}" }"
`; `;
exports[`compiler: element transform > component > resolve namespaced component from setup bindings (inline const) 1`] = ` exports[`compiler: element transform > component > resolve namespaced component from setup bindings (inline const) 1`] = `
"(() => { "(() => {
const n0 = _createComponent(Foo.Example, null, null, null, true) const n0 = _createComponent(Foo.Example, null, null, true)
return n0 return n0
})()" })()"
`; `;
@ -90,7 +90,7 @@ exports[`compiler: element transform > component > resolve namespaced component
"import { createComponent as _createComponent } from 'vue/vapor'; "import { createComponent as _createComponent } from 'vue/vapor';
export function render(_ctx) { export function render(_ctx) {
const n0 = _createComponent(_ctx.Foo.Example, null, null, null, true) const n0 = _createComponent(_ctx.Foo.Example, null, null, true)
return n0 return n0
}" }"
`; `;
@ -102,7 +102,7 @@ export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo") const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, [ const n0 = _createComponent(_component_Foo, [
{ onBar: () => $event => (_ctx.handleBar($event)) } { onBar: () => $event => (_ctx.handleBar($event)) }
], null, null, true) ], null, true)
return n0 return n0
}" }"
`; `;
@ -117,7 +117,7 @@ export function render(_ctx) {
id: () => ("foo"), id: () => ("foo"),
class: () => ("bar") class: () => ("bar")
} }
], null, null, true) ], null, true)
return n0 return n0
}" }"
`; `;
@ -129,7 +129,7 @@ export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo") const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, [ const n0 = _createComponent(_component_Foo, [
() => (_ctx.obj) () => (_ctx.obj)
], null, null, true) ], null, true)
return n0 return n0
}" }"
`; `;
@ -142,7 +142,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Foo, [ const n0 = _createComponent(_component_Foo, [
{ id: () => ("foo") }, { id: () => ("foo") },
() => (_ctx.obj) () => (_ctx.obj)
], null, null, true) ], null, true)
return n0 return n0
}" }"
`; `;
@ -155,7 +155,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Foo, [ const n0 = _createComponent(_component_Foo, [
() => (_ctx.obj), () => (_ctx.obj),
{ id: () => ("foo") } { id: () => ("foo") }
], null, null, true) ], null, true)
return n0 return n0
}" }"
`; `;
@ -169,7 +169,7 @@ export function render(_ctx) {
{ id: () => ("foo") }, { id: () => ("foo") },
() => (_ctx.obj), () => (_ctx.obj),
{ class: () => ("bar") } { class: () => ("bar") }
], null, null, true) ], null, true)
return n0 return n0
}" }"
`; `;
@ -181,7 +181,7 @@ export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo") const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, [ const n0 = _createComponent(_component_Foo, [
() => (_toHandlers(_ctx.obj)) () => (_toHandlers(_ctx.obj))
], null, null, true) ], null, true)
return n0 return n0
}" }"
`; `;
@ -195,7 +195,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Foo, [ const n0 = _createComponent(_component_Foo, [
() => ({ [_toHandlerKey(_ctx.foo-_ctx.bar)]: () => _ctx.bar }), () => ({ [_toHandlerKey(_ctx.foo-_ctx.bar)]: () => _ctx.bar }),
() => ({ [_toHandlerKey(_ctx.baz)]: () => _ctx.qux }) () => ({ [_toHandlerKey(_ctx.baz)]: () => _ctx.qux })
], null, null, true) ], null, true)
return n0 return n0
}" }"
`; `;
@ -208,7 +208,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Foo, [ const n0 = _createComponent(_component_Foo, [
() => ({ [_ctx.foo-_ctx.bar]: _ctx.bar }), () => ({ [_ctx.foo-_ctx.bar]: _ctx.bar }),
() => ({ [_ctx.baz]: _ctx.qux }) () => ({ [_ctx.baz]: _ctx.qux })
], null, null, true) ], null, true)
return n0 return n0
}" }"
`; `;

View File

@ -9,7 +9,7 @@ export function render(_ctx) {
{ modelValue: () => (_ctx.foo), { modelValue: () => (_ctx.foo),
"onUpdate:modelValue": () => $event => (_ctx.foo = $event), "onUpdate:modelValue": () => $event => (_ctx.foo = $event),
modelModifiers: () => ({ trim: true, "bar-baz": true }) } modelModifiers: () => ({ trim: true, "bar-baz": true }) }
], null, null, true) ], null, true)
return n0 return n0
}" }"
`; `;
@ -22,7 +22,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Comp, [ const n0 = _createComponent(_component_Comp, [
{ modelValue: () => (_ctx.foo), { modelValue: () => (_ctx.foo),
"onUpdate:modelValue": () => $event => (_ctx.foo = $event) } "onUpdate:modelValue": () => $event => (_ctx.foo = $event) }
], null, null, true) ], null, true)
return n0 return n0
}" }"
`; `;
@ -41,7 +41,7 @@ export function render(_ctx) {
"onUpdate:bar": () => $event => (_ctx.bar = $event), "onUpdate:bar": () => $event => (_ctx.bar = $event),
barModifiers: () => ({ number: true }) barModifiers: () => ({ number: true })
} }
], null, null, true) ], null, true)
return n0 return n0
}" }"
`; `;
@ -54,7 +54,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Comp, [ const n0 = _createComponent(_component_Comp, [
{ bar: () => (_ctx.foo), { bar: () => (_ctx.foo),
"onUpdate:bar": () => $event => (_ctx.foo = $event) } "onUpdate:bar": () => $event => (_ctx.foo = $event) }
], null, null, true) ], null, true)
return n0 return n0
}" }"
`; `;
@ -71,7 +71,7 @@ export function render(_ctx) {
() => ({ [_ctx.bar]: _ctx.bar, () => ({ [_ctx.bar]: _ctx.bar,
["onUpdate:" + _ctx.bar]: () => $event => (_ctx.bar = $event), ["onUpdate:" + _ctx.bar]: () => $event => (_ctx.bar = $event),
[_ctx.bar + "Modifiers"]: () => ({ number: true }) }) [_ctx.bar + "Modifiers"]: () => ({ number: true }) })
], null, null, true) ], null, true)
return n0 return n0
}" }"
`; `;
@ -84,7 +84,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Comp, [ const n0 = _createComponent(_component_Comp, [
() => ({ [_ctx.arg]: _ctx.foo, () => ({ [_ctx.arg]: _ctx.foo,
["onUpdate:" + _ctx.arg]: () => $event => (_ctx.foo = $event) }) ["onUpdate:" + _ctx.arg]: () => $event => (_ctx.foo = $event) })
], null, null, true) ], null, true)
return n0 return n0
}" }"
`; `;

View File

@ -44,7 +44,7 @@ export function render(_ctx) {
const n1 = t0() const n1 = t0()
const n0 = _createComponent(_component_Comp, [ const n0 = _createComponent(_component_Comp, [
{ id: () => (_ctx.foo) } { id: () => (_ctx.foo) }
], null, null, null, true) ], null, null, true)
_insert(n0, n1) _insert(n0, n1)
return n1 return n1
}" }"

View File

@ -6,60 +6,67 @@ const t0 = _template("foo")
export function render(_ctx) { export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp") const _component_Comp = _resolveComponent("Comp")
const n2 = _createComponent(_component_Comp, null, null, [() => ({ const n2 = _createComponent(_component_Comp, null, [
() => ({
name: _ctx.name, name: _ctx.name,
fn: () => { fn: () => {
const n0 = t0() const n0 = t0()
return n0 return n0
} }
})], true) })
], true)
return n2 return n2
}" }"
`; `;
exports[`compiler: transform slot > dynamic slots name w/ v-for 1`] = ` exports[`compiler: transform slot > dynamic slots name w/ v-for 1`] = `
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, withDestructure as _withDestructure, createForSlots as _createForSlots, template as _template } from 'vue/vapor'; "import { resolveComponent as _resolveComponent, withDestructure as _withDestructure, createForSlots as _createForSlots, createComponent as _createComponent, template as _template } from 'vue/vapor';
const t0 = _template("foo") const t0 = _template("foo")
export function render(_ctx) { export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp") const _component_Comp = _resolveComponent("Comp")
const n2 = _createComponent(_component_Comp, null, null, [() => (_createForSlots(_ctx.list, (item) => ({ const n2 = _createComponent(_component_Comp, null, [
() => (_createForSlots(_ctx.list, (item) => ({
name: item, name: item,
fn: _withDestructure(({ bar }) => [bar], (_ctx0) => { fn: _withDestructure(({ bar }) => [bar], (_ctx0) => {
const n0 = t0() const n0 = t0()
return n0 return n0
}) })
})))], true) })))
], true)
return n2 return n2
}" }"
`; `;
exports[`compiler: transform slot > dynamic slots name w/ v-for and provide absent key 1`] = ` exports[`compiler: transform slot > dynamic slots name w/ v-for and provide absent key 1`] = `
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, createForSlots as _createForSlots, template as _template } from 'vue/vapor'; "import { resolveComponent as _resolveComponent, createForSlots as _createForSlots, createComponent as _createComponent, template as _template } from 'vue/vapor';
const t0 = _template("foo") const t0 = _template("foo")
export function render(_ctx) { export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp") const _component_Comp = _resolveComponent("Comp")
const n2 = _createComponent(_component_Comp, null, null, [() => (_createForSlots(_ctx.list, (_, __, index) => ({ const n2 = _createComponent(_component_Comp, null, [
() => (_createForSlots(_ctx.list, (_, __, index) => ({
name: index, name: index,
fn: () => { fn: () => {
const n0 = t0() const n0 = t0()
return n0 return n0
} }
})))], true) })))
], true)
return n2 return n2
}" }"
`; `;
exports[`compiler: transform slot > dynamic slots name w/ v-if / v-else[-if] 1`] = ` exports[`compiler: transform slot > dynamic slots name w/ v-if / v-else[-if] 1`] = `
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, withDestructure as _withDestructure, template as _template } from 'vue/vapor'; "import { resolveComponent as _resolveComponent, withDestructure as _withDestructure, createComponent as _createComponent, template as _template } from 'vue/vapor';
const t0 = _template("condition slot") const t0 = _template("condition slot")
const t1 = _template("another condition") const t1 = _template("another condition")
const t2 = _template("else condition") const t2 = _template("else condition")
export function render(_ctx) { export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp") const _component_Comp = _resolveComponent("Comp")
const n6 = _createComponent(_component_Comp, null, null, [() => (_ctx.condition const n6 = _createComponent(_component_Comp, null, [
() => (_ctx.condition
? { ? {
name: "condition", name: "condition",
fn: () => { fn: () => {
@ -81,7 +88,8 @@ export function render(_ctx) {
const n4 = t2() const n4 = t2()
return n4 return n4
} }
})], true) })
], true)
return n6 return n6
}" }"
`; `;
@ -92,10 +100,14 @@ const t0 = _template("<div></div>")
export function render(_ctx) { export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp") const _component_Comp = _resolveComponent("Comp")
const n1 = _createComponent(_component_Comp, null, { default: () => { const n1 = _createComponent(_component_Comp, null, [
{
default: () => {
const n0 = t0() const n0 = t0()
return n0 return n0
} }, null, true) }
}
], true)
return n1 return n1
}" }"
`; `;
@ -108,7 +120,8 @@ const t2 = _template("<span></span>")
export function render(_ctx) { export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp") const _component_Comp = _resolveComponent("Comp")
const n4 = _createComponent(_component_Comp, null, { const n4 = _createComponent(_component_Comp, null, [
{
one: () => { one: () => {
const n0 = t0() const n0 = t0()
return n0 return n0
@ -118,69 +131,88 @@ export function render(_ctx) {
const n3 = t2() const n3 = t2()
return [n2, n3] return [n2, n3]
} }
}, null, true) }
], true)
return n4 return n4
}" }"
`; `;
exports[`compiler: transform slot > nested slots scoping 1`] = ` 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'; "import { resolveComponent as _resolveComponent, createTextNode as _createTextNode, withDestructure as _withDestructure, createComponent as _createComponent, template as _template } from 'vue/vapor';
const t0 = _template(" ") const t0 = _template(" ")
export function render(_ctx) { export function render(_ctx) {
const _component_Inner = _resolveComponent("Inner") const _component_Inner = _resolveComponent("Inner")
const _component_Comp = _resolveComponent("Comp") const _component_Comp = _resolveComponent("Comp")
const n5 = _createComponent(_component_Comp, null, { default: _withDestructure(({ foo }) => [foo], (_ctx0) => { const n5 = _createComponent(_component_Comp, null, [
{
default: _withDestructure(({ foo }) => [foo], (_ctx0) => {
const n2 = t0() const n2 = t0()
const n1 = _createComponent(_component_Inner, null, { default: _withDestructure(({ bar }) => [bar], (_ctx1) => { const n1 = _createComponent(_component_Inner, null, [
{
default: _withDestructure(({ bar }) => [bar], (_ctx1) => {
const n0 = _createTextNode(() => [_ctx0[0] + _ctx1[0] + _ctx.baz]) const n0 = _createTextNode(() => [_ctx0[0] + _ctx1[0] + _ctx.baz])
return n0 return n0
}) }) })
}
])
const n3 = _createTextNode(() => [_ctx0[0] + _ctx.bar + _ctx.baz]) const n3 = _createTextNode(() => [_ctx0[0] + _ctx.bar + _ctx.baz])
return [n1, n2, n3] return [n1, n2, n3]
}) }, null, true) })
}
], true)
return n5 return n5
}" }"
`; `;
exports[`compiler: transform slot > on component dynamically named slot 1`] = ` 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'; "import { resolveComponent as _resolveComponent, createTextNode as _createTextNode, withDestructure as _withDestructure, createComponent as _createComponent } from 'vue/vapor';
export function render(_ctx) { export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp") const _component_Comp = _resolveComponent("Comp")
const n1 = _createComponent(_component_Comp, null, { }, [() => ({ const n1 = _createComponent(_component_Comp, null, [
() => ({
name: _ctx.named, name: _ctx.named,
fn: _withDestructure(({ foo }) => [foo], (_ctx0) => { fn: _withDestructure(({ foo }) => [foo], (_ctx0) => {
const n0 = _createTextNode(() => [_ctx0[0] + _ctx.bar]) const n0 = _createTextNode(() => [_ctx0[0] + _ctx.bar])
return n0 return n0
}) })
})], true) })
], true)
return n1 return n1
}" }"
`; `;
exports[`compiler: transform slot > on component named slot 1`] = ` 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'; "import { resolveComponent as _resolveComponent, createTextNode as _createTextNode, withDestructure as _withDestructure, createComponent as _createComponent } from 'vue/vapor';
export function render(_ctx) { export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp") const _component_Comp = _resolveComponent("Comp")
const n1 = _createComponent(_component_Comp, null, { named: _withDestructure(({ foo }) => [foo], (_ctx0) => { const n1 = _createComponent(_component_Comp, null, [
{
named: _withDestructure(({ foo }) => [foo], (_ctx0) => {
const n0 = _createTextNode(() => [_ctx0[0] + _ctx.bar]) const n0 = _createTextNode(() => [_ctx0[0] + _ctx.bar])
return n0 return n0
}) }, null, true) })
}
], true)
return n1 return n1
}" }"
`; `;
exports[`compiler: transform slot > on-component default slot 1`] = ` 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'; "import { resolveComponent as _resolveComponent, createTextNode as _createTextNode, withDestructure as _withDestructure, createComponent as _createComponent } from 'vue/vapor';
export function render(_ctx) { export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp") const _component_Comp = _resolveComponent("Comp")
const n1 = _createComponent(_component_Comp, null, { default: _withDestructure(({ foo }) => [foo], (_ctx0) => { const n1 = _createComponent(_component_Comp, null, [
{
default: _withDestructure(({ foo }) => [foo], (_ctx0) => {
const n0 = _createTextNode(() => [_ctx0[0] + _ctx.bar]) const n0 = _createTextNode(() => [_ctx0[0] + _ctx.bar])
return n0 return n0
}) }, null, true) })
}
], true)
return n1 return n1
}" }"
`; `;

View File

@ -182,9 +182,7 @@ describe('compiler: element transform', () => {
bindingMetadata: { Comp: BindingTypes.SETUP_CONST }, bindingMetadata: { Comp: BindingTypes.SETUP_CONST },
}) })
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
expect(code).contains( expect(code).contains('_createComponent(_ctx.Comp, null, null, true)')
'_createComponent(_ctx.Comp, null, null, null, true)',
)
}) })
test('generate multi root component', () => { test('generate multi root component', () => {

View File

@ -1,7 +1,7 @@
import { ErrorCodes, NodeTypes } from '@vue/compiler-core' import { ErrorCodes, NodeTypes } from '@vue/compiler-core'
import { import {
DynamicSlotType,
IRNodeTypes, IRNodeTypes,
IRSlotType,
transformChildren, transformChildren,
transformElement, transformElement,
transformSlotOutlet, transformSlotOutlet,
@ -42,6 +42,9 @@ describe('compiler: transform slot', () => {
id: 1, id: 1,
tag: 'Comp', tag: 'Comp',
props: [[]], props: [[]],
slots: [
{
slotType: IRSlotType.STATIC,
slots: { slots: {
default: { default: {
type: IRNodeTypes.BLOCK, type: IRNodeTypes.BLOCK,
@ -51,6 +54,8 @@ describe('compiler: transform slot', () => {
}, },
}, },
}, },
],
},
]) ])
expect(ir.block.returns).toEqual([1]) expect(ir.block.returns).toEqual([1])
expect(ir.block.dynamic).toMatchObject({ expect(ir.block.dynamic).toMatchObject({
@ -73,6 +78,9 @@ describe('compiler: transform slot', () => {
type: IRNodeTypes.CREATE_COMPONENT_NODE, type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp', tag: 'Comp',
props: [[]], props: [[]],
slots: [
{
slotType: IRSlotType.STATIC,
slots: { slots: {
default: { default: {
type: IRNodeTypes.BLOCK, type: IRNodeTypes.BLOCK,
@ -87,6 +95,8 @@ describe('compiler: transform slot', () => {
}, },
}, },
}, },
],
},
]) ])
}) })
@ -103,6 +113,9 @@ describe('compiler: transform slot', () => {
{ {
type: IRNodeTypes.CREATE_COMPONENT_NODE, type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp', tag: 'Comp',
slots: [
{
slotType: IRSlotType.STATIC,
slots: { slots: {
named: { named: {
type: IRNodeTypes.BLOCK, type: IRNodeTypes.BLOCK,
@ -113,6 +126,8 @@ describe('compiler: transform slot', () => {
}, },
}, },
}, },
],
},
]) ])
}) })
@ -130,7 +145,7 @@ describe('compiler: transform slot', () => {
{ {
type: IRNodeTypes.CREATE_COMPONENT_NODE, type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp', tag: 'Comp',
dynamicSlots: [ slots: [
{ {
name: { name: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
@ -165,6 +180,9 @@ describe('compiler: transform slot', () => {
id: 4, id: 4,
tag: 'Comp', tag: 'Comp',
props: [[]], props: [[]],
slots: [
{
slotType: IRSlotType.STATIC,
slots: { slots: {
one: { one: {
type: IRNodeTypes.BLOCK, type: IRNodeTypes.BLOCK,
@ -180,6 +198,8 @@ describe('compiler: transform slot', () => {
}, },
}, },
}, },
],
},
]) ])
}) })
@ -207,6 +227,9 @@ describe('compiler: transform slot', () => {
type: IRNodeTypes.CREATE_COMPONENT_NODE, type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp', tag: 'Comp',
props: [[]], props: [[]],
slots: [
{
slotType: IRSlotType.STATIC,
slots: { slots: {
default: { default: {
type: IRNodeTypes.BLOCK, type: IRNodeTypes.BLOCK,
@ -217,12 +240,17 @@ describe('compiler: transform slot', () => {
}, },
}, },
}, },
],
},
]) ])
expect( expect(
(ir.block.operation[0] as any).slots.default.operation[0], (ir.block.operation[0] as any).slots[0].slots.default.operation[0],
).toMatchObject({ ).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE, type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Inner', tag: 'Inner',
slots: [
{
slotType: IRSlotType.STATIC,
slots: { slots: {
default: { default: {
type: IRNodeTypes.BLOCK, type: IRNodeTypes.BLOCK,
@ -232,6 +260,8 @@ describe('compiler: transform slot', () => {
}, },
}, },
}, },
},
],
}) })
}) })
@ -247,8 +277,7 @@ describe('compiler: transform slot', () => {
{ {
type: IRNodeTypes.CREATE_COMPONENT_NODE, type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp', tag: 'Comp',
slots: undefined, slots: [
dynamicSlots: [
{ {
name: { name: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
@ -278,8 +307,7 @@ describe('compiler: transform slot', () => {
{ {
type: IRNodeTypes.CREATE_COMPONENT_NODE, type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp', tag: 'Comp',
slots: undefined, slots: [
dynamicSlots: [
{ {
name: { name: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
@ -310,8 +338,7 @@ describe('compiler: transform slot', () => {
{ {
type: IRNodeTypes.CREATE_COMPONENT_NODE, type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp', tag: 'Comp',
slots: undefined, slots: [
dynamicSlots: [
{ {
name: { name: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
@ -350,21 +377,20 @@ describe('compiler: transform slot', () => {
{ {
type: IRNodeTypes.CREATE_COMPONENT_NODE, type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp', tag: 'Comp',
slots: undefined, slots: [
dynamicSlots: [
{ {
slotType: DynamicSlotType.CONDITIONAL, slotType: IRSlotType.CONDITIONAL,
condition: { content: 'condition' }, condition: { content: 'condition' },
positive: { positive: {
slotType: DynamicSlotType.BASIC, slotType: IRSlotType.DYNAMIC,
}, },
negative: { negative: {
slotType: DynamicSlotType.CONDITIONAL, slotType: IRSlotType.CONDITIONAL,
condition: { content: 'anotherCondition' }, condition: { content: 'anotherCondition' },
positive: { positive: {
slotType: DynamicSlotType.BASIC, slotType: IRSlotType.DYNAMIC,
}, },
negative: { slotType: DynamicSlotType.BASIC }, negative: { slotType: IRSlotType.DYNAMIC },
}, },
}, },
], ],

View File

@ -1,18 +1,19 @@
import { camelize, extend, isArray } from '@vue/shared' import { camelize, extend, isArray } from '@vue/shared'
import type { CodegenContext } from '../generate' import type { CodegenContext } from '../generate'
import { import {
type ComponentBasicDynamicSlot,
type ComponentConditionalDynamicSlot,
type ComponentDynamicSlot,
type ComponentLoopDynamicSlot,
type ComponentSlotBlockIRNode,
type ComponentSlots,
type CreateComponentIRNode, type CreateComponentIRNode,
DynamicSlotType,
IRDynamicPropsKind, IRDynamicPropsKind,
type IRProp, type IRProp,
type IRProps, type IRProps,
type IRPropsStatic, type IRPropsStatic,
type IRSlotDynamic,
type IRSlotDynamicBasic,
type IRSlotDynamicConditional,
type IRSlotDynamicLoop,
IRSlotType,
type IRSlots,
type IRSlotsStatic,
type SlotBlockIRNode,
} from '../ir' } from '../ir'
import { import {
type CodeFragment, type CodeFragment,
@ -45,8 +46,9 @@ export function genCreateComponent(
const { vaporHelper } = context const { vaporHelper } = context
const tag = genTag() const tag = genTag()
const { root, slots, dynamicSlots, once } = oper const { root, props, slots, once } = oper
const rawProps = genRawProps(oper.props, context) const rawProps = genRawProps(props, context)
const rawSlots = genRawSlots(slots, context)
return [ return [
NEWLINE, NEWLINE,
@ -55,8 +57,7 @@ export function genCreateComponent(
vaporHelper('createComponent'), vaporHelper('createComponent'),
tag, tag,
rawProps, rawProps,
slots && genSlots(slots, context), rawSlots,
dynamicSlots && genDynamicSlots(dynamicSlots, context),
root ? 'true' : false, root ? 'true' : false,
once && 'true', once && 'true',
), ),
@ -152,51 +153,51 @@ function genModelModifiers(
return [',', NEWLINE, ...modifiersKey, `: () => ({ ${modifiersVal} })`] return [',', NEWLINE, ...modifiersKey, `: () => ({ ${modifiersVal} })`]
} }
function genSlots(slots: ComponentSlots, context: CodegenContext) { function genRawSlots(slots: IRSlots[], context: CodegenContext) {
if (!slots.length) return
return genMulti(
DELIMITERS_ARRAY_NEWLINE,
...slots.map(slot =>
slot.slotType === IRSlotType.STATIC
? genStaticSlots(slot, context)
: genDynamicSlot(slot, context, true),
),
)
}
function genStaticSlots({ slots }: IRSlotsStatic, context: CodegenContext) {
const names = Object.keys(slots) const names = Object.keys(slots)
return genMulti( return genMulti(
names.length > 1 ? DELIMITERS_OBJECT_NEWLINE : DELIMITERS_OBJECT, DELIMITERS_OBJECT_NEWLINE,
...names.map(name => [ ...names.map(name => [
name, `${name}: `,
': ',
...genSlotBlockWithProps(slots[name], context), ...genSlotBlockWithProps(slots[name], context),
]), ]),
) )
} }
function genDynamicSlots(
dynamicSlots: ComponentDynamicSlot[],
context: CodegenContext,
) {
return genMulti(
dynamicSlots.length > 1 ? DELIMITERS_ARRAY_NEWLINE : DELIMITERS_ARRAY,
...dynamicSlots.map(slot => genDynamicSlot(slot, context, true)),
)
}
function genDynamicSlot( function genDynamicSlot(
slot: ComponentDynamicSlot, slot: IRSlotDynamic,
context: CodegenContext, context: CodegenContext,
top = false, withFunction = false,
): CodeFragment[] { ): CodeFragment[] {
let frag: CodeFragment[]
switch (slot.slotType) { switch (slot.slotType) {
case DynamicSlotType.BASIC: case IRSlotType.DYNAMIC:
return top frag = genBasicDynamicSlot(slot, context)
? ['() => (', ...genBasicDynamicSlot(slot, context), ')'] break
: genBasicDynamicSlot(slot, context) case IRSlotType.LOOP:
case DynamicSlotType.LOOP: frag = genLoopSlot(slot, context)
return top break
? ['() => (', ...genLoopSlot(slot, context), ')'] case IRSlotType.CONDITIONAL:
: genLoopSlot(slot, context) frag = genConditionalSlot(slot, context)
case DynamicSlotType.CONDITIONAL: break
return top
? ['() => (', ...genConditionalSlot(slot, context), ')']
: genConditionalSlot(slot, context)
} }
return withFunction ? ['() => (', ...frag, ')'] : frag
} }
function genBasicDynamicSlot( function genBasicDynamicSlot(
slot: ComponentBasicDynamicSlot, slot: IRSlotDynamicBasic,
context: CodegenContext, context: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {
const { name, fn } = slot const { name, fn } = slot
@ -208,7 +209,7 @@ function genBasicDynamicSlot(
} }
function genLoopSlot( function genLoopSlot(
slot: ComponentLoopDynamicSlot, slot: IRSlotDynamicLoop,
context: CodegenContext, context: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {
const { name, fn, loop } = slot const { name, fn, loop } = slot
@ -249,7 +250,7 @@ function genLoopSlot(
} }
function genConditionalSlot( function genConditionalSlot(
slot: ComponentConditionalDynamicSlot, slot: IRSlotDynamicConditional,
context: CodegenContext, context: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {
const { condition, positive, negative } = slot const { condition, positive, negative } = slot
@ -266,10 +267,7 @@ function genConditionalSlot(
] ]
} }
function genSlotBlockWithProps( function genSlotBlockWithProps(oper: SlotBlockIRNode, context: CodegenContext) {
oper: ComponentSlotBlockIRNode,
context: CodegenContext,
) {
let isDestructureAssignment = false let isDestructureAssignment = false
let rawProps: string | undefined let rawProps: string | undefined
let propsName: string | undefined let propsName: string | undefined

View File

@ -0,0 +1,66 @@
import type { SimpleExpressionNode } from '@vue/compiler-dom'
import type { DirectiveTransformResult } from '../transform'
import type { BlockIRNode, IRFor } from './index'
// props
export interface IRProp extends Omit<DirectiveTransformResult, 'value'> {
values: SimpleExpressionNode[]
}
export enum IRDynamicPropsKind {
EXPRESSION, // v-bind="value"
ATTRIBUTE, // v-bind:[foo]="value"
}
export type IRPropsStatic = IRProp[]
export interface IRPropsDynamicExpression {
kind: IRDynamicPropsKind.EXPRESSION
value: SimpleExpressionNode
handler?: boolean
}
export interface IRPropsDynamicAttribute extends IRProp {
kind: IRDynamicPropsKind.ATTRIBUTE
}
export type IRProps =
| IRPropsStatic
| IRPropsDynamicAttribute
| IRPropsDynamicExpression
// slots
export interface SlotBlockIRNode extends BlockIRNode {
props?: SimpleExpressionNode
}
export enum IRSlotType {
STATIC,
DYNAMIC,
LOOP,
CONDITIONAL,
}
export type IRSlotsStatic = {
slotType: IRSlotType.STATIC
slots: Record<string, SlotBlockIRNode>
}
export interface IRSlotDynamicBasic {
slotType: IRSlotType.DYNAMIC
name: SimpleExpressionNode
fn: SlotBlockIRNode
}
export interface IRSlotDynamicLoop {
slotType: IRSlotType.LOOP
name: SimpleExpressionNode
fn: SlotBlockIRNode
loop: IRFor
}
export interface IRSlotDynamicConditional {
slotType: IRSlotType.CONDITIONAL
condition: SimpleExpressionNode
positive: IRSlotDynamicBasic
negative?: IRSlotDynamicBasic | IRSlotDynamicConditional
}
export type IRSlotDynamic =
| IRSlotDynamicBasic
| IRSlotDynamicLoop
| IRSlotDynamicConditional
export type IRSlots = IRSlotsStatic | IRSlotDynamic

View File

@ -7,11 +7,10 @@ import type {
TemplateChildNode, TemplateChildNode,
} from '@vue/compiler-dom' } from '@vue/compiler-dom'
import type { Prettify } from '@vue/shared' import type { Prettify } from '@vue/shared'
import type { import type { DirectiveTransform, NodeTransform } from '../transform'
DirectiveTransform, import type { IRProp, IRProps, IRSlots } from './component'
DirectiveTransformResult,
NodeTransform, export * from './component'
} from './transform'
export enum IRNodeTypes { export enum IRNodeTypes {
ROOT, ROOT,
@ -88,29 +87,6 @@ export interface ForIRNode extends BaseIRNode, IRFor {
once: boolean once: boolean
} }
export interface IRProp extends Omit<DirectiveTransformResult, 'value'> {
values: SimpleExpressionNode[]
}
export enum IRDynamicPropsKind {
EXPRESSION, // v-bind="value"
ATTRIBUTE, // v-bind:[foo]="value"
}
export type IRPropsStatic = IRProp[]
export interface IRPropsDynamicExpression {
kind: IRDynamicPropsKind.EXPRESSION
value: SimpleExpressionNode
handler?: boolean
}
export interface IRPropsDynamicAttribute extends IRProp {
kind: IRDynamicPropsKind.ATTRIBUTE
}
export type IRProps =
| IRPropsStatic
| IRPropsDynamicAttribute
| IRPropsDynamicExpression
export interface SetPropIRNode extends BaseIRNode { export interface SetPropIRNode extends BaseIRNode {
type: IRNodeTypes.SET_PROP type: IRNodeTypes.SET_PROP
element: number element: number
@ -207,51 +183,12 @@ export interface WithDirectiveIRNode extends BaseIRNode {
asset?: boolean asset?: boolean
} }
export interface ComponentSlotBlockIRNode extends BlockIRNode {
props?: SimpleExpressionNode
}
export type ComponentSlots = Record<string, ComponentSlotBlockIRNode>
export enum DynamicSlotType {
BASIC,
LOOP,
CONDITIONAL,
}
export interface ComponentBasicDynamicSlot {
slotType: DynamicSlotType.BASIC
name: SimpleExpressionNode
fn: ComponentSlotBlockIRNode
}
export interface ComponentLoopDynamicSlot {
slotType: DynamicSlotType.LOOP
name: SimpleExpressionNode
fn: ComponentSlotBlockIRNode
loop: IRFor
}
export interface ComponentConditionalDynamicSlot {
slotType: DynamicSlotType.CONDITIONAL
condition: SimpleExpressionNode
positive: ComponentBasicDynamicSlot
negative?: ComponentBasicDynamicSlot | ComponentConditionalDynamicSlot
}
export type ComponentDynamicSlot =
| ComponentBasicDynamicSlot
| ComponentLoopDynamicSlot
| ComponentConditionalDynamicSlot
export interface CreateComponentIRNode extends BaseIRNode { export interface CreateComponentIRNode extends BaseIRNode {
type: IRNodeTypes.CREATE_COMPONENT_NODE type: IRNodeTypes.CREATE_COMPONENT_NODE
id: number id: number
tag: string tag: string
props: IRProps[] props: IRProps[]
slots: IRSlots[]
slots?: ComponentSlots
dynamicSlots?: ComponentDynamicSlot[]
asset: boolean asset: boolean
root: boolean root: boolean
once: boolean once: boolean

View File

@ -16,12 +16,11 @@ import {
import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared' import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared'
import { import {
type BlockIRNode, type BlockIRNode,
type ComponentDynamicSlot,
type ComponentSlots,
DynamicFlag, DynamicFlag,
type HackOptions, type HackOptions,
type IRDynamicInfo, type IRDynamicInfo,
IRNodeTypes, IRNodeTypes,
type IRSlots,
type OperationNode, type OperationNode,
type RootIRNode, type RootIRNode,
type VaporDirectiveNode, type VaporDirectiveNode,
@ -81,8 +80,7 @@ export class TransformContext<T extends AllNode = AllNode> {
component: Set<string> = this.ir.component component: Set<string> = this.ir.component
directive: Set<string> = this.ir.directive directive: Set<string> = this.ir.directive
slots?: ComponentSlots slots: IRSlots[] = []
dynamicSlots?: ComponentDynamicSlot[]
private globalId = 0 private globalId = 0
@ -96,14 +94,12 @@ export class TransformContext<T extends AllNode = AllNode> {
} }
enterBlock(ir: BlockIRNode, isVFor: boolean = false): () => void { enterBlock(ir: BlockIRNode, isVFor: boolean = false): () => void {
const { block, template, dynamic, childrenTemplate, slots, dynamicSlots } = const { block, template, dynamic, childrenTemplate, slots } = this
this
this.block = ir this.block = ir
this.dynamic = ir.dynamic this.dynamic = ir.dynamic
this.template = '' this.template = ''
this.childrenTemplate = [] this.childrenTemplate = []
this.slots = undefined this.slots = []
this.dynamicSlots = undefined
isVFor && this.inVFor++ isVFor && this.inVFor++
return () => { return () => {
// exit // exit
@ -113,7 +109,6 @@ export class TransformContext<T extends AllNode = AllNode> {
this.dynamic = dynamic this.dynamic = dynamic
this.childrenTemplate = childrenTemplate this.childrenTemplate = childrenTemplate
this.slots = slots this.slots = slots
this.dynamicSlots = dynamicSlots
isVFor && this.inVFor-- isVFor && this.inVFor--
} }
} }

View File

@ -105,11 +105,9 @@ function transformComponentElement(
asset, asset,
root, root,
slots: context.slots, slots: context.slots,
dynamicSlots: context.dynamicSlots,
once: context.inVOnce, once: context.inVOnce,
}) })
context.slots = undefined context.slots = []
context.dynamicSlots = undefined
} }
function resolveSetupReference(name: string, context: TransformContext) { function resolveSetupReference(name: string, context: TransformContext) {

View File

@ -3,6 +3,7 @@ import {
ElementTypes, ElementTypes,
ErrorCodes, ErrorCodes,
NodeTypes, NodeTypes,
type SimpleExpressionNode,
type TemplateChildNode, type TemplateChildNode,
createCompilerError, createCompilerError,
isTemplateNode, isTemplateNode,
@ -11,17 +12,19 @@ import {
import type { NodeTransform, TransformContext } from '../transform' import type { NodeTransform, TransformContext } from '../transform'
import { newBlock } from './utils' import { newBlock } from './utils'
import { import {
type ComponentBasicDynamicSlot,
type ComponentConditionalDynamicSlot,
type ComponentSlotBlockIRNode,
DynamicFlag, DynamicFlag,
DynamicSlotType,
type IRFor, type IRFor,
type IRSlotDynamic,
type IRSlotDynamicBasic,
type IRSlotDynamicConditional,
IRSlotType,
type IRSlots,
type IRSlotsStatic,
type SlotBlockIRNode,
type VaporDirectiveNode, type VaporDirectiveNode,
} from '../ir' } from '../ir'
import { findDir, resolveExpression } from '../utils' import { findDir, resolveExpression } from '../utils'
// TODO dynamic slots
export const transformVSlot: NodeTransform = (node, context) => { export const transformVSlot: NodeTransform = (node, context) => {
if (node.type !== NodeTypes.ELEMENT) return if (node.type !== NodeTypes.ELEMENT) return
@ -37,129 +40,127 @@ export const transformVSlot: NodeTransform = (node, context) => {
parent.node.tagType === ElementTypes.COMPONENT parent.node.tagType === ElementTypes.COMPONENT
if (isComponent && children.length) { if (isComponent && children.length) {
const arg = dir && dir.arg return transformComponentSlot(
const slotName = arg ? arg.content : 'default' node,
dir,
context as TransformContext<ElementNode>,
)
} else if (isSlotTemplate && dir) {
return transformTemplateSlot(
node,
dir,
context as TransformContext<ElementNode>,
)
} else if (!isComponent && dir) {
context.options.onError(
createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, dir.loc),
)
}
}
// <Foo v-slot:default>
function transformComponentSlot(
node: ElementNode,
dir: VaporDirectiveNode | undefined,
context: TransformContext<ElementNode>,
) {
const { children } = node
const arg = dir && dir.arg
const nonSlotTemplateChildren = children.filter( const nonSlotTemplateChildren = children.filter(
n => n =>
isNonWhitespaceContent(node) && isNonWhitespaceContent(node) &&
!(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot)), !(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot)),
) )
const [block, onExit] = createSlotBlock( const [block, onExit] = createSlotBlock(node, dir, context)
node,
dir,
context as TransformContext<ElementNode>,
)
const slots = (context.slots ||= {}) const { slots } = context
const dynamicSlots = (context.dynamicSlots ||= [])
return () => { return () => {
onExit() onExit()
let hasOtherSlots = !!Object.keys(slots).length const hasOtherSlots = !!slots.length
if (dir && hasOtherSlots) {
if (dir && (hasOtherSlots || dynamicSlots.length)) {
// already has on-component slot - this is incorrect usage. // already has on-component slot - this is incorrect usage.
context.options.onError( context.options.onError(
createCompilerError(ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, dir.loc), createCompilerError(ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, dir.loc),
) )
// discarding other slots, referenced how compiler-core implements return
Object.keys(slots).forEach(slotName => delete slots[slotName])
dynamicSlots.length = 0
hasOtherSlots = false
} }
if (nonSlotTemplateChildren.length) { if (nonSlotTemplateChildren.length) {
if (slots.default) { if (hasStaticSlot(slots, 'default')) {
context.options.onError( context.options.onError(
createCompilerError( createCompilerError(
ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN, ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
nonSlotTemplateChildren[0].loc, nonSlotTemplateChildren[0].loc,
), ),
) )
} else if (!arg || arg.isStatic) {
slots[slotName] = block
} else { } else {
dynamicSlots.push({ registerSlot(slots, arg, block)
slotType: DynamicSlotType.BASIC,
name: arg,
fn: block,
})
}
context.slots = slots context.slots = slots
}
} else if (hasOtherSlots) { } else if (hasOtherSlots) {
context.slots = slots context.slots = slots
} }
if (dynamicSlots.length) context.dynamicSlots = dynamicSlots
} }
} else if (isSlotTemplate && dir) { }
let { arg } = dir
// <template #foo>
function transformTemplateSlot(
node: ElementNode,
dir: VaporDirectiveNode,
context: TransformContext<ElementNode>,
) {
context.dynamic.flags |= DynamicFlag.NON_TEMPLATE context.dynamic.flags |= DynamicFlag.NON_TEMPLATE
const arg = dir.arg && resolveExpression(dir.arg)
const vFor = findDir(node, 'for') const vFor = findDir(node, 'for')
const vIf = findDir(node, 'if') const vIf = findDir(node, 'if')
const vElse = findDir(node, /^else(-if)?$/, true /* allowEmpty */) const vElse = findDir(node, /^else(-if)?$/, true /* allowEmpty */)
const slots = context.slots! const { slots } = context
const dynamicSlots = context.dynamicSlots! const [block, onExit] = createSlotBlock(node, dir, context)
const [block, onExit] = createSlotBlock( if (!vFor && !vIf && !vElse) {
node, const slotName = arg ? arg.isStatic && arg.content : 'default'
dir, if (slotName && hasStaticSlot(slots, slotName)) {
context as TransformContext<ElementNode>,
)
arg &&= resolveExpression(arg)
if ((!arg || arg.isStatic) && !vFor && !vIf && !vElse) {
const slotName = arg ? arg.content : 'default'
if (slots[slotName]) {
context.options.onError( context.options.onError(
createCompilerError( createCompilerError(ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES, dir.loc),
ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES,
dir.loc,
),
) )
} else { } else {
slots[slotName] = block registerSlot(slots, arg, block)
} }
} else if (vIf) { } else if (vIf) {
dynamicSlots.push({ registerDynamicSlot(slots, {
slotType: DynamicSlotType.CONDITIONAL, slotType: IRSlotType.CONDITIONAL,
condition: vIf.exp!, condition: vIf.exp!,
positive: { positive: {
slotType: DynamicSlotType.BASIC, slotType: IRSlotType.DYNAMIC,
name: arg!, name: arg!,
fn: block, fn: block,
}, },
}) })
} else if (vElse) { } else if (vElse) {
const vIfIR = dynamicSlots[dynamicSlots.length - 1] const vIfSlot = slots[slots.length - 1] as IRSlotDynamic
if (vIfIR.slotType === DynamicSlotType.CONDITIONAL) { if (vIfSlot.slotType === IRSlotType.CONDITIONAL) {
let ifNode = vIfIR let ifNode = vIfSlot
while ( while (
ifNode.negative && ifNode.negative &&
ifNode.negative.slotType === DynamicSlotType.CONDITIONAL ifNode.negative.slotType === IRSlotType.CONDITIONAL
) )
ifNode = ifNode.negative ifNode = ifNode.negative
const negative: const negative: IRSlotDynamicBasic | IRSlotDynamicConditional = vElse.exp
| ComponentBasicDynamicSlot
| ComponentConditionalDynamicSlot = vElse.exp
? { ? {
slotType: DynamicSlotType.CONDITIONAL, slotType: IRSlotType.CONDITIONAL,
condition: vElse.exp, condition: vElse.exp,
positive: { positive: {
slotType: DynamicSlotType.BASIC, slotType: IRSlotType.DYNAMIC,
name: arg!, name: arg!,
fn: block, fn: block,
}, },
} }
: { : {
slotType: DynamicSlotType.BASIC, slotType: IRSlotType.DYNAMIC,
name: arg!, name: arg!,
fn: block, fn: block,
} }
@ -171,42 +172,69 @@ export const transformVSlot: NodeTransform = (node, context) => {
} }
} else if (vFor) { } else if (vFor) {
if (vFor.forParseResult) { if (vFor.forParseResult) {
dynamicSlots.push({ registerDynamicSlot(slots, {
slotType: DynamicSlotType.LOOP, slotType: IRSlotType.LOOP,
name: arg!, name: arg!,
fn: block, fn: block,
loop: vFor.forParseResult as IRFor, loop: vFor.forParseResult as IRFor,
}) })
} else { } else {
context.options.onError( context.options.onError(
createCompilerError( createCompilerError(ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION, vFor.loc),
ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
vFor.loc,
),
) )
} }
}
return onExit
}
function ensureStaticSlots(slots: IRSlots[]): IRSlotsStatic['slots'] {
let lastSlots = slots[slots.length - 1]
if (!slots.length || lastSlots.slotType !== IRSlotType.STATIC) {
slots.push(
(lastSlots = {
slotType: IRSlotType.STATIC,
slots: {},
}),
)
}
return lastSlots.slots
}
function registerSlot(
slots: IRSlots[],
name: SimpleExpressionNode | undefined,
block: SlotBlockIRNode,
) {
const isStatic = !name || name.isStatic
if (isStatic) {
const staticSlots = ensureStaticSlots(slots)
staticSlots[name ? name.content : 'default'] = block
} else { } else {
dynamicSlots.push({ slots.push({
slotType: DynamicSlotType.BASIC, slotType: IRSlotType.DYNAMIC,
name: arg!, name: name!,
fn: block, fn: block,
}) })
} }
}
return () => onExit() function registerDynamicSlot(allSlots: IRSlots[], dynamic: IRSlotDynamic) {
} else if (!isComponent && dir) { allSlots.push(dynamic)
context.options.onError( }
createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, dir.loc),
) function hasStaticSlot(slots: IRSlots[], name: string) {
} return slots.some(slot => {
if (slot.slotType === IRSlotType.STATIC) return !!slot.slots[name]
})
} }
function createSlotBlock( function createSlotBlock(
slotNode: ElementNode, slotNode: ElementNode,
dir: VaporDirectiveNode | undefined, dir: VaporDirectiveNode | undefined,
context: TransformContext<ElementNode>, context: TransformContext<ElementNode>,
): [ComponentSlotBlockIRNode, () => void] { ): [SlotBlockIRNode, () => void] {
const block: ComponentSlotBlockIRNode = newBlock(slotNode) const block: SlotBlockIRNode = newBlock(slotNode)
block.props = dir && dir.exp block.props = dir && dir.exp
const exitBlock = context.enterBlock(block) const exitBlock = context.enterBlock(block)
return [block, exitBlock] return [block, exitBlock]

View File

@ -149,7 +149,7 @@ describe('api: setup context', () => {
const { html } = define({ const { html } = define({
render() { render() {
return createComponent(Child, null, null, [ return createComponent(Child, null, [
() => ({ () => ({
name: 'foo', name: 'foo',
fn: () => createTextNode(() => [id.value]), fn: () => createTextNode(() => [id.value]),

View File

@ -40,7 +40,6 @@ describe('attribute fallthrough', () => {
}, },
], ],
null, null,
null,
true, true,
) )
}, },
@ -85,7 +84,6 @@ describe('attribute fallthrough', () => {
}, },
], ],
null, null,
null,
true, true,
) )
}, },
@ -123,7 +121,6 @@ describe('attribute fallthrough', () => {
}, },
], ],
null, null,
null,
true, true,
) )
return n0 return n0
@ -146,7 +143,6 @@ describe('attribute fallthrough', () => {
}, },
], ],
null, null,
null,
true, true,
) )
}, },

View File

@ -244,7 +244,6 @@ describe('component: props', () => {
id: () => _ctx.id, id: () => _ctx.id,
}, },
null, null,
null,
true, true,
) )
}, },

View File

@ -2,6 +2,7 @@
import { import {
createComponent, createComponent,
createForSlots,
createSlot, createSlot,
createVaporApp, createVaporApp,
defineComponent, defineComponent,
@ -98,7 +99,7 @@ describe('component: slots', () => {
const { render } = define({ const { render } = define({
render() { render() {
return createComponent(Child, {}, { _: 2 as any }, [ return createComponent(Child, {}, [
() => () =>
flag1.value flag1.value
? { name: 'one', fn: () => template('<span></span>')() } ? { name: 'one', fn: () => template('<span></span>')() }
@ -134,11 +135,11 @@ describe('component: slots', () => {
const { render } = define({ const { render } = define({
setup() { setup() {
return createComponent(Child, {}, {}, [ return createComponent(Child, {}, [
() => () =>
flag1.value flag1.value
? [{ name: 'header', fn: () => template('header')() }] ? { name: 'header', fn: () => template('header')() }
: [{ name: 'footer', fn: () => template('footer')() }], : { name: 'footer', fn: () => template('footer')() },
]) ])
}, },
}) })
@ -172,35 +173,28 @@ describe('component: slots', () => {
const { instance } = define({ const { instance } = define({
render() { render() {
return createComponent( return createComponent(Comp, {}, [
Comp,
{},
{ {
default: () => { default: () => {
instanceInDefaultSlot = getCurrentInstance() instanceInDefaultSlot = getCurrentInstance()
return template('content')() return template('content')()
}, },
}, },
[ () => ({
() => [
{
name: 'inVFor', name: 'inVFor',
fn: () => { fn: () => {
instanceInVForSlot = getCurrentInstance() instanceInVForSlot = getCurrentInstance()
return template('content')() return template('content')()
}, },
}, }),
],
() => ({ () => ({
name: 'inVIf', name: 'inVIf',
key: '1',
fn: () => { fn: () => {
instanceInVIfSlot = getCurrentInstance() instanceInVIfSlot = getCurrentInstance()
return template('content')() return template('content')()
}, },
}), }),
], ])
)
}, },
}).render() }).render()
@ -223,7 +217,7 @@ describe('component: slots', () => {
const { render } = define({ const { render } = define({
render() { render() {
return createComponent(Child, {}, {}, [ return createComponent(Child, {}, [
() => { () => {
slotFn1() slotFn1()
return flag1.value return flag1.value
@ -264,6 +258,39 @@ describe('component: slots', () => {
expect(slotFn2).toHaveBeenCalledTimes(2) expect(slotFn2).toHaveBeenCalledTimes(2)
}) })
test('should work with createFlorSlots', async () => {
const loop = ref([1, 2, 3])
let instance: any
const Child = () => {
instance = getCurrentInstance()
return template('child')()
}
const { render } = define({
setup() {
return createComponent(Child, {}, [
() =>
createForSlots(loop.value, (item, i) => ({
name: item,
fn: () => template(item + i)(),
})),
])
},
})
render()
expect(instance.slots).toHaveProperty('1')
expect(instance.slots).toHaveProperty('2')
expect(instance.slots).toHaveProperty('3')
loop.value.push(4)
await nextTick()
expect(instance.slots).toHaveProperty('4')
loop.value.shift()
await nextTick()
expect(instance.slots).not.toHaveProperty('1')
})
test.todo('should respect $stable flag', async () => { test.todo('should respect $stable flag', async () => {
// TODO: $stable flag? // TODO: $stable flag?
}) })
@ -327,9 +354,7 @@ describe('component: slots', () => {
}) })
const { host } = define(() => { const { host } = define(() => {
return createComponent( return createComponent(Comp, {}, [
Comp,
{},
{ {
header: withDestructure( header: withDestructure(
({ title }) => [title], ({ title }) => [title],
@ -342,7 +367,7 @@ describe('component: slots', () => {
}, },
), ),
}, },
) ])
}).render() }).render()
expect(host.innerHTML).toBe('<div><h1>header</h1></div>') expect(host.innerHTML).toBe('<div><h1>header</h1></div>')
@ -481,10 +506,10 @@ describe('component: slots', () => {
const { host } = define(() => { const { host } = define(() => {
// dynamic slot // dynamic slot
return createComponent(Comp, {}, {}, [ return createComponent(Comp, {}, [
() => ({ () => ({
name: 'header', name: 'header',
fn: props => template(props.title)(), fn: (props: any) => template(props.title)(),
}), }),
]) ])
}).render() }).render()
@ -548,7 +573,7 @@ describe('component: slots', () => {
}) })
const { host } = define(() => { const { host } = define(() => {
return createComponent(Child, {}, {}, [ return createComponent(Child, {}, [
() => () =>
flag1.value flag1.value
? { name: 'one', fn: () => template('one content')() } ? { name: 'one', fn: () => template('one content')() }

View File

@ -69,7 +69,7 @@ describe('directive: v-show', () => {
const { instance, host } = define({ const { instance, host } = define({
render() { render() {
const n1 = t1() const n1 = t1()
const n2 = createComponent(Child, [], null, null, true) const n2 = createComponent(Child, [], null, true)
withDirectives(n2, [[vShow, () => visible.value]]) withDirectives(n2, [[vShow, () => visible.value]])
on(n1 as HTMLElement, 'click', () => handleClick) on(n1 as HTMLElement, 'click', () => handleClick)
return [n1, n2] return [n1, n2]

View File

@ -18,7 +18,7 @@ import { getCurrentScope } from '@vue/reactivity'
let removeComponentInstance = NOOP let removeComponentInstance = NOOP
beforeEach(() => { beforeEach(() => {
const instance = createComponentInstance((() => {}) as any, {}, null, null) const instance = createComponentInstance((() => {}) as any, {}, null)
const reset = setCurrentInstance(instance) const reset = setCurrentInstance(instance)
const prev = getCurrentScope() const prev = getCurrentScope()
instance.scope.on() instance.scope.on()

View File

@ -5,14 +5,13 @@ import {
} from './component' } from './component'
import { setupComponent } from './apiRender' import { setupComponent } from './apiRender'
import type { RawProps } from './componentProps' import type { RawProps } from './componentProps'
import type { DynamicSlots, Slots } from './componentSlots' import type { RawSlots } from './componentSlots'
import { withAttrs } from './componentAttrs' import { withAttrs } from './componentAttrs'
export function createComponent( export function createComponent(
comp: Component, comp: Component,
rawProps: RawProps | null = null, rawProps: RawProps | null = null,
slots: Slots | null = null, slots: RawSlots | null = null,
dynamicSlots: DynamicSlots | null = null,
singleRoot: boolean = false, singleRoot: boolean = false,
once: boolean = false, once: boolean = false,
) { ) {
@ -21,7 +20,6 @@ export function createComponent(
comp, comp,
singleRoot ? withAttrs(rawProps) : rawProps, singleRoot ? withAttrs(rawProps) : rawProps,
slots, slots,
dynamicSlots,
once, once,
) )
setupComponent(instance, singleRoot) setupComponent(instance, singleRoot)

View File

@ -112,7 +112,6 @@ export function createVaporApp(
rootComponent, rootComponent,
rootProps, rootProps,
null, null,
null,
false, false,
context, context,
) )

View File

@ -22,12 +22,7 @@ import {
emit, emit,
normalizeEmitsOptions, normalizeEmitsOptions,
} from './componentEmits' } from './componentEmits'
import { import { type RawSlots, type StaticSlots, initSlots } from './componentSlots'
type DynamicSlots,
type InternalSlots,
type Slots,
initSlots,
} from './componentSlots'
import { VaporLifecycleHooks } from './apiLifecycle' import { VaporLifecycleHooks } from './apiLifecycle'
import { warn } from './warning' import { warn } from './warning'
import { import {
@ -51,7 +46,7 @@ export type SetupContext<E = EmitsOptions> = E extends any
attrs: Data attrs: Data
emit: EmitFn<E> emit: EmitFn<E>
expose: (exposed?: Record<string, any>) => void expose: (exposed?: Record<string, any>) => void
slots: Readonly<InternalSlots> slots: Readonly<StaticSlots>
} }
: never : never
@ -179,13 +174,13 @@ export interface ComponentInternalInstance {
emit: EmitFn emit: EmitFn
emitted: Record<string, boolean> | null emitted: Record<string, boolean> | null
attrs: Data attrs: Data
slots: InternalSlots slots: StaticSlots
refs: Data refs: Data
// exposed properties via expose() // exposed properties via expose()
exposed?: Record<string, any> exposed?: Record<string, any>
attrsProxy?: Data attrsProxy?: Data
slotsProxy?: Slots slotsProxy?: StaticSlots
// lifecycle // lifecycle
isMounted: boolean isMounted: boolean
@ -266,8 +261,7 @@ let uid = 0
export function createComponentInstance( export function createComponentInstance(
component: Component, component: Component,
rawProps: RawProps | null, rawProps: RawProps | null,
slots: Slots | null, slots: RawSlots | null,
dynamicSlots: DynamicSlots | null,
once: boolean = false, once: boolean = false,
// application root node only // application root node only
appContext?: AppContext, appContext?: AppContext,
@ -363,7 +357,7 @@ export function createComponentInstance(
} }
instance.scope = new BlockEffectScope(instance, parent && parent.scope) instance.scope = new BlockEffectScope(instance, parent && parent.scope)
initProps(instance, rawProps, !isFunction(component), once) initProps(instance, rawProps, !isFunction(component), once)
initSlots(instance, slots, dynamicSlots) initSlots(instance, slots)
instance.emit = emit.bind(null, instance) instance.emit = emit.bind(null, instance)
return instance return instance
@ -417,7 +411,7 @@ function getAttrsProxy(instance: ComponentInternalInstance): Data {
/** /**
* Dev-only * Dev-only
*/ */
function getSlotsProxy(instance: ComponentInternalInstance): Slots { function getSlotsProxy(instance: ComponentInternalInstance): StaticSlots {
return ( return (
instance.slotsProxy || instance.slotsProxy ||
(instance.slotsProxy = new Proxy(instance.slots, { (instance.slotsProxy = new Proxy(instance.slots, {

View File

@ -13,7 +13,6 @@ import {
import { type Block, type Fragment, fragmentKey } from './apiRender' import { type Block, type Fragment, fragmentKey } from './apiRender'
import { firstEffect, renderEffect } from './renderEffect' import { firstEffect, renderEffect } from './renderEffect'
import { createComment, createTextNode, insert, remove } from './dom/element' import { createComment, createTextNode, insert, remove } from './dom/element'
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
import type { NormalizedRawProps } from './componentProps' import type { NormalizedRawProps } from './componentProps'
import type { Data } from '@vue/runtime-shared' import type { Data } from '@vue/runtime-shared'
import { mergeProps } from './dom/prop' import { mergeProps } from './dom/prop'
@ -24,82 +23,75 @@ export type Slot<T extends any = any> = (
...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)> ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>
) => Block ) => Block
export type InternalSlots = { export type StaticSlots = Record<string, Slot>
[name: string]: Slot | undefined export type DynamicSlot = { name: string; fn: Slot }
} export type DynamicSlotFn = () => DynamicSlot | DynamicSlot[] | undefined
export type NormalizedRawSlots = Array<StaticSlots | DynamicSlotFn>
export type RawSlots = NormalizedRawSlots | StaticSlots | null
export type Slots = Readonly<InternalSlots> export const isDynamicSlotFn = isFunction as (
val: StaticSlots | DynamicSlotFn,
export interface DynamicSlot { ) => val is DynamicSlotFn
name: string
fn: Slot
}
type DynamicSlotFn = () => DynamicSlot | DynamicSlot[]
export type DynamicSlots = DynamicSlotFn[]
export function initSlots( export function initSlots(
instance: ComponentInternalInstance, instance: ComponentInternalInstance,
rawSlots: InternalSlots | null = null, rawSlots: RawSlots | null = null,
dynamicSlots: DynamicSlots | null = null,
) { ) {
let slots: InternalSlots = {} if (!rawSlots) return
if (!isArray(rawSlots)) rawSlots = [rawSlots]
for (const key in rawSlots) { if (rawSlots.length === 1 && !isDynamicSlotFn(rawSlots[0])) {
const slot = rawSlots[key] instance.slots = rawSlots[0]
if (slot) { return
slots[key] = withCtx(slot)
}
} }
if (dynamicSlots) { const resolved: StaticSlots = (instance.slots = shallowReactive({}))
slots = shallowReactive(slots) const keys: Set<string>[] = []
const dynamicSlotRecords: Record<string, boolean>[] = [] rawSlots.forEach((slots, index) => {
dynamicSlots.forEach((fn, index) => { const isDynamicSlot = isDynamicSlotFn(slots)
if (isDynamicSlot) {
firstEffect(instance, () => { firstEffect(instance, () => {
const slotRecord = (dynamicSlotRecords[index] = const recordNames = keys[index] || (keys[index] = new Set())
dynamicSlotRecords[index] || {}) let dynamicSlot: ReturnType<DynamicSlotFn>
const dynamicSlot: DynamicSlot | DynamicSlot[] = if (isDynamicSlotFn(slots)) {
callWithAsyncErrorHandling( dynamicSlot = slots()
fn,
instance,
VaporErrorCodes.RENDER_FUNCTION,
)
// array of dynamic slot generated by <template v-for="..." #[...]>
if (isArray(dynamicSlot)) { if (isArray(dynamicSlot)) {
for (let j = 0; j < dynamicSlot.length; j++) { for (const slot of dynamicSlot) {
slots[dynamicSlot[j].name] = withCtx(dynamicSlot[j].fn) registerSlot(slot.name, slot.fn, recordNames)
slotRecord[dynamicSlot[j].name] = true
} }
} else if (dynamicSlot) { } else if (dynamicSlot) {
// conditional single slot generated by <template v-if="..." #foo> registerSlot(dynamicSlot.name, dynamicSlot.fn, recordNames)
slots[dynamicSlot.name] = withCtx(dynamicSlot.fn)
slotRecord[dynamicSlot.name] = true
} }
// delete stale slots } else {
for (const key in slotRecord) { }
for (const name of recordNames) {
if ( if (
slotRecord[key] && !(isArray(dynamicSlot)
!(dynamicSlot && isArray(dynamicSlot) ? dynamicSlot.some(s => s.name === name)
? dynamicSlot.some(s => s.name === key) : dynamicSlot && dynamicSlot.name === name)
: dynamicSlot.name === key)
) { ) {
slotRecord[key] = false recordNames.delete(name)
delete slots[key] delete resolved[name]
} }
} }
}) })
}) } else {
for (const name in slots) {
registerSlot(name, slots[name])
} }
}
})
instance.slots = slots function registerSlot(name: string, fn: Slot, recordNames?: Set<string>) {
resolved[name] = withCtx(fn)
recordNames && recordNames.add(name)
}
function withCtx(fn: Slot): Slot { function withCtx(fn: Slot): Slot {
return (...args: any[]) => { return (...args: any[]) => {
const reset = setCurrentInstance(instance.parent!) const reset = setCurrentInstance(instance.parent!)
try { try {
return fn(...args) return fn(...(args as any))
} finally { } finally {
reset() reset()
} }