Skip to content

Commit 457b41a

Browse files
alex-snezhkoedison1105
authored andcommitted
fix(runtime-vapor): separate slot owner and consumer contexts
track slot owners and consumers to keep `instance.parent` accurate in slot context
1 parent 615db5e commit 457b41a

File tree

9 files changed

+186
-66
lines changed

9 files changed

+186
-66
lines changed

packages/compiler-vapor/src/generators/component.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,7 @@ import { genEventHandler } from './event'
4040
import { genDirectiveModifiers, genDirectivesForElement } from './directive'
4141
import { genBlock } from './block'
4242
import { genModelHandler } from './vModel'
43-
import {
44-
isBuiltInComponent,
45-
isKeepAliveTag,
46-
isTeleportTag,
47-
isTransitionGroupTag,
48-
} from '../utils'
43+
import { isBuiltInComponent, isTransitionGroupTag } from '../utils'
4944

5045
export function genCreateComponent(
5146
operation: CreateComponentIRNode,
@@ -467,11 +462,11 @@ function genSlotBlockWithProps(oper: SlotBlockIRNode, context: CodegenContext) {
467462

468463
if (
469464
node.type === NodeTypes.ELEMENT &&
470-
// Not a real component
471-
!isTeleportTag(node.tag) &&
472-
// Needs to determine whether to activate/deactivate based on instance.parent being KeepAlive
473-
!isKeepAliveTag(node.tag) &&
474-
// Slot updates need to trigger TransitionGroup's onBeforeUpdate/onUpdated hook
465+
// // Not a real component
466+
// !isTeleportTag(node.tag) &&
467+
// // Needs to determine whether to activate/deactivate based on instance.parent being KeepAlive
468+
// !isKeepAliveTag(node.tag) &&
469+
// // Slot updates need to trigger TransitionGroup's onBeforeUpdate/onUpdated hook
475470
!isTransitionGroupTag(node.tag)
476471
) {
477472
// wrap with withVaporCtx to ensure correct currentInstance inside slot

packages/runtime-core/src/componentCurrentInstance.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export const setCurrentInstance = (
9292
}
9393
}
9494

95-
const internalOptions = ['ce'] as const
95+
const internalOptions = ['ce', 'type'] as const
9696

9797
/**
9898
* @internal

packages/runtime-dom/src/helpers/useCssModule.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { getCurrentInstance, warn } from '@vue/runtime-core'
1+
import { useInstanceOption, warn } from '@vue/runtime-core'
22
import { EMPTY_OBJ } from '@vue/shared'
33

44
export function useCssModule(name = '$style'): Record<string, string> {
55
if (!__GLOBAL__) {
6-
const instance = getCurrentInstance()!
7-
if (!instance) {
6+
const { hasInstance, value: type } = useInstanceOption('type', true)
7+
if (!hasInstance) {
88
__DEV__ && warn(`useCssModule must be called inside setup()`)
99
return EMPTY_OBJ
1010
}
11-
const modules = instance.type.__cssModules
11+
const modules = type!.__cssModules
1212
if (!modules) {
1313
__DEV__ && warn(`Current instance does not have CSS modules injected.`)
1414
return EMPTY_OBJ

packages/runtime-vapor/__tests__/apiInject.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ import {
1212
} from '@vue/runtime-dom'
1313
import {
1414
createComponent,
15+
createSlot,
1516
createTextNode,
1617
createVaporApp,
18+
defineVaporComponent,
1719
renderEffect,
1820
} from '../src'
1921
import { makeRender } from './_utils'
@@ -368,6 +370,32 @@ describe('api: provide/inject', () => {
368370
expect(host.innerHTML).toBe('')
369371
})
370372

373+
it('should work with slots', () => {
374+
const Parent = defineVaporComponent({
375+
setup() {
376+
provide('test', 'hello')
377+
return createSlot('default', null)
378+
},
379+
})
380+
381+
const Child = defineVaporComponent({
382+
setup() {
383+
const test = inject('test')
384+
return createTextNode(toDisplayString(test))
385+
},
386+
})
387+
388+
const { host } = define({
389+
setup() {
390+
return createComponent(Parent, null, {
391+
default: () => createComponent(Child),
392+
})
393+
},
394+
}).render()
395+
396+
expect(host.innerHTML).toBe('hello<!--slot-->')
397+
})
398+
371399
describe('hasInjectionContext', () => {
372400
it('should be false outside of setup', () => {
373401
expect(hasInjectionContext()).toBe(false)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { useCssModule } from '@vue/runtime-dom'
2+
import { makeRender } from '../_utils'
3+
import { defineVaporComponent, template } from '@vue/runtime-vapor'
4+
5+
const define = makeRender<any>()
6+
7+
describe('useCssModule', () => {
8+
function mountWithModule(modules: any, name?: string) {
9+
let res
10+
define(
11+
defineVaporComponent({
12+
__cssModules: modules,
13+
setup() {
14+
res = useCssModule(name)
15+
const n0 = template('<div></div>')()
16+
return n0
17+
},
18+
}),
19+
).render()
20+
return res
21+
}
22+
23+
test('basic usage', () => {
24+
const modules = {
25+
$style: {
26+
red: 'red',
27+
},
28+
}
29+
expect(mountWithModule(modules)).toMatchObject(modules.$style)
30+
})
31+
32+
test('basic usage', () => {
33+
const modules = {
34+
foo: {
35+
red: 'red',
36+
},
37+
}
38+
expect(mountWithModule(modules, 'foo')).toMatchObject(modules.foo)
39+
})
40+
41+
test('warn out of setup usage', () => {
42+
useCssModule()
43+
expect('must be called inside setup').toHaveBeenWarned()
44+
})
45+
46+
test('warn missing injection', () => {
47+
mountWithModule(undefined)
48+
expect('instance does not have CSS modules').toHaveBeenWarned()
49+
})
50+
51+
test('warn missing injection', () => {
52+
mountWithModule({ $style: { red: 'red' } }, 'foo')
53+
expect('instance does not have CSS module named "foo"').toHaveBeenWarned()
54+
})
55+
})

packages/runtime-vapor/src/block.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -253,19 +253,25 @@ export function setScopeId(block: Block, scopeIds: string[]): void {
253253

254254
export function setComponentScopeId(instance: VaporComponentInstance): void {
255255
const parent = instance.parent
256-
if (!parent) return
256+
const slotScopeOwner = instance.slotScopeOwner
257+
if (!parent && !slotScopeOwner) return
257258
// prevent setting scopeId on multi-root fragments
258259
if (isArray(instance.block) && instance.block.length > 1) return
259260

260261
const scopeIds: string[] = []
261262

262-
const scopeId = parent.type.__scopeId
263-
if (scopeId) {
264-
scopeIds.push(scopeId)
263+
const slotScopeId = slotScopeOwner && slotScopeOwner.type.__scopeId
264+
const parentScopeId = parent && parent.type.__scopeId
265+
266+
if (slotScopeId) {
267+
scopeIds.push(slotScopeId)
268+
} else if (parentScopeId) {
269+
scopeIds.push(parentScopeId)
265270
}
266271

267272
// inherit scopeId from vdom parent
268273
if (
274+
parent &&
269275
parent.subTree &&
270276
(parent.subTree.component as any) === instance &&
271277
parent.vnode!.scopeId

packages/runtime-vapor/src/component.ts

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ import {
7171
type VaporSlot,
7272
dynamicSlotsProxyHandlers,
7373
getSlot,
74+
getSlotConsumer,
75+
getSlotScopeOwner,
7476
} from './componentSlots'
7577
import { hmrReload, hmrRerender } from './hmr'
7678
import {
@@ -178,8 +180,10 @@ export function createComponent(
178180
rawSlots?: LooseRawSlots | null,
179181
isSingleRoot?: boolean,
180182
once?: boolean,
181-
appContext: GenericAppContext = (currentInstance &&
182-
currentInstance.appContext) ||
183+
appContext: GenericAppContext = (((getSlotScopeOwner() as VaporComponentInstance | null) ||
184+
(currentInstance as VaporComponentInstance | null)) &&
185+
((getSlotScopeOwner() as VaporComponentInstance | null) ||
186+
(currentInstance as VaporComponentInstance | null))!.appContext) ||
183187
emptyContext,
184188
): VaporComponentInstance {
185189
const _insertionParent = insertionParent
@@ -191,15 +195,19 @@ export function createComponent(
191195
resetInsertionState()
192196
}
193197

198+
const parentInstance =
199+
(getSlotConsumer() as VaporComponentInstance | null) ||
200+
(currentInstance as VaporComponentInstance | null)
201+
194202
if (
195203
isSingleRoot &&
196204
component.inheritAttrs !== false &&
197-
isVaporComponent(currentInstance) &&
198-
currentInstance.hasFallthrough
205+
isVaporComponent(parentInstance) &&
206+
parentInstance.hasFallthrough
199207
) {
200208
// check if we are the single root of the parent
201209
// if yes, inject parent attrs as dynamic props source
202-
const attrs = currentInstance.attrs
210+
const attrs = parentInstance.attrs
203211
if (rawProps) {
204212
;((rawProps as RawProps).$ || ((rawProps as RawProps).$ = [])).push(
205213
() => attrs,
@@ -210,12 +218,8 @@ export function createComponent(
210218
}
211219

212220
// keep-alive
213-
if (
214-
currentInstance &&
215-
currentInstance.vapor &&
216-
isKeepAlive(currentInstance)
217-
) {
218-
const cached = (currentInstance as KeepAliveInstance).getCachedComponent(
221+
if (parentInstance && parentInstance.vapor && isKeepAlive(parentInstance)) {
222+
const cached = (parentInstance as KeepAliveInstance).getCachedComponent(
219223
component,
220224
)
221225
// @ts-expect-error
@@ -262,6 +266,7 @@ export function createComponent(
262266
rawSlots as RawSlots,
263267
appContext,
264268
once,
269+
parentInstance,
265270
)
266271

267272
// HMR
@@ -475,6 +480,9 @@ export class VaporComponentInstance implements GenericComponentInstance {
475480

476481
slots: StaticSlots
477482

483+
// slot template owner for scope inheritance
484+
slotScopeOwner: VaporComponentInstance | null
485+
478486
// to hold vnode props / slots in vdom interop mode
479487
rawPropsRef?: ShallowRef<any>
480488
rawSlotsRef?: ShallowRef<any>
@@ -541,17 +549,18 @@ export class VaporComponentInstance implements GenericComponentInstance {
541549
rawSlots?: RawSlots | null,
542550
appContext?: GenericAppContext,
543551
once?: boolean,
552+
parent: GenericComponentInstance | null = currentInstance,
544553
) {
545554
this.vapor = true
546555
this.uid = nextUid()
547556
this.type = comp
548-
this.parent = currentInstance
549-
this.root = currentInstance ? currentInstance.root : this
557+
this.parent = parent
558+
this.root = parent ? parent.root : this
550559

551-
if (currentInstance) {
552-
this.appContext = currentInstance.appContext
553-
this.provides = currentInstance.provides
554-
this.ids = currentInstance.ids
560+
if (parent) {
561+
this.appContext = parent.appContext
562+
this.provides = parent.provides
563+
this.ids = parent.ids
555564
} else {
556565
this.appContext = appContext || emptyContext
557566
this.provides = Object.create(this.appContext.provides)
@@ -600,6 +609,8 @@ export class VaporComponentInstance implements GenericComponentInstance {
600609
: rawSlots
601610
: EMPTY_OBJ
602611

612+
this.slotScopeOwner = getSlotScopeOwner() as VaporComponentInstance | null
613+
603614
// apply custom element special handling
604615
if (comp.ce) {
605616
comp.ce(this)
@@ -672,7 +683,8 @@ export function createPlainElement(
672683
;(el as any).$root = isSingleRoot
673684

674685
if (!isHydrating) {
675-
const scopeId = currentInstance!.type.__scopeId
686+
const scopeOwner = getSlotScopeOwner() || currentInstance
687+
const scopeId = scopeOwner && scopeOwner.type.__scopeId
676688
if (scopeId) setScopeId(el, [scopeId])
677689
}
678690

0 commit comments

Comments
 (0)