Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(runtime-vapor): component slot #143

Merged
merged 44 commits into from
Mar 24, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
d435acb
feat(runtime-vapor): component slot
Ubugeeei Mar 4, 2024
711645e
Merge branch 'main' of github.com:vuejs/core-vapor into ubugeeei/feat…
Ubugeeei Mar 4, 2024
61ada62
chore: render fn arg
Ubugeeei Mar 4, 2024
f4768bc
chore playground
Ubugeeei Mar 4, 2024
0321374
Merge branch 'main' of github.com:vuejs/core-vapor into ubugeeei/feat…
Ubugeeei Mar 10, 2024
ffee672
test(runtime-vapor): component slots (def test cases)
Ubugeeei Mar 10, 2024
246badc
test(runtime-vapor): component slots [wip]
Ubugeeei Mar 10, 2024
60edb08
chore: add comment
Ubugeeei Mar 17, 2024
e3bfd50
Merge branch 'main' of github.com:vuejs/core-vapor into ubugeeei/feat…
Ubugeeei Mar 17, 2024
d690331
chore: merge refactoring of component
Ubugeeei Mar 17, 2024
e967ed1
chore: fix test
Ubugeeei Mar 17, 2024
fd2d9ff
feat(runtime-vapor): dynamic slots
Ubugeeei Mar 17, 2024
a5d9945
chore: fmt
Ubugeeei Mar 17, 2024
3e0d646
chore: slot playground
Ubugeeei Mar 17, 2024
ab91662
chore: remove dead comments
Ubugeeei Mar 17, 2024
0159af9
chore: remove unnecessary diffs
Ubugeeei Mar 17, 2024
1e00d6c
chore: add comments
Ubugeeei Mar 17, 2024
d83f04c
Merge branch 'main' of github.com:vuejs/core-vapor into ubugeeei/feat…
Ubugeeei Mar 18, 2024
ac237ea
Merge branch 'main' of github.com:vuejs/core-vapor into ubugeeei/feat…
Ubugeeei Mar 18, 2024
5609644
chore: renderWatch -> renderEffect
Ubugeeei Mar 18, 2024
34ca9f5
Merge branch 'main' of github.com:vuejs/core-vapor into ubugeeei/feat…
Ubugeeei Mar 18, 2024
6a7957d
chore(runtime-vapor): remove slot arg of createVaporApp
Ubugeeei Mar 19, 2024
d12c2ac
chore(runtime-vapor): provide slots to setup context
Ubugeeei Mar 19, 2024
a259039
Merge remote-tracking branch 'origin/main' into ubugeeei/feat/compone…
sxzz Mar 22, 2024
9c52df2
Merge branch 'main' of github.com:vuejs/core-vapor into ubugeeei/feat…
Ubugeeei Mar 22, 2024
463ae38
Merge branch 'ubugeeei/feat/component-slot' of github.com:vuejs/core-…
Ubugeeei Mar 22, 2024
9dfc966
refactor: tidy
sxzz Mar 22, 2024
9c122eb
refactor(runtime-vapor): CompiledSlotDescriptor -> DynamicSlot
Ubugeeei Mar 23, 2024
9479dba
refactor(runtime-vapor): renderEffect -> baseWatch
Ubugeeei Mar 23, 2024
a54617c
refactor(runtime-vapor): no export createSlots
Ubugeeei Mar 23, 2024
082f6a1
refactor(runtime-vapor): create component slots
Ubugeeei Mar 23, 2024
4d3a8e8
Merge branch 'ubugeeei/feat/component-slot' of github.com:vuejs/core-…
Ubugeeei Mar 23, 2024
cf18747
chore(runtime-vapor): component slots tests
Ubugeeei Mar 23, 2024
38af9aa
feat(runtime-vapor): set dynamic slots updation scheduler
Ubugeeei Mar 23, 2024
13cc597
refactor(runtime-vapor): dynamic slots
Ubugeeei Mar 23, 2024
f1f8b42
chore(runtime-vapor): remove dead test
Ubugeeei Mar 23, 2024
35feb3c
Merge branch 'main' of github.com:vuejs/core-vapor into ubugeeei/feat…
Ubugeeei Mar 23, 2024
e04e00a
chore: remove tracking $slots
Ubugeeei Mar 23, 2024
f6afe50
refactor: for -> extend
Ubugeeei Mar 23, 2024
a8a7e47
chore: typo (DinamicSlots -> DynamicSlots)
Ubugeeei Mar 23, 2024
08cc2bc
chore: remove dead codes
Ubugeeei Mar 23, 2024
c633534
chore(runtime-vapor): make slotsProxy optional
Ubugeeei Mar 23, 2024
5b76208
chore(runtime-vapor): make attrsProxy optional
Ubugeeei Mar 23, 2024
9510748
chore: update
sxzz Mar 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/compiler-vapor/src/generators/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { genExpression } from './expression'
import { genPropKey } from './prop'

// TODO: generate component slots
export function genCreateComponent(
oper: CreateComponentIRNode,
context: CodegenContext,
Expand Down
4 changes: 4 additions & 0 deletions packages/runtime-vapor/__tests__/componentAttrs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe('attribute fallthrough', () => {
id: () => _ctx.id,
},
],
null,
true,
)
},
Expand Down Expand Up @@ -85,6 +86,7 @@ describe('attribute fallthrough', () => {
id: () => _ctx.id,
},
],
null,
true,
)
},
Expand Down Expand Up @@ -123,6 +125,7 @@ describe('attribute fallthrough', () => {
'custom-attr': () => 'custom-attr',
},
],
null,
true,
)
return n0
Expand All @@ -144,6 +147,7 @@ describe('attribute fallthrough', () => {
id: () => _ctx.id,
},
],
null,
true,
)
},
Expand Down
1 change: 1 addition & 0 deletions packages/runtime-vapor/__tests__/componentProps.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ describe('component: props', () => {
foo: () => _ctx.foo,
id: () => _ctx.id,
},
null,
true,
)
},
Expand Down
258 changes: 258 additions & 0 deletions packages/runtime-vapor/__tests__/componentSlots.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
// NOTE: This test is implemented based on the case of `runtime-core/__test__/componentSlots.spec.ts`.

import {
createComponent,
createSlots,
createVaporApp,
defineComponent,
getCurrentInstance,
nextTick,
ref,
template,
} from '../src'
import { makeRender } from './_utils'

const define = makeRender<any>()

describe('component: slots', () => {
function renderWithSlots(slots: any): any {
let instance: any
const Comp = defineComponent({
vapor: true,
render() {
const t0 = template('<div></div>')
const n0 = t0()
instance = getCurrentInstance()
return n0
},
})

const { render } = define({
render() {
return createComponent(Comp, {}, slots)
},
})

render()
return instance
}

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

test.todo(
'initSlots: should normalize object slots (when value is null, string, array)',
() => {
// TODO: normalize
},
)

test.todo(
'initSlots: should normalize object slots (when value is function)',
() => {
// TODO: normalize
},
)

test('initSlots: instance.slots should be set correctly', () => {
let instance: any
const Comp = defineComponent({
render() {
const t0 = template('<div></div>')
const n0 = t0()
instance = getCurrentInstance()
return n0
},
})

const { render } = define({
render() {
return createComponent(
Comp,
{},
createSlots({
header: () => template('header')(),
}),
)
},
})

render()

expect(instance.slots.header()).toMatchObject(
document.createTextNode('header'),
)
})

// TODO: test case name
test('initSlots: instance.slots should be set correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)', () => {
const { slots } = renderWithSlots(
createSlots({
// TODO: normalize from array?
default: () => template('<span></span>')(),
}),
)

// expect(
// '[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.',
// ).toHaveBeenWarned()

expect(slots.default()).toMatchObject(document.createElement('span'))
})

test('updateSlots: instance.slots should be updated correctly', async () => {
const flag1 = ref(true)

let instance: any
const Child = () => {
instance = getCurrentInstance()
return template('child')()
}

const { render } = define({
render() {
return createComponent(
Child,
{},
createSlots({ _: 2 as any }, () => [
flag1.value
? { name: 'one', fn: () => template('<span></span>')() }
: { name: 'two', fn: () => template('<div></div>')() },
]),
)
},
})

render()

expect(instance.slots).toHaveProperty('one')
expect(instance.slots).not.toHaveProperty('two')

flag1.value = false
await nextTick()

expect(instance.slots).not.toHaveProperty('one')
expect(instance.slots).toHaveProperty('two')
})

test.todo(
'updateSlots: instance.slots should be updated correctly',
async () => {
const flag1 = ref(true)

let instance: any
const Child = () => {
instance = getCurrentInstance()
return template('child')()
}

const oldSlots = {
header: () => template('header')(),
footer: undefined,
}
const newSlots = {
header: undefined,
footer: () => template('footer')(),
}

const { render } = define({
setup() {
return (() => {
return createComponent(
Child,
{},
// TODO: maybe it is not supported
createSlots(flag1.value ? oldSlots : newSlots),
)
})()
},
})

render()

expect(instance.slots).toMatchObject({ _: null })

flag1.value = false
await nextTick()

expect(instance.slots).toMatchObject({ _: null })
},
)

// TODO: test case name
test('updateSlots: instance.slots should be update correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)', async () => {
const flag1 = ref(true)

let instance: any
const Child = () => {
instance = getCurrentInstance()
return template('child')()
}

const { render } = define({
setup() {
return createComponent(
Child,
{},
createSlots({}, () => [
flag1.value
? [{ name: 'header', fn: () => template('header')() }]
: [{ name: 'footer', fn: () => template('footer')() }],
]),
)
},
})
render()

expect(instance.slots).toHaveProperty('header')
flag1.value = false
await nextTick()

// expect(
// '[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.',
// ).toHaveBeenWarned()

expect(instance.slots).toHaveProperty('footer')
})

test.todo('should respect $stable flag', async () => {
// TODO: $stable flag?
})

test.todo('should not warn when mounting another app in setup', () => {
// TODO: warning
const Comp = defineComponent({
render() {
const i = getCurrentInstance()
return i!.slots.default!()
},
})
const mountComp = () => {
createVaporApp({
render() {
return createComponent(
Comp,
{},
createSlots({
default: () => template('msg')(),
}),
)!
},
})
}
const App = {
setup() {
mountComp()
},
render() {
return null!
},
}
createVaporApp(App).mount(document.createElement('div'))
expect(
'Slot "default" invoked outside of the render function',
).not.toHaveBeenWarned()
})
})
3 changes: 3 additions & 0 deletions packages/runtime-vapor/src/apiCreateComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@ import {
} from './component'
import { setupComponent } from './apiRender'
import type { RawProps } from './componentProps'
import type { Slots } from './componentSlots'
import { withAttrs } from './componentAttrs'

export function createComponent(
comp: Component,
rawProps: RawProps | null = null,
slots: Slots | null = null,
singleRoot: boolean = false,
) {
const current = currentInstance!
const instance = createComponentInstance(
comp,
singleRoot ? withAttrs(rawProps) : rawProps,
slots,
)
setupComponent(instance, singleRoot)

Expand Down
61 changes: 61 additions & 0 deletions packages/runtime-vapor/src/apiCreateSlots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// NOTE: this filed is based on `runtime-core/src/helpers/createSlots.ts`

import { isArray } from '@vue/shared'
import type { InternalSlots, Slot } from './componentSlots'
import { renderEffect } from './renderEffect'

// TODO: SSR

interface CompiledSlotDescriptor {
Ubugeeei marked this conversation as resolved.
Show resolved Hide resolved
name: string
fn: Slot
key?: string
}

export const createSlots = (
Ubugeeei marked this conversation as resolved.
Show resolved Hide resolved
slots: InternalSlots,
dynamicSlotsGetter?: () => (
| CompiledSlotDescriptor
| CompiledSlotDescriptor[]
| undefined
Ubugeeei marked this conversation as resolved.
Show resolved Hide resolved
)[],
): InternalSlots => {
const dynamicSlotKeys: Record<string, true> = {}
renderEffect(() => {
Ubugeeei marked this conversation as resolved.
Show resolved Hide resolved
const dynamicSlots = dynamicSlotsGetter?.() ?? []
for (let i = 0; i < dynamicSlots.length; i++) {
const slot = dynamicSlots[i]
// 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
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
dynamicSlotKeys[slot.name] = true
}
}
// delete stale slots
for (const key in dynamicSlotKeys) {
if (
// TODO: type (renderWatch)
Ubugeeei marked this conversation as resolved.
Show resolved Hide resolved
!dynamicSlots.some((slot: any) =>
isArray(slot) ? slot.some(s => s.name === key) : slot?.name === key,
)
) {
delete slots[key]
}
}
})
return slots
}
Loading
Loading