From: daiwei Date: Sun, 8 Jun 2025 08:22:30 +0000 (+0800) Subject: wip: slotScopeIds X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=9772a4c6c4ac869c5c4d47d4fdb2b2c859ddac94;p=thirdparty%2Fvuejs%2Fcore.git wip: slotScopeIds --- diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts index 7c232db754..05b16077b0 100644 --- a/packages/compiler-vapor/src/generators/component.ts +++ b/packages/compiler-vapor/src/generators/component.ts @@ -47,7 +47,7 @@ export function genCreateComponent( const { helper } = context const tag = genTag() - const { root, props, slots, once } = operation + const { root, props, slots, once, scopeId } = operation const rawSlots = genRawSlots(slots, context) const [ids, handlers] = processInlineHandlers(props, context) const rawProps = context.withId(() => genRawProps(props, context), ids) @@ -75,6 +75,7 @@ export function genCreateComponent( rawSlots, root ? 'true' : false, once && 'true', + scopeId && JSON.stringify(scopeId), ), ...genDirectivesForElement(operation.id, context), ] diff --git a/packages/compiler-vapor/src/ir/index.ts b/packages/compiler-vapor/src/ir/index.ts index 086f77ca61..e6394ad466 100644 --- a/packages/compiler-vapor/src/ir/index.ts +++ b/packages/compiler-vapor/src/ir/index.ts @@ -197,6 +197,7 @@ export interface CreateComponentIRNode extends BaseIRNode { dynamic?: SimpleExpressionNode parent?: number anchor?: number + scopeId?: string | null } export interface DeclareOldRefIRNode extends BaseIRNode { diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index dceb3fd612..fbb48d8201 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -133,6 +133,7 @@ function transformComponentElement( root: singleRoot, slots: [...context.slots], once: context.inVOnce, + scopeId: context.inSlot ? context.options.scopeId : undefined, dynamic: dynamicComponent, } context.slots = [] diff --git a/packages/runtime-core/src/apiCreateApp.ts b/packages/runtime-core/src/apiCreateApp.ts index 5bdd204cfa..531496fb74 100644 --- a/packages/runtime-core/src/apiCreateApp.ts +++ b/packages/runtime-core/src/apiCreateApp.ts @@ -188,7 +188,12 @@ export interface VaporInteropInterface { move(vnode: VNode, container: any, anchor: any): void slot(n1: VNode | null, n2: VNode, container: any, anchor: any): void - vdomMount: (component: ConcreteComponent, props?: any, slots?: any) => any + vdomMount: ( + component: ConcreteComponent, + props?: any, + slots?: any, + scopeId?: string, + ) => any vdomUnmount: UnmountComponentFn vdomSlot: ( slots: any, diff --git a/packages/runtime-vapor/__tests__/scopeId.spec.ts b/packages/runtime-vapor/__tests__/scopeId.spec.ts index 827ea501f2..48051b1497 100644 --- a/packages/runtime-vapor/__tests__/scopeId.spec.ts +++ b/packages/runtime-vapor/__tests__/scopeId.spec.ts @@ -4,6 +4,7 @@ import { createDynamicComponent, createSlot, defineVaporComponent, + forwardedSlotCreator, setInsertionState, template, vaporInteropPlugin, @@ -200,7 +201,7 @@ describe('scopeId', () => { test.todo('should attach scopeId to suspense content', async () => {}) // :slotted basic - test.todo('should work on slots', () => { + test('should work on slots', () => { const Child = defineVaporComponent({ __scopeId: 'child', setup() { @@ -227,7 +228,14 @@ describe('scopeId', () => { { default: () => { const n0 = template('
')() - const n1 = createComponent(Child2) + const n1 = createComponent( + Child2, + null, + null, + undefined, + undefined, + 'parent', + ) return [n0, n1] }, }, @@ -244,13 +252,69 @@ describe('scopeId', () => { // - scopeId from template context // - slotted scopeId from slot owner // - its own scopeId - `` + + `` + `` + ``, ) }) - test.todo(':slotted on forwarded slots', async () => {}) + test(':slotted on forwarded slots', async () => { + const Wrapper = defineVaporComponent({ + __scopeId: 'wrapper', + setup() { + //
+ const n1 = template('
', true)() as any + setInsertionState(n1) + createSlot('default', null) + return n1 + }, + }) + + const Slotted = defineVaporComponent({ + __scopeId: 'slotted', + setup() { + // + const _createForwardedSlot = forwardedSlotCreator() + const n1 = createComponent( + Wrapper, + null, + { + default: () => { + const n0 = _createForwardedSlot('default', null) + return n0 + }, + }, + true, + ) + return n1 + }, + }) + + const { html } = define({ + __scopeId: 'root', + setup() { + //
+ const n2 = createComponent( + Slotted, + null, + { + default: () => { + return template('
')() + }, + }, + true, + ) + return n2 + }, + }).render() + + expect(html()).toBe( + `
` + + `
` + + `` + + `
`, + ) + }) }) describe('vdom interop', () => { @@ -262,17 +326,16 @@ describe('vdom interop', () => { }, }) - const VdomChild = { - __scopeId: 'vdom-child', + const VdomParent = { + __scopeId: 'vdom-parent', setup() { return () => h(VaporChild as any) }, } const App = { - __scopeId: 'parent', setup() { - return () => h(VdomChild) + return () => h(VdomParent) }, } @@ -280,13 +343,13 @@ describe('vdom interop', () => { createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe( - ``, + ``, ) }) test('vdom parent > vapor > vdom child', () => { - const InnerVdomChild = { - __scopeId: 'inner-vdom-child', + const VdomChild = { + __scopeId: 'vdom-child', setup() { return () => h('button') }, @@ -295,21 +358,20 @@ describe('vdom interop', () => { const VaporChild = defineVaporComponent({ __scopeId: 'vapor-child', setup() { - return createComponent(InnerVdomChild as any, null, null, true) + return createComponent(VdomChild as any, null, null, true) }, }) - const VdomChild = { - __scopeId: 'vdom-child', + const VdomParent = { + __scopeId: 'vdom-parent', setup() { return () => h(VaporChild as any) }, } const App = { - __scopeId: 'parent', setup() { - return () => h(VdomChild) + return () => h(VdomParent) }, } @@ -317,43 +379,42 @@ describe('vdom interop', () => { createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe( - ``, + ``, ) }) test('vdom parent > vapor > vapor > vdom child', () => { - const InnerVdomChild = { - __scopeId: 'inner-vdom-child', + const VdomChild = { + __scopeId: 'vdom-child', setup() { return () => h('button') }, } - const VaporChild = defineVaporComponent({ - __scopeId: 'vapor-child', + const NestedVaporChild = defineVaporComponent({ + __scopeId: 'nested-vapor-child', setup() { - return createComponent(InnerVdomChild as any, null, null, true) + return createComponent(VdomChild as any, null, null, true) }, }) - const VaporChild2 = defineVaporComponent({ - __scopeId: 'vapor-child2', + const VaporChild = defineVaporComponent({ + __scopeId: 'vapor-child', setup() { - return createComponent(VaporChild as any, null, null, true) + return createComponent(NestedVaporChild as any, null, null, true) }, }) - const VdomChild = { - __scopeId: 'vdom-child', + const VdomParent = { + __scopeId: 'vdom-parent', setup() { - return () => h(VaporChild2 as any) + return () => h(VaporChild as any) }, } const App = { - __scopeId: 'parent', setup() { - return () => h(VdomChild) + return () => h(VdomParent) }, } @@ -361,7 +422,7 @@ describe('vdom interop', () => { createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe( - ``, + ``, ) }) @@ -373,17 +434,16 @@ describe('vdom interop', () => { }, }) - const VdomChild = { - __scopeId: 'vdom-child', + const VdomParent = { + __scopeId: 'vdom-parent', setup() { return () => h(VaporChild as any) }, } const App = { - __scopeId: 'parent', setup() { - return () => h(VdomChild) + return () => h(VdomParent) }, } @@ -391,7 +451,7 @@ describe('vdom interop', () => { createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe( - ``, + ``, ) }) @@ -403,17 +463,16 @@ describe('vdom interop', () => { }, } - const VaporChild = defineVaporComponent({ - __scopeId: 'vapor-child', + const VaporParent = defineVaporComponent({ + __scopeId: 'vapor-parent', setup() { return createComponent(VdomChild as any, null, null, true) }, }) const App = { - __scopeId: 'parent', setup() { - return () => h(VaporChild as any) + return () => h(VaporParent as any) }, } @@ -421,36 +480,35 @@ describe('vdom interop', () => { createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe( - ``, + ``, ) }) test('vapor parent > vdom > vapor child', () => { - const InnerVaporChild = defineVaporComponent({ - __scopeId: 'inner-vapor-child', + const VaporChild = defineVaporComponent({ + __scopeId: 'vapor-child', setup() { - return template('', true)() + return template('', true)() }, }) const VdomChild = { __scopeId: 'vdom-child', setup() { - return () => h(InnerVaporChild as any) + return () => h(VaporChild as any) }, } - const VaporChild = defineVaporComponent({ - __scopeId: 'vapor-child', + const VaporParent = defineVaporComponent({ + __scopeId: 'vapor-parent', setup() { return createComponent(VdomChild as any, null, null, true) }, }) const App = { - __scopeId: 'parent', setup() { - return () => h(VaporChild as any) + return () => h(VaporParent as any) }, } @@ -458,43 +516,100 @@ describe('vdom interop', () => { createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe( - ``, + ``, ) }) test('vapor parent > vdom > vdom > vapor child', () => { - const InnerVaporChild = defineVaporComponent({ - __scopeId: 'inner-vapor-child', + const VaporChild = defineVaporComponent({ + __scopeId: 'vapor-child', setup() { - return template('', true)() + return template('', true)() }, }) const VdomChild = { __scopeId: 'vdom-child', setup() { - return () => h(InnerVaporChild as any) + return () => h(VaporChild as any) }, } - const VdomChild2 = { - __scopeId: 'vdom-child2', + const VdomParent = { + __scopeId: 'vdom-parent', setup() { return () => h(VdomChild as any) }, } - const VaporChild = defineVaporComponent({ - __scopeId: 'vapor-child', + const VaporParent = defineVaporComponent({ + __scopeId: 'vapor-parent', setup() { - return createComponent(VdomChild2 as any, null, null, true) + return createComponent(VdomParent as any, null, null, true) }, }) const App = { - __scopeId: 'parent', setup() { - return () => h(VaporChild as any) + return () => h(VaporParent as any) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + + expect(root.innerHTML).toBe( + ``, + ) + }) + + test('vapor parent > vapor slot > vdom child', () => { + const VaporSlot = defineVaporComponent({ + __scopeId: 'vapor-slot', + setup() { + const n1 = template('
', true)() as any + setInsertionState(n1) + createSlot('default', null) + return n1 + }, + }) + + const VdomChild = { + __scopeId: 'vdom-child', + setup() { + return () => h('span') + }, + } + + const VaporParent = defineVaporComponent({ + __scopeId: 'vapor-parent', + setup() { + const n2 = createComponent( + VaporSlot, + null, + { + default: () => { + const n0 = template('
')() + const n1 = createComponent( + VdomChild, + undefined, + undefined, + undefined, + undefined, + 'vapor-parent', + ) + return [n0, n1] + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => h(VaporParent as any) }, } @@ -502,7 +617,11 @@ describe('vdom interop', () => { createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe( - ``, + `
` + + `
` + + `` + + `` + + `
`, ) }) }) diff --git a/packages/runtime-vapor/src/apiCreateApp.ts b/packages/runtime-vapor/src/apiCreateApp.ts index 834437ee35..1a9ed06a73 100644 --- a/packages/runtime-vapor/src/apiCreateApp.ts +++ b/packages/runtime-vapor/src/apiCreateApp.ts @@ -41,6 +41,8 @@ const mountApp: AppMountFn = (app, container) => { app._props as RawProps, null, false, + false, + undefined, app._context, ) mountComponent(instance, container) @@ -61,6 +63,8 @@ const hydrateApp: AppMountFn = (app, container) => { app._props as RawProps, null, false, + false, + undefined, app._context, ) mountComponent(instance, container) diff --git a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts index 33697b4ca7..e436f77e29 100644 --- a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts +++ b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts @@ -12,6 +12,8 @@ export function createDynamicComponent( rawProps?: RawProps | null, rawSlots?: RawSlots | null, isSingleRoot?: boolean, + once?: boolean, + scopeId?: string, ): VaporFragment { const _insertionParent = insertionParent const _insertionAnchor = insertionAnchor @@ -28,6 +30,8 @@ export function createDynamicComponent( rawProps, rawSlots, isSingleRoot, + once, + scopeId, ), value, ) diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts index a67e43543e..a5a63807ee 100644 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@ -35,6 +35,11 @@ export class DynamicFragment extends VaporFragment { scope: EffectScope | undefined current?: BlockFn fallback?: BlockFn + /** + * slot only + * indicates forwarded slot + */ + forwarded?: boolean constructor(anchorLabel?: string) { super([]) @@ -206,7 +211,6 @@ export function setScopeId(block: Block, scopeId: string): void { export function setComponentScopeId(instance: VaporComponentInstance): void { const parent = instance.parent if (!parent) return - if (isArray(instance.block) && instance.block.length > 1) return const scopeId = parent.type.__scopeId diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index ea01450a11..4708db6188 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -145,6 +145,8 @@ export function createComponent( rawProps?: LooseRawProps | null, rawSlots?: LooseRawSlots | null, isSingleRoot?: boolean, + once?: boolean, // TODO once support + scopeId?: string, appContext: GenericAppContext = (currentInstance && currentInstance.appContext) || emptyContext, @@ -163,6 +165,7 @@ export function createComponent( component as any, rawProps, rawSlots, + scopeId, ) if (!isHydrating && _insertionParent) { insert(frag, _insertionParent, _insertionAnchor) @@ -282,6 +285,8 @@ export function createComponent( onScopeDispose(() => unmountComponent(instance), true) + if (scopeId) setScopeId(instance.block, scopeId) + if (!isHydrating && _insertionParent) { mountComponent(instance, _insertionParent, _insertionAnchor) } @@ -477,16 +482,25 @@ export function createComponentWithFallback( rawProps?: LooseRawProps | null, rawSlots?: LooseRawSlots | null, isSingleRoot?: boolean, + once?: boolean, + scopeId?: string, ): HTMLElement | VaporComponentInstance { if (!isString(comp)) { - return createComponent(comp, rawProps, rawSlots, isSingleRoot) + return createComponent( + comp, + rawProps, + rawSlots, + isSingleRoot, + once, + scopeId, + ) } const el = document.createElement(comp) // mark single root ;(el as any).$root = isSingleRoot - const scopeId = currentInstance!.type.__scopeId + scopeId = scopeId || currentInstance!.type.__scopeId if (scopeId) setScopeId(el, scopeId) if (rawProps) { diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts index 19e9b5b6d1..32dd235a0e 100644 --- a/packages/runtime-vapor/src/componentSlots.ts +++ b/packages/runtime-vapor/src/componentSlots.ts @@ -1,5 +1,11 @@ import { EMPTY_OBJ, NO, hasOwn, isArray, isFunction } from '@vue/shared' -import { type Block, type BlockFn, DynamicFragment, insert } from './block' +import { + type Block, + type BlockFn, + DynamicFragment, + insert, + setScopeId, +} from './block' import { rawPropsProxyHandlers } from './componentProps' import { currentInstance, isRef } from '@vue/runtime-dom' import type { LooseRawProps, VaporComponentInstance } from './component' @@ -156,9 +162,27 @@ export function createSlot( } } + if (i) fragment.forwarded = true + if (i || !hasForwardedSlot(fragment.nodes)) { + const scopeId = instance!.type.__scopeId + if (scopeId) setScopeId(fragment, `${scopeId}-s`) + } + if (!isHydrating && _insertionParent) { insert(fragment, _insertionParent, _insertionAnchor) } return fragment } + +function isForwardedSlot(block: Block): block is DynamicFragment { + return block instanceof DynamicFragment && !!block.forwarded +} + +function hasForwardedSlot(block: Block): block is DynamicFragment { + if (isArray(block)) { + return block.some(isForwardedSlot) + } else { + return isForwardedSlot(block) + } +} diff --git a/packages/runtime-vapor/src/vdomInterop.ts b/packages/runtime-vapor/src/vdomInterop.ts index e4d3c8de3b..af86b291b2 100644 --- a/packages/runtime-vapor/src/vdomInterop.ts +++ b/packages/runtime-vapor/src/vdomInterop.ts @@ -155,6 +155,7 @@ function createVDOMComponent( component: ConcreteComponent, rawProps?: LooseRawProps | null, rawSlots?: LooseRawSlots | null, + scopeId?: string, ): VaporFragment { const frag = new VaporFragment([]) const vnode = createVNode( @@ -183,7 +184,7 @@ function createVDOMComponent( internals.umt(vnode.component!, null, !!parentNode) } - vnode.scopeId = parentInstance.type.__scopeId! + vnode.scopeId = scopeId || parentInstance.type.__scopeId! frag.insert = (parentNode, anchor) => { if (!isMounted) {