feat(compiler-vapor): v-slot props + v-slot on component (#223)

Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
Rizumu Ayaka 2024-06-03 06:41:14 +08:00 committed by GitHub
parent cef446af7a
commit 208dbc6d65
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 395 additions and 66 deletions

View File

@ -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

View File

@ -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
}" }"
`; `;

View File

@ -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`,
) )

View File

@ -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,
},
},
})
})
}) })
}) })

View File

@ -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,

View File

@ -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
}

View File

@ -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,

View File

@ -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>

View File

@ -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 {