From: Evan You Date: Tue, 3 Dec 2024 05:21:10 +0000 (+0800) Subject: wip: attr fallthrough X-Git-Tag: v3.6.0-alpha.1~16^2~252 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f8046a3e1ae4efc9ea86e998c10726391c392de6;p=thirdparty%2Fvuejs%2Fcore.git wip: attr fallthrough --- diff --git a/packages/runtime-vapor/src/apiCreateComponentSimple.ts b/packages/runtime-vapor/src/apiCreateComponentSimple.ts index 562fdd640b..98af50c19a 100644 --- a/packages/runtime-vapor/src/apiCreateComponentSimple.ts +++ b/packages/runtime-vapor/src/apiCreateComponentSimple.ts @@ -4,17 +4,16 @@ import { pauseTracking, resetTracking, } from '@vue/reactivity' -import { - type Component, - type ComponentInternalInstance, - SetupContext, -} from './component' +import type { Component } from './component' import { NO, camelize, hasOwn, isFunction } from '@vue/shared' import { type SchedulerJob, queueJob } from '../../runtime-core/src/scheduler' import { insert } from './dom/element' import { normalizeContainer } from './apiRender' import { normalizePropsOptions, resolvePropValue } from './componentProps' import type { Block } from './block' +import { EmitFn, type EmitsOptions } from './componentEmits' +import { StaticSlots } from './componentSlots' +import { setDynamicProp } from './dom/prop' interface RawProps { [key: string]: PropSource @@ -28,11 +27,19 @@ type DynamicPropsSource = PropSource> export function createComponentSimple( component: Component, rawProps?: RawProps, -): Block { - const instance = new ComponentInstance( - component, - rawProps, - ) as any as ComponentInternalInstance + isSingleRoot?: boolean, +): ComponentInstance { + // check if we are the single root of the parent + // if yes, inject parent attrs as dynamic props source + if (isSingleRoot && currentInstance && currentInstance.hasFallthrough) { + if (rawProps) { + ;(rawProps.$ || (rawProps.$ = [])).push(currentInstance.attrs) + } else { + rawProps = { $: [currentInstance.attrs] } + } + } + + const instance = new ComponentInstance(component, rawProps) pauseTracking() let prevInstance = currentInstance @@ -41,30 +48,46 @@ export function createComponentSimple( const setupFn = isFunction(component) ? component : component.setup const setupContext = setupFn!.length > 1 ? new SetupContext(instance) : null - const node = setupFn!( + instance.block = setupFn!( instance.props, // @ts-expect-error setupContext, - ) as Block + ) as Block // TODO handle return object // single root, inherit attrs if ( - rawProps && + instance.hasFallthrough && component.inheritAttrs !== false && - node instanceof Element && + instance.block instanceof Element && Object.keys(instance.attrs).length ) { renderEffectSimple(() => { - // TODO + for (const key in instance.attrs) { + setDynamicProp(instance.block as Element, key, instance.attrs[key]) + } }) } instance.scope.off() currentInstance = prevInstance resetTracking() - // @ts-expect-error - node.__vue__ = instance - return node + return instance +} + +class SetupContext { + attrs: Record + // emit: EmitFn + // slots: Readonly + expose: (exposed?: Record) => void + + constructor(instance: ComponentInstance) { + this.attrs = instance.attrs + // this.emit = instance.emit as EmitFn + // this.slots = instance.slots + this.expose = (exposed = {}) => { + instance.exposed = exposed + } + } } let uid = 0 @@ -76,19 +99,24 @@ export class ComponentInstance { scope: EffectScope = new EffectScope(true) props: Record attrs: Record + block: Block + exposed?: Record + hasFallthrough: boolean + constructor(comp: Component, rawProps?: RawProps) { this.type = comp + this.block = null! // to be set // init props - let mayHaveFallthroughAttrs = false + this.hasFallthrough = false if (comp.props && rawProps && rawProps.$) { // has dynamic props, use proxy const handlers = getDynamicPropsHandlers(comp, this) this.props = new Proxy(rawProps, handlers[0]) this.attrs = new Proxy(rawProps, handlers[1]) - mayHaveFallthroughAttrs = true + this.hasFallthrough = true } else { - mayHaveFallthroughAttrs = initStaticProps( + this.hasFallthrough = initStaticProps( comp, rawProps, (this.props = {}), @@ -97,14 +125,14 @@ export class ComponentInstance { } // TODO validate props - - if (mayHaveFallthroughAttrs) { - // TODO apply fallthrough attrs - } // TODO init slots } } +export function isVaporComponent(value: unknown): value is ComponentInstance { + return value instanceof ComponentInstance +} + function initStaticProps( comp: Component, rawProps: RawProps | undefined, @@ -185,11 +213,12 @@ function getDynamicPropsHandlers( return castProp(resolveSource(target[key as string])) } if (target.$) { - let source, resolved - for (source of target.$) { - resolved = resolveSource(source) - if (hasOwn(resolved, key)) { - return castProp(resolved[key]) + let i = target.$.length + let source + while (i--) { + source = resolveSource(target.$[i]) + if (hasOwn(source, key)) { + return castProp(source[key]) } } } @@ -219,10 +248,9 @@ function getDynamicPropsHandlers( if (key === '$' || isProp(key)) return false if (hasOwn(target, key)) return true if (target.$) { - let source, resolved - for (source of target.$) { - resolved = resolveSource(source) - if (hasOwn(resolved, key)) { + let i = target.$.length + while (i--) { + if (hasOwn(resolveSource(target.$[i]), key)) { return true } } @@ -247,8 +275,9 @@ function getDynamicPropsHandlers( key => key !== '$' && !isProp(key), ) if (target.$) { - for (const source of target.$) { - staticKeys.push(...Object.keys(resolveSource(source))) + let i = target.$.length + while (i--) { + staticKeys.push(...Object.keys(resolveSource(target.$[i]))) } } return staticKeys diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts index b14032c925..de5a301d19 100644 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@ -1,9 +1,12 @@ import { isArray } from '@vue/shared' -import { type ComponentInternalInstance, isVaporComponent } from './component' +import { + type ComponentInstance, + isVaporComponent, +} from './apiCreateComponentSimple' export const fragmentKey: unique symbol = Symbol(__DEV__ ? `fragmentKey` : ``) -export type Block = Node | Fragment | ComponentInternalInstance | Block[] +export type Block = Node | Fragment | ComponentInstance | Block[] export type Fragment = { nodes: Block anchor?: Node @@ -27,7 +30,7 @@ export function normalizeBlock(block: Block): Node[] { } export function findFirstRootElement( - instance: ComponentInternalInstance, + instance: ComponentInstance, ): Element | undefined { const element = getFirstNode(instance.block) return element instanceof Element ? element : undefined diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index b66fb49302..859b5ed241 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -25,6 +25,7 @@ import { createAppContext, } from './apiCreateVaporApp' import type { Data } from '@vue/runtime-shared' +import type { ComponentInstance } from './apiCreateComponentSimple' export type Component = FunctionalComponent | ObjectComponent @@ -49,7 +50,7 @@ export class SetupContext { slots: Readonly expose: (exposed?: Record) => void - constructor(instance: ComponentInternalInstance) { + constructor(instance: ComponentInstance) { this.attrs = instance.attrs this.emit = instance.emit as EmitFn this.slots = instance.slots diff --git a/packages/runtime-vapor/src/dom/element.ts b/packages/runtime-vapor/src/dom/element.ts index 4aaf12dfe5..6f0fc27f89 100644 --- a/packages/runtime-vapor/src/dom/element.ts +++ b/packages/runtime-vapor/src/dom/element.ts @@ -2,6 +2,7 @@ import { isArray } from '@vue/shared' import { renderEffect } from '../renderEffect' import { setText } from './prop' import { type Block, normalizeBlock } from '../block' +import { isVaporComponent } from '../apiCreateComponentSimple' // export function insert( // block: Block, @@ -21,6 +22,8 @@ export function insert( ): void { if (block instanceof Node) { parent.insertBefore(block, anchor) + } else if (isVaporComponent(block)) { + insert(block.block, parent, anchor) } else if (isArray(block)) { for (let i = 0; i < block.length; i++) { insert(block[i], parent, anchor)