Skip to content

Commit

Permalink
Merge branch 'main' into feat/directive-hooks-with-v-for
Browse files Browse the repository at this point in the history
  • Loading branch information
LittleSound committed Apr 9, 2024
2 parents b961c43 + 98bae0c commit 38c9d9f
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 36 deletions.
22 changes: 14 additions & 8 deletions packages/compiler-vapor/src/generators/block.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { type BlockIRNode, IRNodeTypes, type WithDirectiveIRNode } from '../ir'
import {
type BlockIRNode,
IRNodeTypes,
type OperationNode,
type WithDirectiveIRNode,
} from '../ir'
import {
type CodeFragment,
INDENT_END,
Expand Down Expand Up @@ -41,11 +46,7 @@ export function genBlockContent(
push(...genChildren(child, context, child.id!))
}

const directiveOps = operation.filter(
(oper): oper is WithDirectiveIRNode =>
oper.type === IRNodeTypes.WITH_DIRECTIVE,
)
for (const directives of groupDirective(directiveOps)) {
for (const directives of groupDirective(operation)) {
push(...genWithDirective(directives, context))
}

Expand All @@ -67,9 +68,14 @@ export function genBlockContent(
return frag
}

function groupDirective(ops: WithDirectiveIRNode[]): WithDirectiveIRNode[][] {
function groupDirective(operation: OperationNode[]): WithDirectiveIRNode[][] {
const directiveOps = operation.filter(
(oper): oper is WithDirectiveIRNode =>
oper.type === IRNodeTypes.WITH_DIRECTIVE,
)

const directiveMap: Record<number, WithDirectiveIRNode[]> = {}
for (const oper of ops) {
for (const oper of directiveOps) {
if (!directiveMap[oper.element]) directiveMap[oper.element] = []
directiveMap[oper.element].push(oper)
}
Expand Down
79 changes: 64 additions & 15 deletions packages/runtime-vapor/__tests__/componentSlots.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,6 @@ function renderWithSlots(slots: any): any {
}

describe('component: slots', () => {
test('initSlots: instance.slots should be set correctly', () => {
const { slots } = renderWithSlots({ _: 1 })
expect(slots).toMatchObject({ _: 1 })
})

// NOTE: slot normalization is not supported
test.todo(
'initSlots: should normalize object slots (when value is null, string, array)',
() => {},
)
test.todo(
'initSlots: should normalize object slots (when value is function)',
() => {},
)

test('initSlots: instance.slots should be set correctly', () => {
let instance: any
const Comp = defineComponent({
Expand All @@ -73,6 +58,16 @@ describe('component: slots', () => {
)
})

// NOTE: slot normalization is not supported
test.todo(
'initSlots: should normalize object slots (when value is null, string, array)',
() => {},
)
test.todo(
'initSlots: should normalize object slots (when value is function)',
() => {},
)

// runtime-core's "initSlots: instance.slots should be set correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)"
test('initSlots: instance.slots should be set correctly', () => {
const { slots } = renderWithSlots({
Expand Down Expand Up @@ -152,6 +147,60 @@ describe('component: slots', () => {
expect(instance.slots).toHaveProperty('footer')
})

test('the current instance should be kept in the slot', async () => {
let instanceInDefaultSlot: any
let instanceInVForSlot: any
let instanceInVIfSlot: any

const Comp = defineComponent({
render() {
const instance = getCurrentInstance()
instance!.slots.default!()
instance!.slots.inVFor!()
instance!.slots.inVIf!()
return template('<div></div>')()
},
})

const { instance } = define({
render() {
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')()
},
},
],
)
},
}).render()

expect(instanceInDefaultSlot).toBe(instance)
expect(instanceInVForSlot).toBe(instance)
expect(instanceInVIfSlot).toBe(instance)
})

test.todo('should respect $stable flag', async () => {
// TODO: $stable flag?
})
Expand Down
46 changes: 33 additions & 13 deletions packages/runtime-vapor/src/componentSlots.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type IfAny, extend, isArray } from '@vue/shared'
import { type IfAny, isArray } from '@vue/shared'
import { baseWatch } from '@vue/reactivity'
import type { ComponentInternalInstance } from './component'
import { type ComponentInternalInstance, setCurrentInstance } from './component'
import type { Block } from './apiRender'
import { createVaporPreScheduler } from './scheduler'

Expand Down Expand Up @@ -29,7 +29,14 @@ export const initSlots = (
rawSlots: InternalSlots | null = null,
dynamicSlots: DynamicSlots | null = null,
) => {
const slots: InternalSlots = extend({}, rawSlots)
const slots: InternalSlots = {}

for (const key in rawSlots) {
const slot = rawSlots[key]
if (slot) {
slots[key] = withCtx(slot)
}
}

if (dynamicSlots) {
const dynamicSlotKeys: Record<string, true> = {}
Expand All @@ -41,20 +48,22 @@ export const initSlots = (
// array of dynamic slot generated by <template v-for="..." #[...]>
if (isArray(slot)) {
for (let j = 0; j < slot.length; j++) {
slots[slot[j].name] = slot[j].fn
slots[slot[j].name] = withCtx(slot[j].fn)
dynamicSlotKeys[slot[j].name] = true
}
} else if (slot) {
// conditional single slot generated by <template v-if="..." #foo>
slots[slot.name] = slot.key
? (...args: any[]) => {
const res = slot.fn(...args)
// attach branch key so each conditional branch is considered a
// different fragment
if (res) (res as any).key = slot.key
return res
}
: slot.fn
slots[slot.name] = withCtx(
slot.key
? (...args: any[]) => {
const res = slot.fn(...args)
// attach branch key so each conditional branch is considered a
// different fragment
if (res) (res as any).key = slot.key
return res
}
: slot.fn,
)
dynamicSlotKeys[slot.name] = true
}
}
Expand All @@ -77,4 +86,15 @@ export const initSlots = (
}

instance.slots = slots

function withCtx(fn: Slot): Slot {
return (...args: any[]) => {
const reset = setCurrentInstance(instance.parent!)
try {
return fn(...args)
} finally {
reset()
}
}
}
}

0 comments on commit 38c9d9f

Please sign in to comment.