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