]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
chore: Merge branch 'edison/feat/fowardedSlots' into edison/feat/setScopeId
authordaiwei <daiwei521@126.com>
Fri, 20 Jun 2025 07:24:11 +0000 (15:24 +0800)
committerdaiwei <daiwei521@126.com>
Fri, 20 Jun 2025 07:24:11 +0000 (15:24 +0800)
1  2 
packages/compiler-vapor/src/generators/component.ts
packages/compiler-vapor/src/ir/index.ts
packages/compiler-vapor/src/transforms/transformElement.ts
packages/runtime-core/src/index.ts
packages/runtime-core/src/renderer.ts
packages/runtime-vapor/__tests__/scopeId.spec.ts
packages/runtime-vapor/src/apiCreateDynamicComponent.ts
packages/runtime-vapor/src/block.ts
packages/runtime-vapor/src/component.ts
packages/runtime-vapor/src/componentSlots.ts
packages/runtime-vapor/src/vdomInterop.ts

index fbb48d8201824dafa2d8888ebd2526de501beea6,05153e729aff475dd609bc8bb5bedfe3f1cde0d3..c089037fcd69d75888afb35c2b88d2382779a93c
@@@ -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 = []
Simple merge
Simple merge
index 48051b1497f0d7efb1638d4a59dc6e5d6bf7bccf,0000000000000000000000000000000000000000..a4e878f832923e43657aabd1c86524cc018f977b
mode 100644,000000..100644
--- /dev/null
@@@ -1,627 -1,0 +1,627 @@@
-         `<span child2="" parent="" child="" child-s=""></span>` +
 +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('<div child></div>', true)()
 +      },
 +    })
 +
 +    const { html } = define({
 +      __scopeId: 'parent',
 +      setup() {
 +        return createComponent(Child)
 +      },
 +    }).render()
 +    expect(html()).toBe(`<div child="" parent=""></div>`)
 +  })
 +
 +  test('should attach scopeId to child component with insertion state', () => {
 +    const Child = defineVaporComponent({
 +      __scopeId: 'child',
 +      setup() {
 +        return template('<div child></div>', true)()
 +      },
 +    })
 +
 +    const { html } = define({
 +      __scopeId: 'parent',
 +      setup() {
 +        const t0 = template('<div parent></div>', true)
 +        const n1 = t0() as any
 +        setInsertionState(n1)
 +        createComponent(Child)
 +        return n1
 +      },
 +    }).render()
 +    expect(html()).toBe(`<div parent=""><div child="" parent=""></div></div>`)
 +  })
 +
 +  test('should attach scopeId to nested child component', () => {
 +    const Child = defineVaporComponent({
 +      __scopeId: 'child',
 +      setup() {
 +        return template('<div child></div>', true)()
 +      },
 +    })
 +
 +    const Parent = defineVaporComponent({
 +      __scopeId: 'parent',
 +      setup() {
 +        return createComponent(Child)
 +      },
 +    })
 +
 +    const { html } = define({
 +      __scopeId: 'app',
 +      setup() {
 +        return createComponent(Parent)
 +      },
 +    }).render()
 +    expect(html()).toBe(`<div child="" parent="" app=""></div>`)
 +  })
 +
 +  test('should not attach scopeId to nested multiple root components', () => {
 +    const Child = defineVaporComponent({
 +      __scopeId: 'child',
 +      setup() {
 +        return template('<div child></div>', true)()
 +      },
 +    })
 +
 +    const Parent = defineVaporComponent({
 +      __scopeId: 'parent',
 +      setup() {
 +        const n0 = template('<div parent></div>')()
 +        const n1 = createComponent(Child)
 +        return [n0, n1]
 +      },
 +    })
 +
 +    const { html } = define({
 +      __scopeId: 'app',
 +      setup() {
 +        return createComponent(Parent)
 +      },
 +    }).render()
 +    expect(html()).toBe(`<div parent=""></div><div child="" parent=""></div>`)
 +  })
 +
 +  test('should attach scopeId to nested child component with insertion state', () => {
 +    const Child = defineVaporComponent({
 +      __scopeId: 'child',
 +      setup() {
 +        return template('<div child></div>', true)()
 +      },
 +    })
 +
 +    const Parent = defineVaporComponent({
 +      __scopeId: 'parent',
 +      setup() {
 +        return createComponent(Child)
 +      },
 +    })
 +
 +    const { html } = define({
 +      __scopeId: 'app',
 +      setup() {
 +        const t0 = template('<div app></div>', true)
 +        const n1 = t0() as any
 +        setInsertionState(n1)
 +        createComponent(Parent)
 +        return n1
 +      },
 +    }).render()
 +    expect(html()).toBe(
 +      `<div app=""><div child="" parent="" app=""></div></div>`,
 +    )
 +  })
 +
 +  test('should attach scopeId to dynamic component', () => {
 +    const { html } = define({
 +      __scopeId: 'parent',
 +      setup() {
 +        return createDynamicComponent(() => 'button')
 +      },
 +    }).render()
 +    expect(html()).toBe(`<button parent=""></button><!--dynamic-component-->`)
 +  })
 +
 +  test('should attach scopeId to dynamic component with insertion state', () => {
 +    const { html } = define({
 +      __scopeId: 'parent',
 +      setup() {
 +        const t0 = template('<div parent></div>', true)
 +        const n1 = t0() as any
 +        setInsertionState(n1)
 +        createDynamicComponent(() => 'button')
 +        return n1
 +      },
 +    }).render()
 +    expect(html()).toBe(
 +      `<div parent=""><button parent=""></button><!--dynamic-component--></div>`,
 +    )
 +  })
 +
 +  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(
 +      `<button child="" parent=""></button><!--dynamic-component-->`,
 +    )
 +  })
 +
 +  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('<div parent></div>', true)
 +        const n1 = t0() as any
 +        setInsertionState(n1)
 +        createComponent(Comp, null, null, true)
 +        return n1
 +      },
 +    }).render()
 +    expect(html()).toBe(
 +      `<div parent=""><button child="" parent=""></button><!--dynamic-component--></div>`,
 +    )
 +  })
 +
 +  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('<div child></div>', true)() as any
 +        setInsertionState(n1)
 +        createSlot('default', null)
 +        return n1
 +      },
 +    })
 +
 +    const Child2 = defineVaporComponent({
 +      __scopeId: 'child2',
 +      setup() {
 +        return template('<span child2></span>', true)()
 +      },
 +    })
 +
 +    const { html } = define({
 +      __scopeId: 'parent',
 +      setup() {
 +        const n2 = createComponent(
 +          Child,
 +          null,
 +          {
 +            default: () => {
 +              const n0 = template('<div parent></div>')()
 +              const n1 = createComponent(
 +                Child2,
 +                null,
 +                null,
 +                undefined,
 +                undefined,
 +                'parent',
 +              )
 +              return [n0, n1]
 +            },
 +          },
 +          true,
 +        )
 +        return n2
 +      },
 +    }).render()
 +
 +    expect(html()).toBe(
 +      `<div child="" parent="">` +
 +        `<div parent="" child-s=""></div>` +
 +        // component inside slot should have:
 +        // - scopeId from template context
 +        // - slotted scopeId from slot owner
 +        // - its own scopeId
-   test('vapor parent > vapor slot > vdom child', () => {
++        `<span child2="" parent="" child-s="" child=""></span>` +
 +        `<!--slot-->` +
 +        `</div>`,
 +    )
 +  })
 +
 +  test(':slotted on forwarded slots', async () => {
 +    const Wrapper = defineVaporComponent({
 +      __scopeId: 'wrapper',
 +      setup() {
 +        // <div><slot/></div>
 +        const n1 = template('<div wrapper></div>', true)() as any
 +        setInsertionState(n1)
 +        createSlot('default', null)
 +        return n1
 +      },
 +    })
 +
 +    const Slotted = defineVaporComponent({
 +      __scopeId: 'slotted',
 +      setup() {
 +        // <Wrapper><slot/></Wrapper>
 +        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() {
 +        // <Slotted><div></div></Slotted>
 +        const n2 = createComponent(
 +          Slotted,
 +          null,
 +          {
 +            default: () => {
 +              return template('<div root></div>')()
 +            },
 +          },
 +          true,
 +        )
 +        return n2
 +      },
 +    }).render()
 +
 +    expect(html()).toBe(
 +      `<div wrapper="" slotted="" root="">` +
 +        `<div root="" slotted-s=""></div>` +
 +        `<!--slot--><!--slot-->` +
 +        `</div>`,
 +    )
 +  })
 +})
 +
 +describe('vdom interop', () => {
 +  test('vdom parent > vapor child', () => {
 +    const VaporChild = defineVaporComponent({
 +      __scopeId: 'vapor-child',
 +      setup() {
 +        return template('<button vapor-child></button>', 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(
 +      `<button vapor-child="" vdom-parent=""></button>`,
 +    )
 +  })
 +
 +  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(
 +      `<button vdom-child="" vapor-child="" vdom-parent=""></button>`,
 +    )
 +  })
 +
 +  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(
 +      `<button vdom-child="" nested-vapor-child="" vapor-child="" vdom-parent=""></button>`,
 +    )
 +  })
 +
 +  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(
 +      `<button vapor-child="" vdom-parent=""></button><!--dynamic-component-->`,
 +    )
 +  })
 +
 +  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(
 +      `<button vdom-child="" vapor-parent=""></button>`,
 +    )
 +  })
 +
 +  test('vapor parent > vdom > vapor child', () => {
 +    const VaporChild = defineVaporComponent({
 +      __scopeId: 'vapor-child',
 +      setup() {
 +        return template('<button vapor-child></button>', 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(
 +      `<button vapor-child="" vdom-child="" vapor-parent=""></button>`,
 +    )
 +  })
 +
 +  test('vapor parent > vdom > vdom > vapor child', () => {
 +    const VaporChild = defineVaporComponent({
 +      __scopeId: 'vapor-child',
 +      setup() {
 +        return template('<button vapor-child></button>', 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(
 +      `<button vapor-child="" vdom-child="" vdom-parent="" vapor-parent=""></button>`,
 +    )
 +  })
 +
++  test.todo('vapor parent > vapor slot > vdom child', () => {
 +    const VaporSlot = defineVaporComponent({
 +      __scopeId: 'vapor-slot',
 +      setup() {
 +        const n1 = template('<div vapor-slot></div>', 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('<div vapor-parent></div>')()
 +              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(
 +      `<div vapor-slot="" vapor-parent="">` +
 +        `<div vapor-parent="" vapor-slot-s=""></div>` +
 +        `<span vdom-child="" vapor-parent="" vapor-slot-s=""></span>` +
 +        `<!--slot-->` +
 +        `</div>`,
 +    )
 +  })
 +})
Simple merge
index 4708db618858e889d720c99182142dbc14ffb93b,af15133dbe54f7510deb7abc2bf85e88f76f6250..6e59c0bdef5c4801fa6246a8cdeffe37a5070b20
@@@ -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
index 32dd235a0e769ea0f50693475ed9f4d5a04967cf,5ccbe6be89c8249683c98e40c863f5cbf4825304..020c729fa30b95a19175972744c7e54d0abd3c42
@@@ -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
+   }
+ }