From e9b7388d08a2ff6b23efeabdd31b80fd3621fe10 Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Fri, 9 Feb 2024 23:41:29 +0900 Subject: [PATCH 1/3] feat(runtime-vapor): component attrs --- .../__tests__/componentProps.spec.ts | 112 +++++++++++++++--- packages/runtime-vapor/src/component.ts | 2 + packages/runtime-vapor/src/componentProps.ts | 23 +++- packages/runtime-vapor/src/render.ts | 4 +- 4 files changed, 119 insertions(+), 22 deletions(-) diff --git a/packages/runtime-vapor/__tests__/componentProps.spec.ts b/packages/runtime-vapor/__tests__/componentProps.spec.ts index e6a4dec94..dfcfcfe93 100644 --- a/packages/runtime-vapor/__tests__/componentProps.spec.ts +++ b/packages/runtime-vapor/__tests__/componentProps.spec.ts @@ -22,13 +22,14 @@ const define = makeRender() describe('component props (vapor)', () => { test('stateful', () => { let props: any - // TODO: attrs + let attrs: any const { render } = define({ props: ['fooBar', 'barBaz'], render() { const instance = getCurrentInstance()! props = instance.props + attrs = instance.attrs }, }) @@ -36,33 +37,57 @@ describe('component props (vapor)', () => { get fooBar() { return 1 }, + get bar() { + return 2 + }, }) expect(props.fooBar).toEqual(1) + expect(attrs.bar).toEqual(2) // test passing kebab-case and resolving to camelCase render({ get ['foo-bar']() { return 2 }, + get bar() { + return 3 + }, + get baz() { + return 4 + }, }) expect(props.fooBar).toEqual(2) + expect(attrs.bar).toEqual(3) + expect(attrs.baz).toEqual(4) // test updating kebab-case should not delete it (#955) render({ get ['foo-bar']() { return 3 }, + get bar() { + return 3 + }, + get baz() { + return 4 + }, get barBaz() { return 5 }, }) expect(props.fooBar).toEqual(3) expect(props.barBaz).toEqual(5) + expect(attrs.bar).toEqual(3) + expect(attrs.baz).toEqual(4) - render({}) + render({ + get qux() { + return 5 + }, + }) expect(props.fooBar).toBeUndefined() expect(props.barBaz).toBeUndefined() - // expect(props.qux).toEqual(5) // TODO: attrs + expect(attrs.qux).toEqual(5) }) test.todo('stateful with setup', () => { @@ -71,11 +96,12 @@ describe('component props (vapor)', () => { test('functional with declaration', () => { let props: any - // TODO: attrs + let attrs: any const { component: Comp, render } = define((_props: any) => { const instance = getCurrentInstance()! props = instance.props + attrs = instance.attrs return {} }) Comp.props = ['foo'] @@ -85,45 +111,68 @@ describe('component props (vapor)', () => { get foo() { return 1 }, + get bar() { + return 2 + }, }) expect(props.foo).toEqual(1) + expect(attrs.bar).toEqual(2) render({ get foo() { return 2 }, + get bar() { + return 3 + }, + get baz() { + return 4 + }, }) expect(props.foo).toEqual(2) + expect(attrs.bar).toEqual(3) + expect(attrs.baz).toEqual(4) - render({}) + render({ + get qux() { + return 5 + }, + }) expect(props.foo).toBeUndefined() + expect(attrs.qux).toEqual(5) }) - test('functional without declaration', () => { + // FIXME: + test.todo('functional without declaration', () => { let props: any - // TODO: attrs + let attrs: any - const { component: Comp, render } = define((_props: any) => { - const instance = getCurrentInstance()! - props = instance.props - return {} - }) - Comp.props = undefined as any - Comp.render = (() => {}) as any + const { component: Comp, render } = define( + (_props: any, { attrs: _attrs }: any) => { + const instance = getCurrentInstance()! + props = instance.props + attrs = instance.attrs + return {} + }, + ) + Comp.props = undefined + Comp.render = () => {} render({ get foo() { return 1 }, }) - expect(props.foo).toBeUndefined() + expect(props.foo).toEqual(1) + expect(attrs.foo).toEqual(1) render({ get foo() { return 2 }, }) - expect(props.foo).toBeUndefined() + expect(props.foo).toEqual(2) + expect(attrs.foo).toEqual(2) }) test('boolean casting', () => { @@ -490,8 +539,35 @@ describe('component props (vapor)', () => { }) // #5016 - test.todo('handling attr with undefined value', () => { - // TODO: attrs + test('handling attr with undefined value', () => { + const { render, host } = define({ + render() { + const instance = getCurrentInstance()! + + const t0 = template('
') + const n0 = t0() + const n1 = children(n0, 0) + watchEffect(() => { + setText( + n1, + JSON.stringify(instance.attrs) + Object.keys(instance.attrs), + ) + }) + return n0 + }, + }) + + let attrs: any = { + get foo() { + return undefined + }, + } + + render(attrs) + + expect(host.innerHTML).toBe( + `
${JSON.stringify(attrs) + Object.keys(attrs)}
`, + ) }) // #6915 diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 570834eac..be3bebd79 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -58,6 +58,7 @@ export interface ComponentInternalInstance { // state props: Data + attrs: Data setupState: Data emit: EmitFn emitted: Record | null @@ -179,6 +180,7 @@ export const createComponentInstance = ( // state props: EMPTY_OBJ, + attrs: EMPTY_OBJ, setupState: EMPTY_OBJ, refs: EMPTY_OBJ, metadata: new WeakMap(), diff --git a/packages/runtime-vapor/src/componentProps.ts b/packages/runtime-vapor/src/componentProps.ts index 36235fb53..278fd5ebe 100644 --- a/packages/runtime-vapor/src/componentProps.ts +++ b/packages/runtime-vapor/src/componentProps.ts @@ -19,6 +19,7 @@ import { type ComponentInternalInstance, setCurrentInstance, } from './component' +import { isEmitListener } from './componentEmits' export type ComponentPropsOptions

= | ComponentObjectPropsOptions

@@ -76,8 +77,10 @@ export function initProps( rawProps: Data | null, ) { const props: Data = {} + const attrs: Data = {} const [options, needCastKeys] = instance.propsOptions + let hasAttrsChanged = false let rawCastValues: Data | undefined if (rawProps) { for (let key in rawProps) { @@ -96,6 +99,7 @@ export function initProps( get() { return valueGetter() }, + enumerable: true, }) } else { // NOTE: must getter @@ -105,10 +109,22 @@ export function initProps( get() { return valueGetter() }, + enumerable: true, }) } - } else { - // TODO: + } else if (!isEmitListener(instance.emitsOptions, key)) { + // if (!(key in attrs) || value !== attrs[key]) { + if (!(key in attrs)) { + // NOTE: must getter + // attrs[key] = value + Object.defineProperty(attrs, key, { + get() { + return valueGetter() + }, + enumerable: true, + }) + hasAttrsChanged = true + } } } } @@ -149,6 +165,9 @@ export function initProps( } instance.props = shallowReactive(props) + instance.attrs = attrs + + return hasAttrsChanged } function resolvePropValue( diff --git a/packages/runtime-vapor/src/render.ts b/packages/runtime-vapor/src/render.ts index acda15cf3..716bac7d7 100644 --- a/packages/runtime-vapor/src/render.ts +++ b/packages/runtime-vapor/src/render.ts @@ -46,8 +46,8 @@ export function mountComponent( const reset = setCurrentInstance(instance) const block = instance.scope.run(() => { - const { component, props, emit } = instance - const ctx = { expose: () => {}, emit } + const { component, props, emit, attrs } = instance + const ctx = { expose: () => {}, emit, attrs } const setupFn = typeof component === 'function' ? component : component.setup From e01f01032331a21ef554d073dca72901836eec2c Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Sat, 10 Feb 2024 00:52:56 +0900 Subject: [PATCH 2/3] feat(runtime-vapor): functional component props --- packages/runtime-vapor/__tests__/_utils.ts | 10 ++-------- .../runtime-vapor/__tests__/componentProps.spec.ts | 2 +- packages/runtime-vapor/src/componentProps.ts | 11 ++++++++++- packages/runtime-vapor/src/render.ts | 13 +++++++++---- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/packages/runtime-vapor/__tests__/_utils.ts b/packages/runtime-vapor/__tests__/_utils.ts index 0565d4377..b0e015e2f 100644 --- a/packages/runtime-vapor/__tests__/_utils.ts +++ b/packages/runtime-vapor/__tests__/_utils.ts @@ -1,4 +1,4 @@ -import { type Data, isFunction } from '@vue/shared' +import type { Data } from '@vue/shared' import { type ComponentInternalInstance, type ObjectComponent, @@ -24,13 +24,7 @@ export function makeRender( }) const define = (comp: Component) => { - const component = defineComponent( - isFunction(comp) - ? { - setup: comp, - } - : comp, - ) + const component = defineComponent(comp) let instance: ComponentInternalInstance const render = ( props: Data = {}, diff --git a/packages/runtime-vapor/__tests__/componentProps.spec.ts b/packages/runtime-vapor/__tests__/componentProps.spec.ts index dfcfcfe93..333496a6a 100644 --- a/packages/runtime-vapor/__tests__/componentProps.spec.ts +++ b/packages/runtime-vapor/__tests__/componentProps.spec.ts @@ -143,7 +143,7 @@ describe('component props (vapor)', () => { }) // FIXME: - test.todo('functional without declaration', () => { + test('functional without declaration', () => { let props: any let attrs: any diff --git a/packages/runtime-vapor/src/componentProps.ts b/packages/runtime-vapor/src/componentProps.ts index 278fd5ebe..c1f76748d 100644 --- a/packages/runtime-vapor/src/componentProps.ts +++ b/packages/runtime-vapor/src/componentProps.ts @@ -75,6 +75,7 @@ export type NormalizedPropsOptions = [NormalizedProps, string[]] | [] export function initProps( instance: ComponentInternalInstance, rawProps: Data | null, + isStateful: boolean, ) { const props: Data = {} const attrs: Data = {} @@ -164,7 +165,15 @@ export function initProps( validateProps(rawProps || {}, props, instance) } - instance.props = shallowReactive(props) + if (isStateful) { + instance.props = shallowReactive(props) + } else { + if (instance.propsOptions === EMPTY_ARR) { + instance.props = attrs + } else { + instance.props = props + } + } instance.attrs = attrs return hasAttrsChanged diff --git a/packages/runtime-vapor/src/render.ts b/packages/runtime-vapor/src/render.ts index 716bac7d7..e9c948dab 100644 --- a/packages/runtime-vapor/src/render.ts +++ b/packages/runtime-vapor/src/render.ts @@ -1,5 +1,11 @@ import { proxyRefs } from '@vue/reactivity' -import { type Data, invokeArrayFns, isArray, isObject } from '@vue/shared' +import { + type Data, + invokeArrayFns, + isArray, + isFunction, + isObject, +} from '@vue/shared' import { type Component, type ComponentInternalInstance, @@ -28,7 +34,7 @@ export function render( container: string | ParentNode, ): ComponentInternalInstance { const instance = createComponentInstance(comp, props) - initProps(instance, props) + initProps(instance, props, !isFunction(instance.component)) return mountComponent(instance, (container = normalizeContainer(container))) } @@ -49,8 +55,7 @@ export function mountComponent( const { component, props, emit, attrs } = instance const ctx = { expose: () => {}, emit, attrs } - const setupFn = - typeof component === 'function' ? component : component.setup + const setupFn = isFunction(component) ? component : component.setup const stateOrNode = setupFn && setupFn(props, ctx) let block: Block | undefined From fcb0809c0de6dc8acd4cf970cf64372b85b44b2b Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Sat, 10 Feb 2024 01:31:43 +0900 Subject: [PATCH 3/3] chore: remove dead code --- .../__tests__/componentProps.spec.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/runtime-vapor/__tests__/componentProps.spec.ts b/packages/runtime-vapor/__tests__/componentProps.spec.ts index 333496a6a..8e9e8ce70 100644 --- a/packages/runtime-vapor/__tests__/componentProps.spec.ts +++ b/packages/runtime-vapor/__tests__/componentProps.spec.ts @@ -105,7 +105,6 @@ describe('component props (vapor)', () => { return {} }) Comp.props = ['foo'] - Comp.render = (() => {}) as any render({ get foo() { @@ -147,16 +146,12 @@ describe('component props (vapor)', () => { let props: any let attrs: any - const { component: Comp, render } = define( - (_props: any, { attrs: _attrs }: any) => { - const instance = getCurrentInstance()! - props = instance.props - attrs = instance.attrs - return {} - }, - ) - Comp.props = undefined - Comp.render = () => {} + const { render } = define((_props: any, { attrs: _attrs }: any) => { + const instance = getCurrentInstance()! + props = instance.props + attrs = instance.attrs + return {} + }) render({ get foo() { @@ -543,7 +538,6 @@ describe('component props (vapor)', () => { const { render, host } = define({ render() { const instance = getCurrentInstance()! - const t0 = template('

') const n0 = t0() const n1 = children(n0, 0)