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,16 +33,20 @@ export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const _directive_hello = _resolveDirective("hello")
const _directive_test = _resolveDirective("test")
const n4 = _createComponent(_component_Comp, null, { default: () => {
const n0 = _createIf(() => (true), () => {
const n3 = t0()
const n2 = _createComponent(_component_Bar)
_withDirectives(n2, [[_directive_hello, void 0, void 0, { world: true }]])
_insert(n2, n3)
return n3
})
return n0
} }, null, true)
const n4 = _createComponent(_component_Comp, null, [
{
default: () => {
const n0 = _createIf(() => (true), () => {
const n3 = t0()
const n2 = _createComponent(_component_Bar)
_withDirectives(n2, [[_directive_hello, void 0, void 0, { world: true }]])
_insert(n2, n3)
return n3
})
return n0
}
}
], true)
_withDirectives(n4, [[_directive_test]])
return n4
}"

View File

@ -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, null, null, true)
const n0 = _createComponent(_component_Example, 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, null, null, true)
const n0 = _createComponent(_ctx.Comp, 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, null, null, true)
const n0 = _createComponent(_component_Foo, null, null, true)
return n0
}"
`;
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
})()"
`;
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
})()"
`;
@ -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, null, null, true)
const n0 = _createComponent(_ctx.Example, null, null, true)
return n0
}"
`;
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
})()"
`;
@ -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, null, null, true)
const n0 = _createComponent(_ctx.Foo.Example, 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, null, null, true)
const n0 = _createComponent(Foo.Example, 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, null, null, true)
const n0 = _createComponent(_ctx.Foo.Example, 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)) }
], null, null, true)
], null, true)
return n0
}"
`;
@ -117,7 +117,7 @@ export function render(_ctx) {
id: () => ("foo"),
class: () => ("bar")
}
], null, null, true)
], null, true)
return n0
}"
`;
@ -129,7 +129,7 @@ export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, [
() => (_ctx.obj)
], null, null, true)
], null, true)
return n0
}"
`;
@ -142,7 +142,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Foo, [
{ id: () => ("foo") },
() => (_ctx.obj)
], null, null, true)
], null, true)
return n0
}"
`;
@ -155,7 +155,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Foo, [
() => (_ctx.obj),
{ id: () => ("foo") }
], null, null, true)
], null, true)
return n0
}"
`;
@ -169,7 +169,7 @@ export function render(_ctx) {
{ id: () => ("foo") },
() => (_ctx.obj),
{ class: () => ("bar") }
], null, null, true)
], 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))
], null, null, true)
], 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 })
], null, null, true)
], 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 })
], null, null, true)
], null, true)
return n0
}"
`;

View File

@ -9,7 +9,7 @@ export function render(_ctx) {
{ modelValue: () => (_ctx.foo),
"onUpdate:modelValue": () => $event => (_ctx.foo = $event),
modelModifiers: () => ({ trim: true, "bar-baz": true }) }
], null, null, true)
], 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) }
], null, null, true)
], null, true)
return n0
}"
`;
@ -41,7 +41,7 @@ export function render(_ctx) {
"onUpdate:bar": () => $event => (_ctx.bar = $event),
barModifiers: () => ({ number: true })
}
], null, null, true)
], 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) }
], null, null, true)
], 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 }) })
], null, null, true)
], 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) })
], null, null, true)
], null, true)
return n0
}"
`;

View File

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

View File

@ -6,82 +6,90 @@ 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)
const n2 = _createComponent(_component_Comp, null, [
() => ({
name: _ctx.name,
fn: () => {
const n0 = t0()
return n0
}
})
], true)
return n2
}"
`;
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")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n2 = _createComponent(_component_Comp, null, null, [() => (_createForSlots(_ctx.list, (item) => ({
name: item,
fn: _withDestructure(({ bar }) => [bar], (_ctx0) => {
const n0 = t0()
return n0
})
})))], true)
const n2 = _createComponent(_component_Comp, null, [
() => (_createForSlots(_ctx.list, (item) => ({
name: item,
fn: _withDestructure(({ bar }) => [bar], (_ctx0) => {
const n0 = t0()
return n0
})
})))
], true)
return n2
}"
`;
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")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n2 = _createComponent(_component_Comp, null, null, [() => (_createForSlots(_ctx.list, (_, __, index) => ({
name: index,
fn: () => {
const n0 = t0()
return n0
}
})))], true)
const n2 = _createComponent(_component_Comp, null, [
() => (_createForSlots(_ctx.list, (_, __, index) => ({
name: index,
fn: () => {
const n0 = t0()
return n0
}
})))
], true)
return n2
}"
`;
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 t1 = _template("another condition")
const t2 = _template("else condition")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n6 = _createComponent(_component_Comp, null, null, [() => (_ctx.condition
? {
name: "condition",
fn: () => {
const n0 = t0()
return n0
}
}
: _ctx.anotherCondition
const n6 = _createComponent(_component_Comp, null, [
() => (_ctx.condition
? {
name: "condition",
fn: _withDestructure(({ foo, bar }) => [foo, bar], (_ctx0) => {
const n2 = t1()
return n2
})
}
: {
name: "condition",
fn: () => {
const n4 = t2()
return n4
const n0 = t0()
return n0
}
})], true)
}
: _ctx.anotherCondition
? {
name: "condition",
fn: _withDestructure(({ foo, bar }) => [foo, bar], (_ctx0) => {
const n2 = t1()
return n2
})
}
: {
name: "condition",
fn: () => {
const n4 = t2()
return n4
}
})
], true)
return n6
}"
`;
@ -92,10 +100,14 @@ const t0 = _template("<div></div>")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n1 = _createComponent(_component_Comp, null, { default: () => {
const n0 = t0()
return n0
} }, null, true)
const n1 = _createComponent(_component_Comp, null, [
{
default: () => {
const n0 = t0()
return n0
}
}
], true)
return n1
}"
`;
@ -108,79 +120,99 @@ const t2 = _template("<span></span>")
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]
const n4 = _createComponent(_component_Comp, null, [
{
one: () => {
const n0 = t0()
return n0
},
default: () => {
const n2 = t1()
const n3 = t2()
return [n2, n3]
}
}
}, null, true)
], true)
return n4
}"
`;
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(" ")
export function render(_ctx) {
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
}) })
const n3 = _createTextNode(() => [_ctx0[0] + _ctx.bar + _ctx.baz])
return [n1, n2, n3]
}) }, null, true)
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
})
}
])
const n3 = _createTextNode(() => [_ctx0[0] + _ctx.bar + _ctx.baz])
return [n1, n2, n3]
})
}
], 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';
"import { resolveComponent as _resolveComponent, createTextNode as _createTextNode, withDestructure as _withDestructure, createComponent as _createComponent } 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
const n1 = _createComponent(_component_Comp, null, [
() => ({
name: _ctx.named,
fn: _withDestructure(({ foo }) => [foo], (_ctx0) => {
const n0 = _createTextNode(() => [_ctx0[0] + _ctx.bar])
return n0
})
})
})], true)
], 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';
"import { resolveComponent as _resolveComponent, createTextNode as _createTextNode, withDestructure as _withDestructure, createComponent as _createComponent } 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)
const n1 = _createComponent(_component_Comp, null, [
{
named: _withDestructure(({ foo }) => [foo], (_ctx0) => {
const n0 = _createTextNode(() => [_ctx0[0] + _ctx.bar])
return n0
})
}
], 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';
"import { resolveComponent as _resolveComponent, createTextNode as _createTextNode, withDestructure as _withDestructure, createComponent as _createComponent } 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)
const n1 = _createComponent(_component_Comp, null, [
{
default: _withDestructure(({ foo }) => [foo], (_ctx0) => {
const n0 = _createTextNode(() => [_ctx0[0] + _ctx.bar])
return n0
})
}
], true)
return n1
}"
`;

View File

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

View File

@ -1,7 +1,7 @@
import { ErrorCodes, NodeTypes } from '@vue/compiler-core'
import {
DynamicSlotType,
IRNodeTypes,
IRSlotType,
transformChildren,
transformElement,
transformSlotOutlet,
@ -42,14 +42,19 @@ describe('compiler: transform slot', () => {
id: 1,
tag: 'Comp',
props: [[]],
slots: {
default: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
slots: [
{
slotType: IRSlotType.STATIC,
slots: {
default: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
},
},
},
},
},
],
},
])
expect(ir.block.returns).toEqual([1])
@ -73,19 +78,24 @@ describe('compiler: transform slot', () => {
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' }],
slots: [
{
slotType: IRSlotType.STATIC,
slots: {
default: {
type: IRNodeTypes.BLOCK,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: '{ foo }',
ast: {
type: 'ArrowFunctionExpression',
params: [{ type: 'ObjectPattern' }],
},
},
},
},
},
},
],
},
])
})
@ -103,15 +113,20 @@ describe('compiler: transform slot', () => {
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: {
named: {
type: IRNodeTypes.BLOCK,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: '{ foo }',
slots: [
{
slotType: IRSlotType.STATIC,
slots: {
named: {
type: IRNodeTypes.BLOCK,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: '{ foo }',
},
},
},
},
},
],
},
])
})
@ -130,7 +145,7 @@ describe('compiler: transform slot', () => {
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
dynamicSlots: [
slots: [
{
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
@ -165,20 +180,25 @@ describe('compiler: transform slot', () => {
id: 4,
tag: 'Comp',
props: [[]],
slots: {
one: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
slots: [
{
slotType: IRSlotType.STATIC,
slots: {
one: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
},
},
default: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{}, { template: 1 }, { template: 2 }],
},
},
},
},
default: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{}, { template: 1 }, { template: 2 }],
},
},
},
],
},
])
})
@ -207,31 +227,41 @@ describe('compiler: transform slot', () => {
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [[]],
slots: {
default: {
type: IRNodeTypes.BLOCK,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: '{ foo }',
slots: [
{
slotType: IRSlotType.STATIC,
slots: {
default: {
type: IRNodeTypes.BLOCK,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: '{ foo }',
},
},
},
},
},
],
},
])
expect(
(ir.block.operation[0] as any).slots.default.operation[0],
(ir.block.operation[0] as any).slots[0].slots.default.operation[0],
).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Inner',
slots: {
default: {
type: IRNodeTypes.BLOCK,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: '{ bar }',
slots: [
{
slotType: IRSlotType.STATIC,
slots: {
default: {
type: IRNodeTypes.BLOCK,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: '{ bar }',
},
},
},
},
},
],
})
})
@ -247,8 +277,7 @@ describe('compiler: transform slot', () => {
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: undefined,
dynamicSlots: [
slots: [
{
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
@ -278,8 +307,7 @@ describe('compiler: transform slot', () => {
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: undefined,
dynamicSlots: [
slots: [
{
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
@ -310,8 +338,7 @@ describe('compiler: transform slot', () => {
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: undefined,
dynamicSlots: [
slots: [
{
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
@ -350,21 +377,20 @@ describe('compiler: transform slot', () => {
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: undefined,
dynamicSlots: [
slots: [
{
slotType: DynamicSlotType.CONDITIONAL,
slotType: IRSlotType.CONDITIONAL,
condition: { content: 'condition' },
positive: {
slotType: DynamicSlotType.BASIC,
slotType: IRSlotType.DYNAMIC,
},
negative: {
slotType: DynamicSlotType.CONDITIONAL,
slotType: IRSlotType.CONDITIONAL,
condition: { content: 'anotherCondition' },
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 type { CodegenContext } from '../generate'
import {
type ComponentBasicDynamicSlot,
type ComponentConditionalDynamicSlot,
type ComponentDynamicSlot,
type ComponentLoopDynamicSlot,
type ComponentSlotBlockIRNode,
type ComponentSlots,
type CreateComponentIRNode,
DynamicSlotType,
IRDynamicPropsKind,
type IRProp,
type IRProps,
type IRPropsStatic,
type IRSlotDynamic,
type IRSlotDynamicBasic,
type IRSlotDynamicConditional,
type IRSlotDynamicLoop,
IRSlotType,
type IRSlots,
type IRSlotsStatic,
type SlotBlockIRNode,
} from '../ir'
import {
type CodeFragment,
@ -45,8 +46,9 @@ export function genCreateComponent(
const { vaporHelper } = context
const tag = genTag()
const { root, slots, dynamicSlots, once } = oper
const rawProps = genRawProps(oper.props, context)
const { root, props, slots, once } = oper
const rawProps = genRawProps(props, context)
const rawSlots = genRawSlots(slots, context)
return [
NEWLINE,
@ -55,8 +57,7 @@ export function genCreateComponent(
vaporHelper('createComponent'),
tag,
rawProps,
slots && genSlots(slots, context),
dynamicSlots && genDynamicSlots(dynamicSlots, context),
rawSlots,
root ? 'true' : false,
once && 'true',
),
@ -152,51 +153,51 @@ function genModelModifiers(
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)
return genMulti(
names.length > 1 ? DELIMITERS_OBJECT_NEWLINE : DELIMITERS_OBJECT,
DELIMITERS_OBJECT_NEWLINE,
...names.map(name => [
name,
': ',
`${name}: `,
...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(
slot: ComponentDynamicSlot,
slot: IRSlotDynamic,
context: CodegenContext,
top = false,
withFunction = false,
): CodeFragment[] {
let frag: CodeFragment[]
switch (slot.slotType) {
case DynamicSlotType.BASIC:
return top
? ['() => (', ...genBasicDynamicSlot(slot, context), ')']
: genBasicDynamicSlot(slot, context)
case DynamicSlotType.LOOP:
return top
? ['() => (', ...genLoopSlot(slot, context), ')']
: genLoopSlot(slot, context)
case DynamicSlotType.CONDITIONAL:
return top
? ['() => (', ...genConditionalSlot(slot, context), ')']
: genConditionalSlot(slot, context)
case IRSlotType.DYNAMIC:
frag = genBasicDynamicSlot(slot, context)
break
case IRSlotType.LOOP:
frag = genLoopSlot(slot, context)
break
case IRSlotType.CONDITIONAL:
frag = genConditionalSlot(slot, context)
break
}
return withFunction ? ['() => (', ...frag, ')'] : frag
}
function genBasicDynamicSlot(
slot: ComponentBasicDynamicSlot,
slot: IRSlotDynamicBasic,
context: CodegenContext,
): CodeFragment[] {
const { name, fn } = slot
@ -208,7 +209,7 @@ function genBasicDynamicSlot(
}
function genLoopSlot(
slot: ComponentLoopDynamicSlot,
slot: IRSlotDynamicLoop,
context: CodegenContext,
): CodeFragment[] {
const { name, fn, loop } = slot
@ -249,7 +250,7 @@ function genLoopSlot(
}
function genConditionalSlot(
slot: ComponentConditionalDynamicSlot,
slot: IRSlotDynamicConditional,
context: CodegenContext,
): CodeFragment[] {
const { condition, positive, negative } = slot
@ -266,10 +267,7 @@ function genConditionalSlot(
]
}
function genSlotBlockWithProps(
oper: ComponentSlotBlockIRNode,
context: CodegenContext,
) {
function genSlotBlockWithProps(oper: SlotBlockIRNode, context: CodegenContext) {
let isDestructureAssignment = false
let rawProps: 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,
} from '@vue/compiler-dom'
import type { Prettify } from '@vue/shared'
import type {
DirectiveTransform,
DirectiveTransformResult,
NodeTransform,
} from './transform'
import type { DirectiveTransform, NodeTransform } from '../transform'
import type { IRProp, IRProps, IRSlots } from './component'
export * from './component'
export enum IRNodeTypes {
ROOT,
@ -88,29 +87,6 @@ export interface ForIRNode extends BaseIRNode, IRFor {
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 {
type: IRNodeTypes.SET_PROP
element: number
@ -207,51 +183,12 @@ export interface WithDirectiveIRNode extends BaseIRNode {
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 {
type: IRNodeTypes.CREATE_COMPONENT_NODE
id: number
tag: string
props: IRProps[]
slots?: ComponentSlots
dynamicSlots?: ComponentDynamicSlot[]
slots: IRSlots[]
asset: boolean
root: boolean
once: boolean

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@
import {
createComponent,
createForSlots,
createSlot,
createVaporApp,
defineComponent,
@ -98,7 +99,7 @@ describe('component: slots', () => {
const { render } = define({
render() {
return createComponent(Child, {}, { _: 2 as any }, [
return createComponent(Child, {}, [
() =>
flag1.value
? { name: 'one', fn: () => template('<span></span>')() }
@ -134,11 +135,11 @@ describe('component: slots', () => {
const { render } = define({
setup() {
return createComponent(Child, {}, {}, [
return createComponent(Child, {}, [
() =>
flag1.value
? [{ name: 'header', fn: () => template('header')() }]
: [{ name: 'footer', fn: () => template('footer')() }],
? { name: 'header', fn: () => template('header')() }
: { name: 'footer', fn: () => template('footer')() },
])
},
})
@ -172,35 +173,28 @@ describe('component: slots', () => {
const { instance } = define({
render() {
return createComponent(
Comp,
{},
return createComponent(Comp, {}, [
{
default: () => {
instanceInDefaultSlot = getCurrentInstance()
return template('content')()
},
},
[
() => [
{
name: 'inVFor',
fn: () => {
instanceInVForSlot = getCurrentInstance()
return template('content')()
},
},
],
() => ({
name: 'inVIf',
key: '1',
fn: () => {
instanceInVIfSlot = getCurrentInstance()
return template('content')()
},
}),
],
)
() => ({
name: 'inVFor',
fn: () => {
instanceInVForSlot = getCurrentInstance()
return template('content')()
},
}),
() => ({
name: 'inVIf',
fn: () => {
instanceInVIfSlot = getCurrentInstance()
return template('content')()
},
}),
])
},
}).render()
@ -223,7 +217,7 @@ describe('component: slots', () => {
const { render } = define({
render() {
return createComponent(Child, {}, {}, [
return createComponent(Child, {}, [
() => {
slotFn1()
return flag1.value
@ -264,6 +258,39 @@ describe('component: slots', () => {
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 () => {
// TODO: $stable flag?
})
@ -327,9 +354,7 @@ describe('component: slots', () => {
})
const { host } = define(() => {
return createComponent(
Comp,
{},
return createComponent(Comp, {}, [
{
header: withDestructure(
({ title }) => [title],
@ -342,7 +367,7 @@ describe('component: slots', () => {
},
),
},
)
])
}).render()
expect(host.innerHTML).toBe('<div><h1>header</h1></div>')
@ -481,10 +506,10 @@ describe('component: slots', () => {
const { host } = define(() => {
// dynamic slot
return createComponent(Comp, {}, {}, [
return createComponent(Comp, {}, [
() => ({
name: 'header',
fn: props => template(props.title)(),
fn: (props: any) => template(props.title)(),
}),
])
}).render()
@ -548,7 +573,7 @@ describe('component: slots', () => {
})
const { host } = define(() => {
return createComponent(Child, {}, {}, [
return createComponent(Child, {}, [
() =>
flag1.value
? { name: 'one', fn: () => template('one content')() }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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