From: Evan You Date: Tue, 3 Dec 2024 08:48:28 +0000 (+0800) Subject: refactor: reuse props logic from core X-Git-Tag: v3.6.0-alpha.1~16^2~251 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=783d8b4d0da5fa4a21e9cd3fb03a6c596d31808a;p=thirdparty%2Fvuejs%2Fcore.git refactor: reuse props logic from core --- diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index a6e97aaa10..c877ebd4b9 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -470,7 +470,7 @@ export interface ComponentInternalInstance { * avoid unnecessary watcher trigger * @internal */ - propsDefaults: Data + propsDefaults: Data | null /** * setup related * @internal @@ -647,7 +647,7 @@ export function createComponentInstance( emitted: null, // props default value - propsDefaults: EMPTY_OBJ, + propsDefaults: null, // inheritAttrs inheritAttrs: type.inheritAttrs, diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index bd5e4d9d07..85619244a9 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -282,11 +282,10 @@ export function updateProps( const camelizedKey = camelize(key) props[camelizedKey] = resolvePropValue( options, - rawCurrentProps, camelizedKey, value, instance, - false /* isAbsent */, + baseResolveDefault, ) } } else { @@ -331,10 +330,10 @@ export function updateProps( ) { props[key] = resolvePropValue( options, - rawCurrentProps, key, undefined, instance, + baseResolveDefault, true /* isAbsent */, ) } @@ -428,16 +427,15 @@ function setFullProps( } if (needCastKeys) { - const rawCurrentProps = toRaw(props) const castValues = rawCastValues || EMPTY_OBJ for (let i = 0; i < needCastKeys.length; i++) { const key = needCastKeys[i] props[key] = resolvePropValue( options!, - rawCurrentProps, key, castValues[key], instance, + baseResolveDefault, !hasOwn(castValues, key), ) } @@ -446,14 +444,32 @@ function setFullProps( return hasAttrsChanged } -function resolvePropValue( +/** + * A type that allows both vdom and vapor instances + */ +type CommonInstance = Pick< + ComponentInternalInstance, + 'props' | 'propsDefaults' | 'ce' +> + +/** + * @internal for runtime-vapor + */ +export function resolvePropValue( options: NormalizedProps, - props: Data, key: string, value: unknown, - instance: ComponentInternalInstance, - isAbsent: boolean, -) { + instance: T, + /** + * Allow runtime-specific default resolution logic + */ + resolveDefault: ( + factory: (props: Data) => unknown, + instance: T, + key: string, + ) => unknown, + isAbsent = false, +): unknown { const opt = options[key] if (opt != null) { const hasDefault = hasOwn(opt, 'default') @@ -465,19 +481,16 @@ function resolvePropValue( !opt.skipFactory && isFunction(defaultValue) ) { - const { propsDefaults } = instance - if (key in propsDefaults) { - value = propsDefaults[key] + const cachedDefaults = + instance.propsDefaults || (instance.propsDefaults = {}) + if (hasOwn(cachedDefaults, key)) { + value = cachedDefaults[key] } else { - const reset = setCurrentInstance(instance) - value = propsDefaults[key] = defaultValue.call( - __COMPAT__ && - isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance) - ? createPropsDefaultThis(instance, props, key) - : null, - props, + value = cachedDefaults[key] = resolveDefault( + defaultValue, + instance, + key, ) - reset() } } else { value = defaultValue @@ -502,6 +515,27 @@ function resolvePropValue( return value } +/** + * runtime-dom-specific default resolving logic + */ +function baseResolveDefault( + factory: (props: Data) => unknown, + instance: ComponentInternalInstance, + key: string, +) { + let value + const reset = setCurrentInstance(instance) + const props = toRaw(instance.props) + value = factory.call( + __COMPAT__ && isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance) + ? createPropsDefaultThis(instance, props, key) + : null, + props, + ) + reset() + return value +} + const mixinPropsCache = new WeakMap() export function normalizePropsOptions( @@ -550,6 +584,22 @@ export function normalizePropsOptions( return EMPTY_ARR as any } + baseNormalizePropsOptions(raw, normalized, needCastKeys) + const res: NormalizedPropsOptions = [normalized, needCastKeys] + if (isObject(comp)) { + cache.set(comp, res) + } + return res +} + +/** + * @internal for runtime-vapor only + */ +export function baseNormalizePropsOptions( + raw: ComponentPropsOptions | undefined, + normalized: NonNullable, + needCastKeys: NonNullable, +): void { if (isArray(raw)) { for (let i = 0; i < raw.length; i++) { if (__DEV__ && !isString(raw[i])) { @@ -604,12 +654,6 @@ export function normalizePropsOptions( } } } - - const res: NormalizedPropsOptions = [normalized, needCastKeys] - if (isObject(comp)) { - cache.set(comp, res) - } - return res } function validatePropName(key: string) { diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index a24714b8ca..a0e13a2bc4 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -320,6 +320,7 @@ export type { ExtractPropTypes, ExtractPublicPropTypes, ExtractDefaultPropTypes, + NormalizedPropsOptions, } from './componentProps' export type { Directive, @@ -480,3 +481,10 @@ export const compatUtils = ( export const DeprecationTypes = ( __COMPAT__ ? _DeprecationTypes : null ) as typeof _DeprecationTypes + +// VAPOR ----------------------------------------------------------------------- + +// **IMPORTANT** These APIs are exposed solely for @vue/runtime-vapor and may +// change without notice between versions. User code should never rely on them. + +export { baseNormalizePropsOptions, resolvePropValue } from './componentProps' diff --git a/packages/runtime-vapor/src/_new/apiCreateApp.ts b/packages/runtime-vapor/src/_new/apiCreateApp.ts new file mode 100644 index 0000000000..f6a7c00bcd --- /dev/null +++ b/packages/runtime-vapor/src/_new/apiCreateApp.ts @@ -0,0 +1,18 @@ +import { normalizeContainer } from '../apiRender' +import { insert } from '../dom/element' +import { type Component, createComponent } from './component' + +export function createVaporApp(comp: Component): any { + return { + mount(container: string | ParentNode) { + container = normalizeContainer(container) + // clear content before mounting + if (container.nodeType === 1 /* Node.ELEMENT_NODE */) { + container.textContent = '' + } + const instance = createComponent(comp) + insert(instance.block, container) + return instance + }, + } +} diff --git a/packages/runtime-vapor/src/_new/component.ts b/packages/runtime-vapor/src/_new/component.ts new file mode 100644 index 0000000000..670b73131c --- /dev/null +++ b/packages/runtime-vapor/src/_new/component.ts @@ -0,0 +1,184 @@ +import { + type ComponentPropsOptions, + EffectScope, + type EmitsOptions, + type NormalizedPropsOptions, +} from '@vue/runtime-core' +import type { Block } from '../block' +import type { Data } from '@vue/runtime-shared' +import { pauseTracking, resetTracking } from '@vue/reactivity' +import { isFunction } from '@vue/shared' +import { + type RawProps, + getDynamicPropsHandlers, + initStaticProps, +} from './componentProps' +import { setDynamicProp } from '../dom/prop' +import { renderEffect } from './renderEffect' + +export type Component = FunctionalComponent | ObjectComponent + +export type SetupFn = ( + props: any, + ctx: SetupContext, +) => Block | Data | undefined + +export type FunctionalComponent = SetupFn & + Omit & { + displayName?: string + } & SharedInternalOptions + +export interface ObjectComponent + extends ComponentInternalOptions, + SharedInternalOptions { + setup?: SetupFn + inheritAttrs?: boolean + props?: ComponentPropsOptions + emits?: EmitsOptions + render?(ctx: any): Block + + name?: string + vapor?: boolean +} + +interface SharedInternalOptions { + __propsOptions?: NormalizedPropsOptions + __propsHandlers?: [ProxyHandler, ProxyHandler] +} + +// Note: can't mark this whole interface internal because some public interfaces +// extend it. +interface ComponentInternalOptions { + /** + * @internal + */ + __scopeId?: string + /** + * @internal + */ + __cssModules?: Data + /** + * @internal + */ + __hmrId?: string + /** + * Compat build only, for bailing out of certain compatibility behavior + */ + __isBuiltIn?: boolean + /** + * This one should be exposed so that devtools can make use of it + */ + __file?: string + /** + * name inferred from filename + */ + __name?: string +} + +export function createComponent( + component: Component, + rawProps?: RawProps, + 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 + currentInstance = instance + instance.scope.on() + + const setupFn = isFunction(component) ? component : component.setup + const setupContext = setupFn!.length > 1 ? new SetupContext(instance) : null + instance.block = setupFn!( + instance.props, + // @ts-expect-error + setupContext, + ) as Block // TODO handle return object + + // single root, inherit attrs + if ( + instance.hasFallthrough && + component.inheritAttrs !== false && + instance.block instanceof Element && + Object.keys(instance.attrs).length + ) { + renderEffect(() => { + for (const key in instance.attrs) { + setDynamicProp(instance.block as Element, key, instance.attrs[key]) + } + }) + } + + instance.scope.off() + currentInstance = prevInstance + resetTracking() + return instance +} + +let uid = 0 +export let currentInstance: ComponentInstance | null = null + +export class ComponentInstance { + type: Component + uid: number = uid++ + scope: EffectScope = new EffectScope(true) + props: Record + propsDefaults: Record | null + attrs: Record + block: Block + exposed?: Record + hasFallthrough: boolean + + constructor(comp: Component, rawProps?: RawProps) { + this.type = comp + this.block = null! // to be set + + // init props + this.propsDefaults = null + 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]) + this.hasFallthrough = true + } else { + this.props = {} + this.attrs = {} + this.hasFallthrough = initStaticProps(comp, rawProps, this) + } + + // TODO validate props + // TODO init slots + } +} + +export function isVaporComponent(value: unknown): value is ComponentInstance { + return value instanceof ComponentInstance +} + +export 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 + } + } +} diff --git a/packages/runtime-vapor/src/_new/componentProps.ts b/packages/runtime-vapor/src/_new/componentProps.ts new file mode 100644 index 0000000000..03f6082e48 --- /dev/null +++ b/packages/runtime-vapor/src/_new/componentProps.ts @@ -0,0 +1,214 @@ +import { EMPTY_ARR, NO, camelize, hasOwn, isFunction } from '@vue/shared' +import type { Component, ComponentInstance } from './component' +import { + type NormalizedPropsOptions, + baseNormalizePropsOptions, + resolvePropValue, +} from '@vue/runtime-core' + +export interface RawProps { + [key: string]: PropSource + $?: DynamicPropsSource[] +} + +type PropSource = T | (() => T) + +type DynamicPropsSource = PropSource> + +export function initStaticProps( + comp: Component, + rawProps: RawProps | undefined, + instance: ComponentInstance, +): boolean { + let hasAttrs = false + const { props, attrs } = instance + const [propsOptions, needCastKeys] = normalizePropsOptions(comp) + // TODO emits filtering + for (const key in rawProps) { + const normalizedKey = camelize(key) + const needCast = needCastKeys && needCastKeys.includes(normalizedKey) + const source = rawProps[key] + if (propsOptions && normalizedKey in propsOptions) { + if (isFunction(source)) { + Object.defineProperty(props, normalizedKey, { + enumerable: true, + get: needCast + ? () => + resolvePropValue( + propsOptions, + normalizedKey, + source(), + instance, + resolveDefault, + ) + : source, + }) + } else { + props[normalizedKey] = needCast + ? resolvePropValue( + propsOptions, + normalizedKey, + source, + instance, + resolveDefault, + ) + : source + } + } else { + if (isFunction(source)) { + Object.defineProperty(attrs, key, { + enumerable: true, + get: source, + }) + } else { + attrs[normalizedKey] = source + } + hasAttrs = true + } + } + for (const key in propsOptions) { + if (!(key in props)) { + props[key] = resolvePropValue( + propsOptions, + key, + undefined, + instance, + resolveDefault, + true, + ) + } + } + return hasAttrs +} + +function resolveDefault( + factory: (props: Record) => unknown, + instance: ComponentInstance, +) { + return factory.call(null, instance.props) +} + +// TODO optimization: maybe convert functions into computeds +function resolveSource(source: PropSource): Record { + return isFunction(source) ? source() : source +} + +export function getDynamicPropsHandlers( + comp: Component, + instance: ComponentInstance, +): [ProxyHandler, ProxyHandler] { + if (comp.__propsHandlers) { + return comp.__propsHandlers + } + let normalizedKeys: string[] | undefined + const propsOptions = normalizePropsOptions(comp)[0]! + const isProp = (key: string | symbol) => hasOwn(propsOptions, key) + + const getProp = (target: RawProps, key: string | symbol, asProp: boolean) => { + if (key !== '$' && (asProp ? isProp(key) : !isProp(key))) { + const castProp = (value: any, isAbsent = false) => + asProp + ? resolvePropValue( + propsOptions, + key as string, + value, + instance, + resolveDefault, + isAbsent, + ) + : value + + if (key in target) { + // TODO default value, casting, etc. + return castProp(resolveSource(target[key as string])) + } + if (target.$) { + let i = target.$.length + let source + while (i--) { + source = resolveSource(target.$[i]) + if (hasOwn(source, key)) { + return castProp(source[key]) + } + } + } + return castProp(undefined, true) + } + } + + const propsHandlers = { + get: (target, key) => getProp(target, key, true), + has: (_, key) => isProp(key), + getOwnPropertyDescriptor(target, key) { + if (isProp(key)) { + return { + configurable: true, + enumerable: true, + get: () => getProp(target, key, true), + } + } + }, + ownKeys: () => + normalizedKeys || (normalizedKeys = Object.keys(propsOptions)), + set: NO, + deleteProperty: NO, + } satisfies ProxyHandler + + const hasAttr = (target: RawProps, key: string | symbol) => { + if (key === '$' || isProp(key)) return false + if (hasOwn(target, key)) return true + if (target.$) { + let i = target.$.length + while (i--) { + if (hasOwn(resolveSource(target.$[i]), key)) { + return true + } + } + } + return false + } + + const attrsHandlers = { + get: (target, key) => getProp(target, key, false), + has: hasAttr, + getOwnPropertyDescriptor(target, key) { + if (hasAttr(target, key)) { + return { + configurable: true, + enumerable: true, + get: () => getProp(target, key, false), + } + } + }, + ownKeys(target) { + const staticKeys = Object.keys(target).filter( + key => key !== '$' && !isProp(key), + ) + if (target.$) { + let i = target.$.length + while (i--) { + staticKeys.push(...Object.keys(resolveSource(target.$[i]))) + } + } + return staticKeys + }, + set: NO, + deleteProperty: NO, + } satisfies ProxyHandler + + return (comp.__propsHandlers = [propsHandlers, attrsHandlers]) +} + +function normalizePropsOptions(comp: Component): NormalizedPropsOptions { + const cached = comp.__propsOptions + if (cached) return cached + + const raw = comp.props + if (!raw) return EMPTY_ARR as [] + + const normalized: NormalizedPropsOptions[0] = {} + const needCastKeys: NormalizedPropsOptions[1] = [] + baseNormalizePropsOptions(raw, normalized, needCastKeys) + + return (comp.__propsOptions = [normalized, needCastKeys]) +} diff --git a/packages/runtime-vapor/src/_new/index.ts b/packages/runtime-vapor/src/_new/index.ts new file mode 100644 index 0000000000..ed984366aa --- /dev/null +++ b/packages/runtime-vapor/src/_new/index.ts @@ -0,0 +1,3 @@ +export { createComponent as createComponentSimple } from './component' +export { renderEffect as renderEffectSimple } from './renderEffect' +export { createVaporApp as createVaporAppSimple } from './apiCreateApp' diff --git a/packages/runtime-vapor/src/_new/renderEffect.ts b/packages/runtime-vapor/src/_new/renderEffect.ts new file mode 100644 index 0000000000..e8fd31e394 --- /dev/null +++ b/packages/runtime-vapor/src/_new/renderEffect.ts @@ -0,0 +1,22 @@ +import { ReactiveEffect } from '@vue/reactivity' +import { + type SchedulerJob, + queueJob, +} from '../../../runtime-core/src/scheduler' +import { currentInstance } from './component' + +export function renderEffect(fn: () => void): void { + const updateFn = () => { + fn() + } + const effect = new ReactiveEffect(updateFn) + const job: SchedulerJob = effect.runIfDirty.bind(effect) + job.i = currentInstance as any + job.id = currentInstance!.uid + effect.scheduler = () => queueJob(job) + effect.run() + + // TODO lifecycle + // TODO recurse handling + // TODO measure +} diff --git a/packages/runtime-vapor/src/apiCreateComponentSimple.ts b/packages/runtime-vapor/src/apiCreateComponentSimple.ts deleted file mode 100644 index 98af50c19a..0000000000 --- a/packages/runtime-vapor/src/apiCreateComponentSimple.ts +++ /dev/null @@ -1,322 +0,0 @@ -import { - EffectScope, - ReactiveEffect, - pauseTracking, - resetTracking, -} from '@vue/reactivity' -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 - $?: DynamicPropsSource[] -} - -type PropSource = T | (() => T) - -type DynamicPropsSource = PropSource> - -export function createComponentSimple( - component: Component, - rawProps?: RawProps, - 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 - currentInstance = instance - instance.scope.on() - - const setupFn = isFunction(component) ? component : component.setup - const setupContext = setupFn!.length > 1 ? new SetupContext(instance) : null - instance.block = setupFn!( - instance.props, - // @ts-expect-error - setupContext, - ) as Block // TODO handle return object - - // single root, inherit attrs - if ( - instance.hasFallthrough && - component.inheritAttrs !== false && - instance.block instanceof Element && - Object.keys(instance.attrs).length - ) { - renderEffectSimple(() => { - for (const key in instance.attrs) { - setDynamicProp(instance.block as Element, key, instance.attrs[key]) - } - }) - } - - instance.scope.off() - currentInstance = prevInstance - resetTracking() - 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 -let currentInstance: ComponentInstance | null = null - -export class ComponentInstance { - type: Component - uid: number = uid++ - 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 - 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]) - this.hasFallthrough = true - } else { - this.hasFallthrough = initStaticProps( - comp, - rawProps, - (this.props = {}), - (this.attrs = {}), - ) - } - - // TODO validate props - // TODO init slots - } -} - -export function isVaporComponent(value: unknown): value is ComponentInstance { - return value instanceof ComponentInstance -} - -function initStaticProps( - comp: Component, - rawProps: RawProps | undefined, - props: any, - attrs: any, -): boolean { - let hasAttrs = false - const [propsOptions, needCastKeys] = normalizePropsOptions(comp) - for (const key in rawProps) { - const normalizedKey = camelize(key) - const needCast = needCastKeys && needCastKeys.includes(normalizedKey) - const source = rawProps[key] - if (propsOptions && normalizedKey in propsOptions) { - if (isFunction(source)) { - Object.defineProperty(props, normalizedKey, { - enumerable: true, - get: needCast - ? () => - resolvePropValue(propsOptions, props, normalizedKey, source()) - : source, - }) - } else { - props[normalizedKey] = needCast - ? resolvePropValue(propsOptions, props, normalizedKey, source) - : source - } - } else { - if (isFunction(source)) { - Object.defineProperty(attrs, key, { - enumerable: true, - get: source, - }) - } else { - attrs[normalizedKey] = source - } - hasAttrs = true - } - } - for (const key in propsOptions) { - if (!(key in props)) { - props[key] = resolvePropValue(propsOptions, props, key, undefined, true) - } - } - return hasAttrs -} - -// TODO optimization: maybe convert functions into computeds -function resolveSource(source: PropSource): Record { - return isFunction(source) ? source() : source -} - -function getDynamicPropsHandlers( - comp: Component, - instance: ComponentInstance, -): [ProxyHandler, ProxyHandler] { - if (comp.__propsHandlers) { - return comp.__propsHandlers - } - let normalizedKeys: string[] | undefined - const propsOptions = normalizePropsOptions(comp)[0]! - const isProp = (key: string | symbol) => hasOwn(propsOptions, key) - - const getProp = (target: RawProps, key: string | symbol, asProp: boolean) => { - if (key !== '$' && (asProp ? isProp(key) : !isProp(key))) { - const castProp = (value: any, isAbsent?: boolean) => - asProp - ? resolvePropValue( - propsOptions, - instance.props, - key as string, - value, - isAbsent, - ) - : value - - if (key in target) { - // TODO default value, casting, etc. - return castProp(resolveSource(target[key as string])) - } - if (target.$) { - let i = target.$.length - let source - while (i--) { - source = resolveSource(target.$[i]) - if (hasOwn(source, key)) { - return castProp(source[key]) - } - } - } - return castProp(undefined, true) - } - } - - const propsHandlers = { - get: (target, key) => getProp(target, key, true), - has: (_, key) => isProp(key), - getOwnPropertyDescriptor(target, key) { - if (isProp(key)) { - return { - configurable: true, - enumerable: true, - get: () => getProp(target, key, true), - } - } - }, - ownKeys: () => - normalizedKeys || (normalizedKeys = Object.keys(propsOptions)), - set: NO, - deleteProperty: NO, - } satisfies ProxyHandler - - const hasAttr = (target: RawProps, key: string | symbol) => { - if (key === '$' || isProp(key)) return false - if (hasOwn(target, key)) return true - if (target.$) { - let i = target.$.length - while (i--) { - if (hasOwn(resolveSource(target.$[i]), key)) { - return true - } - } - } - return false - } - - const attrsHandlers = { - get: (target, key) => getProp(target, key, false), - has: hasAttr, - getOwnPropertyDescriptor(target, key) { - if (hasAttr(target, key)) { - return { - configurable: true, - enumerable: true, - get: () => getProp(target, key, false), - } - } - }, - ownKeys(target) { - const staticKeys = Object.keys(target).filter( - key => key !== '$' && !isProp(key), - ) - if (target.$) { - let i = target.$.length - while (i--) { - staticKeys.push(...Object.keys(resolveSource(target.$[i]))) - } - } - return staticKeys - }, - set: NO, - deleteProperty: NO, - } satisfies ProxyHandler - - return (comp.__propsHandlers = [propsHandlers, attrsHandlers]) -} - -export function renderEffectSimple(fn: () => void): void { - const updateFn = () => { - fn() - } - const effect = new ReactiveEffect(updateFn) - const job: SchedulerJob = effect.runIfDirty.bind(effect) - job.i = currentInstance as any - job.id = currentInstance!.uid - effect.scheduler = () => queueJob(job) - effect.run() - - // TODO lifecycle - // TODO recurse handling - // TODO measure -} - -// vapor app can be a subset of main app APIs -// TODO refactor core createApp for reuse -export function createVaporAppSimple(comp: Component): any { - return { - mount(container: string | ParentNode) { - container = normalizeContainer(container) - // clear content before mounting - if (container.nodeType === 1 /* Node.ELEMENT_NODE */) { - container.textContent = '' - } - const rootBlock = createComponentSimple(comp) - insert(rootBlock, container) - }, - } -} diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts index de5a301d19..0a3ce4c4e1 100644 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@ -1,8 +1,5 @@ import { isArray } from '@vue/shared' -import { - type ComponentInstance, - isVaporComponent, -} from './apiCreateComponentSimple' +import { type ComponentInstance, isVaporComponent } from './_new/component' export const fragmentKey: unique symbol = Symbol(__DEV__ ? `fragmentKey` : ``) diff --git a/packages/runtime-vapor/src/dom/element.ts b/packages/runtime-vapor/src/dom/element.ts index 6f0fc27f89..51843844dc 100644 --- a/packages/runtime-vapor/src/dom/element.ts +++ b/packages/runtime-vapor/src/dom/element.ts @@ -2,7 +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' +import { isVaporComponent } from '../_new/component' // export function insert( // block: Block, diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index eb6306232e..b0277f0d80 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -155,11 +155,6 @@ export { export { createBranch, createIf } from './apiCreateIf' export { createFor, createForSlots } from './apiCreateFor' export { createComponent } from './apiCreateComponent' -export { - createComponentSimple, - renderEffectSimple, - createVaporAppSimple, -} from './apiCreateComponentSimple' export { createSelector } from './apiCreateSelector' export { setInheritAttrs } from './componentAttrs' @@ -195,3 +190,5 @@ export const devtools = ( export const setDevtoolsHook = ( __DEV__ || __ESM_BUNDLER__ ? _setDevtoolsHook : NOOP ) as typeof _setDevtoolsHook + +export * from './_new'