Skip to content

Commit

Permalink
feat(compiler-vapor, runtime-vapor): implement v-slot + v-if
Browse files Browse the repository at this point in the history
  • Loading branch information
Doctor-wu committed May 12, 2024
1 parent 7297cae commit bc1b230
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const t0 = _template("foo")
export function render(_ctx) {
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: _ctx.i,
fn: () => {
const n0 = t0()
Expand All @@ -34,6 +34,44 @@ export function render(_ctx) {
}"
`;

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';
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
},
key: "0"
}
: _ctx.anotherCondition
? {
name: "condition",
fn: () => {
const n2 = t1()
return n2
},
key: "1"
}
: {
name: "condition",
fn: () => {
const n4 = t2()
return n4
},
key: "2"
}], true)
return n6
}"
`;

exports[`compiler: transform slot > implicit default slot 1`] = `
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
Expand Down
39 changes: 39 additions & 0 deletions packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ErrorCodes, NodeTypes } from '@vue/compiler-core'
import {
DynamicSlotType,
IRNodeTypes,
transformChildren,
transformElement,
Expand Down Expand Up @@ -159,6 +160,44 @@ describe('compiler: transform slot', () => {
])
})

test('dynamic slots name w/ v-if / v-else[-if]', () => {
const { ir, code } = compileWithSlots(
`<Comp>
<template v-if="condition" #condition>condition slot</template>
<template v-else-if="anotherCondition" #condition>another condition</template>
<template v-else #condition>else condition</template>
</Comp>`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: undefined,
dynamicSlots: [
{
slotType: DynamicSlotType.CONDITIONAL,
condition: { content: 'condition' },
positive: {
slotType: DynamicSlotType.BASIC,
key: 0,
},
negative: {
slotType: DynamicSlotType.CONDITIONAL,
condition: { content: 'anotherCondition' },
positive: {
slotType: DynamicSlotType.BASIC,
key: 1,
},
negative: { slotType: DynamicSlotType.BASIC, key: 2 },
},
},
],
},
])
})

describe('errors', () => {
test('error on extraneous children w/ named default slot', () => {
const onError = vi.fn()
Expand Down
69 changes: 57 additions & 12 deletions packages/compiler-vapor/src/generators/component.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import { camelize, extend, isArray } from '@vue/shared'
import type { CodegenContext } from '../generate'
import {
type ComponentBasicDynamicSlot,
type ComponentConditionalDynamicSlot,
type ComponentDynamicSlot,
type ComponentLoopDynamicSlot,
type ComponentSlots,
type CreateComponentIRNode,
DynamicSlotType,
IRDynamicPropsKind,
type IRProp,
type IRProps,
type IRPropsStatic,
} from '../ir'
import {
type CodeFragment,
INDENT_END,
INDENT_START,
NEWLINE,
SEGMENTS_ARRAY,
SEGMENTS_ARRAY_NEWLINE,
Expand Down Expand Up @@ -158,21 +164,42 @@ function genDynamicSlots(
) {
const slotsExpr = genMulti(
dynamicSlots.length > 1 ? SEGMENTS_ARRAY_NEWLINE : SEGMENTS_ARRAY,
...dynamicSlots.map(slot => {
const { name, fn, forResult } = slot
return forResult
? genForSlot(slot, context)
: genMulti(
SEGMENTS_OBJECT_NEWLINE,
['name: ', ...genExpression(name, context)],
['fn: ', ...genBlock(fn, context)],
)
}),
...dynamicSlots.map(slot => genDynamicSlot(slot, context)),
)
return ['() => ', ...slotsExpr]
}

function genForSlot(slot: ComponentDynamicSlot, context: CodegenContext) {
function genDynamicSlot(
slot: ComponentDynamicSlot,
context: CodegenContext,
): CodeFragment[] {
switch (slot.slotType) {
case DynamicSlotType.BASIC:
return genBasicDynamicSlot(slot, context)
case DynamicSlotType.LOOP:
return genLoopSlot(slot, context)
case DynamicSlotType.CONDITIONAL:
return genConditionalSlot(slot, context)
}
}

function genBasicDynamicSlot(
slot: ComponentBasicDynamicSlot,
context: CodegenContext,
): CodeFragment[] {
const { name, fn, key } = slot
return genMulti(
SEGMENTS_OBJECT_NEWLINE,
['name: ', ...genExpression(name, context)],
['fn: ', ...genBlock(fn, context)],
...(key !== undefined ? [`key: "${key}"`] : []),
)
}

function genLoopSlot(
slot: ComponentLoopDynamicSlot,
context: CodegenContext,
): CodeFragment[] {
const { name, fn, forResult } = slot
const { value, key, index, source } = forResult!
const rawValue = value && value.content
Expand All @@ -191,7 +218,7 @@ function genForSlot(slot: ComponentDynamicSlot, context: CodegenContext) {
return [
...genCall(
context.vaporHelper('createForSlots'),
['() => (', ...genExpression(source, context), ')'],
genExpression(source, context),
[
`(`,
[value, key, index]
Expand All @@ -205,3 +232,21 @@ function genForSlot(slot: ComponentDynamicSlot, context: CodegenContext) {
),
]
}

function genConditionalSlot(
slot: ComponentConditionalDynamicSlot,
context: CodegenContext,
): CodeFragment[] {
const { condition, positive, negative } = slot
return [
...genExpression(condition, context),
INDENT_START,
NEWLINE,
'? ',
...genDynamicSlot(positive, context),
NEWLINE,
': ',
...(negative ? [...genDynamicSlot(negative, context)] : ['void 0']),
INDENT_END,
]
}
32 changes: 29 additions & 3 deletions packages/compiler-vapor/src/ir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,13 +211,39 @@ export interface ComponentSlotBlockIRNode extends BlockIRNode {
// TODO slot props
}
export type ComponentSlots = Record<string, ComponentSlotBlockIRNode>
export interface ComponentDynamicSlot {

export enum DynamicSlotType {
BASIC,
LOOP,
CONDITIONAL,
}

export type ComponentBasicDynamicSlot = {
slotType: DynamicSlotType.BASIC
name: SimpleExpressionNode
fn: ComponentSlotBlockIRNode
key?: string
forResult?: VaporForParseResult
key?: number
}

export type ComponentLoopDynamicSlot = {
slotType: DynamicSlotType.LOOP
name: SimpleExpressionNode
fn: ComponentSlotBlockIRNode
forResult: VaporForParseResult
}

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
Expand Down
79 changes: 72 additions & 7 deletions packages/compiler-vapor/src/transforms/vSlot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import type { NodeTransform, TransformContext } from '../transform'
import { newBlock } from './utils'
import {
type BlockIRNode,
type ComponentBasicDynamicSlot,
type ComponentConditionalDynamicSlot,
DynamicFlag,
DynamicSlotType,
type VaporDirectiveNode,
type VaporForParseResult,
} from '../ir'
Expand Down Expand Up @@ -74,6 +77,9 @@ export const transformVSlot: NodeTransform = (node, context) => {

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!

Expand All @@ -84,7 +90,7 @@ export const transformVSlot: NodeTransform = (node, context) => {

arg &&= resolveExpression(arg)

if (!arg || arg.isStatic) {
if ((!arg || arg.isStatic) && !vFor && !vIf && !vElse) {
const slotName = arg ? arg.content : 'default'

if (slots[slotName]) {
Expand All @@ -98,12 +104,71 @@ export const transformVSlot: NodeTransform = (node, context) => {
slots[slotName] = block
}
} else {
const vFor = findDir(node, 'for')
dynamicSlots.push({
name: arg,
fn: block,
forResult: vFor?.forParseResult as VaporForParseResult,
})
if (vIf) {
dynamicSlots.push({
slotType: DynamicSlotType.CONDITIONAL,
condition: vIf.exp!,
positive: {
slotType: DynamicSlotType.BASIC,
name: arg!,
fn: block,
key: 0,
},
})
} else if (vElse) {
const vIfIR = dynamicSlots[dynamicSlots.length - 1]
if (vIfIR.slotType === DynamicSlotType.CONDITIONAL) {
let ifNode = vIfIR
while (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,
key: ifNode.positive.key! + 1,
},
}
: {
slotType: DynamicSlotType.BASIC,
name: arg!,
fn: block,
key: ifNode.positive.key! + 1,
}
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,
forResult: vFor.forParseResult as VaporForParseResult,
})
} 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()
}
Expand Down
3 changes: 1 addition & 2 deletions packages/runtime-vapor/src/apiCreateFor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,10 +303,9 @@ export const createFor = (
}

export const createForSlots = (
src: () => any[] | Record<any, any> | number | Set<any> | Map<any, any>,
source: any[] | Record<any, any> | number | Set<any> | Map<any, any>,
getSlot: (item: any, key: any, index?: number) => DynamicSlot,
) => {
const source = src()
const sourceLength = getLength(source)
const slots = new Array(sourceLength)
for (let i = 0; i < sourceLength; i++) {
Expand Down

0 comments on commit bc1b230

Please sign in to comment.