From: daiwei Date: Fri, 20 Jun 2025 07:24:11 +0000 (+0800) Subject: chore: Merge branch 'edison/feat/fowardedSlots' into edison/feat/setScopeId X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fa752c4a30a38cf10bd880ad8ffe952c374f1ef6;p=thirdparty%2Fvuejs%2Fcore.git chore: Merge branch 'edison/feat/fowardedSlots' into edison/feat/setScopeId --- fa752c4a30a38cf10bd880ad8ffe952c374f1ef6 diff --cc packages/compiler-vapor/src/transforms/transformElement.ts index fbb48d8201,05153e729a..c089037fcd --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@@ -130,10 -150,9 +150,10 @@@ function transformComponentElement tag, props: propsResult[0] ? propsResult[1] : [propsResult[1]], asset, - root: singleRoot, + root: singleRoot && !context.inVFor, slots: [...context.slots], once: context.inVOnce, + scopeId: context.inSlot ? context.options.scopeId : undefined, dynamic: dynamicComponent, } context.slots = [] diff --cc packages/runtime-vapor/__tests__/scopeId.spec.ts index 48051b1497,0000000000..a4e878f832 mode 100644,000000..100644 --- a/packages/runtime-vapor/__tests__/scopeId.spec.ts +++ b/packages/runtime-vapor/__tests__/scopeId.spec.ts @@@ -1,627 -1,0 +1,627 @@@ +import { createApp, h } from '@vue/runtime-dom' +import { + createComponent, + createDynamicComponent, + createSlot, + defineVaporComponent, + forwardedSlotCreator, + setInsertionState, + template, + vaporInteropPlugin, +} from '../src' +import { makeRender } from './_utils' + +const define = makeRender() + +describe('scopeId', () => { + test('should attach scopeId to child component', () => { + const Child = defineVaporComponent({ + __scopeId: 'child', + setup() { + return template('
', true)() + }, + }) + + const { html } = define({ + __scopeId: 'parent', + setup() { + return createComponent(Child) + }, + }).render() + expect(html()).toBe(`
`) + }) + + test('should attach scopeId to child component with insertion state', () => { + const Child = defineVaporComponent({ + __scopeId: 'child', + setup() { + return template('
', true)() + }, + }) + + const { html } = define({ + __scopeId: 'parent', + setup() { + const t0 = template('
', true) + const n1 = t0() as any + setInsertionState(n1) + createComponent(Child) + return n1 + }, + }).render() + expect(html()).toBe(`
`) + }) + + test('should attach scopeId to nested child component', () => { + const Child = defineVaporComponent({ + __scopeId: 'child', + setup() { + return template('
', true)() + }, + }) + + const Parent = defineVaporComponent({ + __scopeId: 'parent', + setup() { + return createComponent(Child) + }, + }) + + const { html } = define({ + __scopeId: 'app', + setup() { + return createComponent(Parent) + }, + }).render() + expect(html()).toBe(`
`) + }) + + test('should not attach scopeId to nested multiple root components', () => { + const Child = defineVaporComponent({ + __scopeId: 'child', + setup() { + return template('
', true)() + }, + }) + + const Parent = defineVaporComponent({ + __scopeId: 'parent', + setup() { + const n0 = template('
')() + const n1 = createComponent(Child) + return [n0, n1] + }, + }) + + const { html } = define({ + __scopeId: 'app', + setup() { + return createComponent(Parent) + }, + }).render() + expect(html()).toBe(`
`) + }) + + test('should attach scopeId to nested child component with insertion state', () => { + const Child = defineVaporComponent({ + __scopeId: 'child', + setup() { + return template('
', true)() + }, + }) + + const Parent = defineVaporComponent({ + __scopeId: 'parent', + setup() { + return createComponent(Child) + }, + }) + + const { html } = define({ + __scopeId: 'app', + setup() { + const t0 = template('
', true) + const n1 = t0() as any + setInsertionState(n1) + createComponent(Parent) + return n1 + }, + }).render() + expect(html()).toBe( + `
`, + ) + }) + + test('should attach scopeId to dynamic component', () => { + const { html } = define({ + __scopeId: 'parent', + setup() { + return createDynamicComponent(() => 'button') + }, + }).render() + expect(html()).toBe(``) + }) + + test('should attach scopeId to dynamic component with insertion state', () => { + const { html } = define({ + __scopeId: 'parent', + setup() { + const t0 = template('
', true) + const n1 = t0() as any + setInsertionState(n1) + createDynamicComponent(() => 'button') + return n1 + }, + }).render() + expect(html()).toBe( + `
`, + ) + }) + + test('should attach scopeId to nested dynamic component', () => { + const Comp = defineVaporComponent({ + __scopeId: 'child', + setup() { + return createDynamicComponent(() => 'button', null, null, true) + }, + }) + const { html } = define({ + __scopeId: 'parent', + setup() { + return createComponent(Comp, null, null, true) + }, + }).render() + expect(html()).toBe( + ``, + ) + }) + + test('should attach scopeId to nested dynamic component with insertion state', () => { + const Comp = defineVaporComponent({ + __scopeId: 'child', + setup() { + return createDynamicComponent(() => 'button', null, null, true) + }, + }) + const { html } = define({ + __scopeId: 'parent', + setup() { + const t0 = template('
', true) + const n1 = t0() as any + setInsertionState(n1) + createComponent(Comp, null, null, true) + return n1 + }, + }).render() + expect(html()).toBe( + `
`, + ) + }) + + test.todo('should attach scopeId to suspense content', async () => {}) + + // :slotted basic + test('should work on slots', () => { + const Child = defineVaporComponent({ + __scopeId: 'child', + setup() { + const n1 = template('
', true)() as any + setInsertionState(n1) + createSlot('default', null) + return n1 + }, + }) + + const Child2 = defineVaporComponent({ + __scopeId: 'child2', + setup() { + return template('', true)() + }, + }) + + const { html } = define({ + __scopeId: 'parent', + setup() { + const n2 = createComponent( + Child, + null, + { + default: () => { + const n0 = template('
')() + const n1 = createComponent( + Child2, + null, + null, + undefined, + undefined, + 'parent', + ) + return [n0, n1] + }, + }, + true, + ) + return n2 + }, + }).render() + + expect(html()).toBe( + `
` + + `
` + + // component inside slot should have: + // - scopeId from template context + // - slotted scopeId from slot owner + // - its own scopeId - `` + ++ `` + + `` + + `
`, + ) + }) + + 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', () => { + test('vdom parent > vapor child', () => { + const VaporChild = defineVaporComponent({ + __scopeId: 'vapor-child', + setup() { + return template('', true)() + }, + }) + + const VdomParent = { + __scopeId: 'vdom-parent', + setup() { + return () => h(VaporChild as any) + }, + } + + const App = { + setup() { + return () => h(VdomParent) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + + expect(root.innerHTML).toBe( + ``, + ) + }) + + test('vdom parent > vapor > vdom child', () => { + const VdomChild = { + __scopeId: 'vdom-child', + setup() { + return () => h('button') + }, + } + + const VaporChild = defineVaporComponent({ + __scopeId: 'vapor-child', + setup() { + return createComponent(VdomChild as any, null, null, true) + }, + }) + + const VdomParent = { + __scopeId: 'vdom-parent', + setup() { + return () => h(VaporChild as any) + }, + } + + const App = { + setup() { + return () => h(VdomParent) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + + expect(root.innerHTML).toBe( + ``, + ) + }) + + test('vdom parent > vapor > vapor > vdom child', () => { + const VdomChild = { + __scopeId: 'vdom-child', + setup() { + return () => h('button') + }, + } + + const NestedVaporChild = defineVaporComponent({ + __scopeId: 'nested-vapor-child', + setup() { + return createComponent(VdomChild as any, null, null, true) + }, + }) + + const VaporChild = defineVaporComponent({ + __scopeId: 'vapor-child', + setup() { + return createComponent(NestedVaporChild as any, null, null, true) + }, + }) + + const VdomParent = { + __scopeId: 'vdom-parent', + setup() { + return () => h(VaporChild as any) + }, + } + + const App = { + setup() { + return () => h(VdomParent) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + + expect(root.innerHTML).toBe( + ``, + ) + }) + + test('vdom parent > vapor dynamic child', () => { + const VaporChild = defineVaporComponent({ + __scopeId: 'vapor-child', + setup() { + return createDynamicComponent(() => 'button', null, null, true) + }, + }) + + const VdomParent = { + __scopeId: 'vdom-parent', + setup() { + return () => h(VaporChild as any) + }, + } + + const App = { + setup() { + return () => h(VdomParent) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + + expect(root.innerHTML).toBe( + ``, + ) + }) + + test('vapor parent > vdom child', () => { + const VdomChild = { + __scopeId: 'vdom-child', + setup() { + return () => h('button') + }, + } + + const VaporParent = defineVaporComponent({ + __scopeId: 'vapor-parent', + setup() { + return createComponent(VdomChild as any, null, null, true) + }, + }) + + const App = { + setup() { + return () => h(VaporParent as any) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + + expect(root.innerHTML).toBe( + ``, + ) + }) + + test('vapor parent > vdom > vapor child', () => { + const VaporChild = defineVaporComponent({ + __scopeId: 'vapor-child', + setup() { + return template('', true)() + }, + }) + + const VdomChild = { + __scopeId: 'vdom-child', + setup() { + return () => h(VaporChild as any) + }, + } + + const VaporParent = defineVaporComponent({ + __scopeId: 'vapor-parent', + setup() { + return createComponent(VdomChild as any, null, null, true) + }, + }) + + const App = { + setup() { + return () => h(VaporParent as any) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + + expect(root.innerHTML).toBe( + ``, + ) + }) + + test('vapor parent > vdom > vdom > vapor child', () => { + const VaporChild = defineVaporComponent({ + __scopeId: 'vapor-child', + setup() { + return template('', true)() + }, + }) + + const VdomChild = { + __scopeId: 'vdom-child', + setup() { + return () => h(VaporChild as any) + }, + } + + const VdomParent = { + __scopeId: 'vdom-parent', + setup() { + return () => h(VdomChild as any) + }, + } + + const VaporParent = defineVaporComponent({ + __scopeId: 'vapor-parent', + setup() { + return createComponent(VdomParent as any, null, null, true) + }, + }) + + const App = { + setup() { + 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', () => { ++ test.todo('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) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + + expect(root.innerHTML).toBe( + `
` + + `
` + + `` + + `` + + `
`, + ) + }) +}) diff --cc packages/runtime-vapor/src/component.ts index 4708db6188,af15133dbe..6e59c0bdef --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@@ -25,14 -25,7 +25,15 @@@ import unregisterHMR, warn, } from '@vue/runtime-dom' -import { type Block, DynamicFragment, insert, isBlock, remove } from './block' +import { + type Block, ++ DynamicFragment, + insert, + isBlock, + remove, + setComponentScopeId, + setScopeId, +} from './block' import { type ShallowRef, markRaw, @@@ -482,20 -473,19 +485,28 @@@ export function createComponentWithFall 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 _insertionParent = insertionParent + const _insertionAnchor = insertionAnchor + if (isHydrating) { + locateHydrationNode() + } else { + resetInsertionState() + } + const el = document.createElement(comp) // mark single root ;(el as any).$root = isSingleRoot diff --cc packages/runtime-vapor/src/componentSlots.ts index 32dd235a0e,5ccbe6be89..020c729fa3 --- a/packages/runtime-vapor/src/componentSlots.ts +++ b/packages/runtime-vapor/src/componentSlots.ts @@@ -3,8 -3,9 +3,10 @@@ import type Block, type BlockFn, DynamicFragment, + type VaporFragment, insert, + isFragment, + setScopeId, } from './block' import { rawPropsProxyHandlers } from './componentProps' import { currentInstance, isRef } from '@vue/runtime-dom' @@@ -175,14 -195,11 +202,23 @@@ export function createSlot 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) + } +} ++ + function ensureVaporSlotFallback( + block: VaporFragment, + fallback?: VaporSlot, + ): void { + if (block.insert && !block.fallback && fallback) { + block.fallback = fallback + } + }