]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip(vapor): align compiler with new props runtime behavior
authorEvan You <evan@vuejs.org>
Wed, 4 Dec 2024 12:55:10 +0000 (20:55 +0800)
committerEvan You <evan@vuejs.org>
Wed, 4 Dec 2024 13:08:12 +0000 (21:08 +0800)
packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/__snapshots__/transformSlotOutlet.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts
packages/compiler-vapor/__tests__/transforms/vModel.spec.ts
packages/compiler-vapor/src/generators/component.ts
packages/runtime-vapor/src/component.ts
packages/runtime-vapor/src/componentProps.ts

index 79b8fffbb1889042d49773713a487e83e488e7cd..961a6b4138012ee76f684bf31debb12de457bcb2 100644 (file)
@@ -100,9 +100,7 @@ exports[`compiler: element transform > component > should wrap as function if v-
 
 export function render(_ctx) {
   const _component_Foo = _resolveComponent("Foo")
-  const n0 = _createComponent(_component_Foo, [
-    { onBar: () => $event => (_ctx.handleBar($event)) }
-  ], null, true)
+  const n0 = _createComponent(_component_Foo, { onBar: () => $event => (_ctx.handleBar($event)) }, null, true)
   return n0
 }"
 `;
@@ -112,12 +110,10 @@ exports[`compiler: element transform > component > static props 1`] = `
 
 export function render(_ctx) {
   const _component_Foo = _resolveComponent("Foo")
-  const n0 = _createComponent(_component_Foo, [
-    {
-      id: () => ("foo"), 
-      class: () => ("bar")
-    }
-  ], null, true)
+  const n0 = _createComponent(_component_Foo, {
+    id: () => ("foo"), 
+    class: () => ("bar")
+  }, null, true)
   return n0
 }"
 `;
@@ -127,9 +123,9 @@ exports[`compiler: element transform > component > v-bind="obj" 1`] = `
 
 export function render(_ctx) {
   const _component_Foo = _resolveComponent("Foo")
-  const n0 = _createComponent(_component_Foo, [
+  const n0 = _createComponent(_component_Foo, { $: [
     () => (_ctx.obj)
-  ], null, true)
+  ] }, null, true)
   return n0
 }"
 `;
@@ -139,10 +135,12 @@ exports[`compiler: element transform > component > v-bind="obj" after static pro
 
 export function render(_ctx) {
   const _component_Foo = _resolveComponent("Foo")
-  const n0 = _createComponent(_component_Foo, [
-    { id: () => ("foo") }, 
-    () => (_ctx.obj)
-  ], null, true)
+  const n0 = _createComponent(_component_Foo, {
+    id: () => ("foo"), 
+    $: [
+      () => (_ctx.obj)
+    ]
+  }, null, true)
   return n0
 }"
 `;
@@ -152,10 +150,10 @@ exports[`compiler: element transform > component > v-bind="obj" before static pr
 
 export function render(_ctx) {
   const _component_Foo = _resolveComponent("Foo")
-  const n0 = _createComponent(_component_Foo, [
+  const n0 = _createComponent(_component_Foo, { $: [
     () => (_ctx.obj), 
     { id: () => ("foo") }
-  ], null, true)
+  ] }, null, true)
   return n0
 }"
 `;
@@ -165,11 +163,13 @@ exports[`compiler: element transform > component > v-bind="obj" between static p
 
 export function render(_ctx) {
   const _component_Foo = _resolveComponent("Foo")
-  const n0 = _createComponent(_component_Foo, [
-    { id: () => ("foo") }, 
-    () => (_ctx.obj), 
-    { class: () => ("bar") }
-  ], null, true)
+  const n0 = _createComponent(_component_Foo, {
+    id: () => ("foo"), 
+    $: [
+      () => (_ctx.obj), 
+      { class: () => ("bar") }
+    ]
+  }, null, true)
   return n0
 }"
 `;
@@ -179,9 +179,9 @@ exports[`compiler: element transform > component > v-on="obj" 1`] = `
 
 export function render(_ctx) {
   const _component_Foo = _resolveComponent("Foo")
-  const n0 = _createComponent(_component_Foo, [
+  const n0 = _createComponent(_component_Foo, { $: [
     () => (_toHandlers(_ctx.obj))
-  ], null, true)
+  ] }, null, true)
   return n0
 }"
 `;
@@ -192,10 +192,10 @@ import { resolveComponent as _resolveComponent, createComponent as _createCompon
 
 export function render(_ctx) {
   const _component_Foo = _resolveComponent("Foo")
-  const n0 = _createComponent(_component_Foo, [
+  const n0 = _createComponent(_component_Foo, { $: [
     () => ({ [_toHandlerKey(_ctx.foo-_ctx.bar)]: () => _ctx.bar }), 
     () => ({ [_toHandlerKey(_ctx.baz)]: () => _ctx.qux })
-  ], null, true)
+  ] }, null, true)
   return n0
 }"
 `;
@@ -205,10 +205,10 @@ exports[`compiler: element transform > component with dynamic prop arguments 1`]
 
 export function render(_ctx) {
   const _component_Foo = _resolveComponent("Foo")
-  const n0 = _createComponent(_component_Foo, [
+  const n0 = _createComponent(_component_Foo, { $: [
     () => ({ [_ctx.foo-_ctx.bar]: _ctx.bar }), 
     () => ({ [_ctx.baz]: _ctx.qux })
-  ], null, true)
+  ] }, null, true)
   return n0
 }"
 `;
@@ -245,9 +245,7 @@ exports[`compiler: element transform > dynamic component > normal component with
 
 export function render(_ctx) {
   const _component_custom_input = _resolveComponent("custom-input")
-  const n0 = _createComponent(_component_custom_input, [
-    { is: () => ("foo") }
-  ], null, true)
+  const n0 = _createComponent(_component_custom_input, { is: () => ("foo") }, null, true)
   return n0
 }"
 `;
index eab69c82bb5bd44e1cd62fed7350098846eba03e..2b9772aa0eef0a097eb322cbe8d0b065c48ab87a 100644 (file)
@@ -27,9 +27,7 @@ exports[`compiler: transform <slot> outlets > default slot outlet with props & f
 const t0 = _template("<div></div>")
 
 export function render(_ctx) {
-  const n0 = _createSlot("default", [
-    { foo: () => (_ctx.bar) }
-  ], () => {
+  const n0 = _createSlot("default", { foo: () => (_ctx.bar) }, () => {
     const n2 = t0()
     return n2
   })
@@ -41,13 +39,11 @@ exports[`compiler: transform <slot> outlets > default slot outlet with props 1`]
 "import { createSlot as _createSlot } from 'vue/vapor';
 
 export function render(_ctx) {
-  const n0 = _createSlot("default", [
-    {
-      foo: () => ("bar"), 
-      baz: () => (_ctx.qux), 
-      fooBar: () => (_ctx.foo-_ctx.bar)
-    }
-  ])
+  const n0 = _createSlot("default", {
+    foo: () => ("bar"), 
+    baz: () => (_ctx.qux), 
+    fooBar: () => (_ctx.foo-_ctx.bar)
+  })
   return n0
 }"
 `;
@@ -107,9 +103,7 @@ exports[`compiler: transform <slot> outlets > named slot outlet with props & fal
 const t0 = _template("<div></div>")
 
 export function render(_ctx) {
-  const n0 = _createSlot("foo", [
-    { foo: () => (_ctx.bar) }
-  ], () => {
+  const n0 = _createSlot("foo", { foo: () => (_ctx.bar) }, () => {
     const n2 = t0()
     return n2
   })
@@ -130,12 +124,10 @@ exports[`compiler: transform <slot> outlets > statically named slot outlet with
 "import { createSlot as _createSlot } from 'vue/vapor';
 
 export function render(_ctx) {
-  const n0 = _createSlot("foo", [
-    {
-      foo: () => ("bar"), 
-      baz: () => (_ctx.qux)
-    }
-  ])
+  const n0 = _createSlot("foo", {
+    foo: () => ("bar"), 
+    baz: () => (_ctx.qux)
+  })
   return n0
 }"
 `;
@@ -144,11 +136,13 @@ exports[`compiler: transform <slot> outlets > statically named slot outlet with
 "import { createSlot as _createSlot } from 'vue/vapor';
 
 export function render(_ctx) {
-  const n0 = _createSlot("foo", [
-    { foo: () => ("bar") }, 
-    () => (_ctx.obj), 
-    { baz: () => (_ctx.qux) }
-  ])
+  const n0 = _createSlot("foo", {
+    foo: () => ("bar"), 
+    $: [
+      () => (_ctx.obj), 
+      { baz: () => (_ctx.qux) }
+    ]
+  })
   return n0
 }"
 `;
@@ -157,11 +151,13 @@ exports[`compiler: transform <slot> outlets > statically named slot outlet with
 "import { createSlot as _createSlot, toHandlers as _toHandlers } from 'vue/vapor';
 
 export function render(_ctx) {
-  const n0 = _createSlot("default", [
-    { onClick: () => _ctx.foo }, 
-    () => (_toHandlers(_ctx.bar)), 
-    { baz: () => (_ctx.qux) }
-  ])
+  const n0 = _createSlot("default", {
+    onClick: () => _ctx.foo, 
+    $: [
+      () => (_toHandlers(_ctx.bar)), 
+      { baz: () => (_ctx.qux) }
+    ]
+  })
   return n0
 }"
 `;
index 4630eadfb55415b28f7f3aea9cdcf1fef49f59fd..05938f25cb2f2019107ad1139fbefeddb55f664e 100644 (file)
@@ -464,9 +464,7 @@ exports[`compiler v-bind > number value 1`] = `
 
 export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
-  const n0 = _createComponent(_component_Comp, [
-    { depth: () => (0) }
-  ], null, true)
+  const n0 = _createComponent(_component_Comp, { depth: () => (0) }, null, true)
   return n0
 }"
 `;
index 04ceb3c5ac1d484cf225200760b9dc04cc17f057..49512448413246f29e293f3f5aa79e1ec2587168 100644 (file)
@@ -5,11 +5,9 @@ exports[`compiler: vModel transform > component > v-model for component should g
 
 export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
-  const n0 = _createComponent(_component_Comp, [
-    { modelValue: () => (_ctx.foo),
-    "onUpdate:modelValue": () => $event => (_ctx.foo = $event),
-    modelModifiers: () => ({ trim: true, "bar-baz": true }) }
-  ], null, true)
+  const n0 = _createComponent(_component_Comp, { modelValue: () => (_ctx.foo),
+  "onUpdate:modelValue": () => $event => (_ctx.foo = $event),
+  modelModifiers: () => ({ trim: true, "bar-baz": true }) }, null, true)
   return n0
 }"
 `;
@@ -19,10 +17,8 @@ exports[`compiler: vModel transform > component > v-model for component should w
 
 export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
-  const n0 = _createComponent(_component_Comp, [
-    { modelValue: () => (_ctx.foo),
-    "onUpdate:modelValue": () => $event => (_ctx.foo = $event) }
-  ], null, true)
+  const n0 = _createComponent(_component_Comp, { modelValue: () => (_ctx.foo),
+  "onUpdate:modelValue": () => $event => (_ctx.foo = $event) }, null, true)
   return n0
 }"
 `;
@@ -32,16 +28,14 @@ exports[`compiler: vModel transform > component > v-model with arguments for com
 
 export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
-  const n0 = _createComponent(_component_Comp, [
-    {
-      foo: () => (_ctx.foo),
-      "onUpdate:foo": () => $event => (_ctx.foo = $event),
-      fooModifiers: () => ({ trim: true }), 
-      bar: () => (_ctx.bar),
-      "onUpdate:bar": () => $event => (_ctx.bar = $event),
-      barModifiers: () => ({ number: true })
-    }
-  ], null, true)
+  const n0 = _createComponent(_component_Comp, {
+    foo: () => (_ctx.foo),
+    "onUpdate:foo": () => $event => (_ctx.foo = $event),
+    fooModifiers: () => ({ trim: true }), 
+    bar: () => (_ctx.bar),
+    "onUpdate:bar": () => $event => (_ctx.bar = $event),
+    barModifiers: () => ({ number: true })
+  }, null, true)
   return n0
 }"
 `;
@@ -51,10 +45,8 @@ exports[`compiler: vModel transform > component > v-model with arguments for com
 
 export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
-  const n0 = _createComponent(_component_Comp, [
-    { bar: () => (_ctx.foo),
-    "onUpdate:bar": () => $event => (_ctx.foo = $event) }
-  ], null, true)
+  const n0 = _createComponent(_component_Comp, { bar: () => (_ctx.foo),
+  "onUpdate:bar": () => $event => (_ctx.foo = $event) }, null, true)
   return n0
 }"
 `;
@@ -64,14 +56,14 @@ exports[`compiler: vModel transform > component > v-model with dynamic arguments
 
 export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
-  const n0 = _createComponent(_component_Comp, [
+  const n0 = _createComponent(_component_Comp, { $: [
     () => ({ [_ctx.foo]: _ctx.foo,
     ["onUpdate:" + _ctx.foo]: () => $event => (_ctx.foo = $event),
     [_ctx.foo + "Modifiers"]: () => ({ trim: true }) }), 
     () => ({ [_ctx.bar]: _ctx.bar,
     ["onUpdate:" + _ctx.bar]: () => $event => (_ctx.bar = $event),
     [_ctx.bar + "Modifiers"]: () => ({ number: true }) })
-  ], null, true)
+  ] }, null, true)
   return n0
 }"
 `;
@@ -81,10 +73,10 @@ exports[`compiler: vModel transform > component > v-model with dynamic arguments
 
 export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
-  const n0 = _createComponent(_component_Comp, [
+  const n0 = _createComponent(_component_Comp, { $: [
     () => ({ [_ctx.arg]: _ctx.foo,
     ["onUpdate:" + _ctx.arg]: () => $event => (_ctx.foo = $event) })
-  ], null, true)
+  ] }, null, true)
   return n0
 }"
 `;
index eacf622a77f46638af5553d85cc90e76a32c302c..91edd536aacd6571d925adda97fde87e89e17030 100644 (file)
@@ -43,9 +43,7 @@ const t0 = _template("<div></div>")
 export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
   const n1 = t0()
-  const n0 = _createComponent(_component_Comp, [
-    { id: () => (_ctx.foo) }
-  ], null, null, true)
+  const n0 = _createComponent(_component_Comp, { id: () => (_ctx.foo) }, null, null, true)
   _insert(n0, n1)
   return n1
 }"
index 2b5f894645612113fa4ce9e1b02995006821512f..8b7baee40eb84564160bb0ca686b35dd8aec8230 100644 (file)
@@ -199,12 +199,10 @@ describe('compiler: element transform', () => {
       )
 
       expect(code).toMatchSnapshot()
-      expect(code).contains(`[
-    {
-      id: () => ("foo"), 
-      class: () => ("bar")
-    }
-  ]`)
+      expect(code).contains(`{
+    id: () => ("foo"), 
+    class: () => ("bar")
+  }`)
 
       expect(ir.block.operation).toMatchObject([
         {
@@ -273,10 +271,12 @@ describe('compiler: element transform', () => {
         `<Foo id="foo" v-bind="obj" />`,
       )
       expect(code).toMatchSnapshot()
-      expect(code).contains(`[
-    { id: () => ("foo") }, 
-    () => (_ctx.obj)
-  ]`)
+      expect(code).contains(`{
+    id: () => ("foo"), 
+    $: [
+      () => (_ctx.obj)
+    ]
+  }`)
       expect(ir.block.operation).toMatchObject([
         {
           type: IRNodeTypes.CREATE_COMPONENT_NODE,
@@ -321,11 +321,13 @@ describe('compiler: element transform', () => {
         `<Foo id="foo" v-bind="obj" class="bar" />`,
       )
       expect(code).toMatchSnapshot()
-      expect(code).contains(`[
-    { id: () => ("foo") }, 
-    () => (_ctx.obj), 
-    { class: () => ("bar") }
-  ]`)
+      expect(code).contains(`{
+    id: () => ("foo"), 
+    $: [
+      () => (_ctx.obj), 
+      { class: () => ("bar") }
+    ]
+  }`)
       expect(ir.block.operation).toMatchObject([
         {
           type: IRNodeTypes.CREATE_COMPONENT_NODE,
index 921145643a538a5b2754a08c3861c884ed091696..8cbbab9dd4029505bdc7a641ebbf63769220623e 100644 (file)
@@ -208,9 +208,9 @@ describe('compiler: vModel transform', () => {
     test('v-model for component should work', () => {
       const { code, ir } = compileWithVModel('<Comp v-model="foo" />')
       expect(code).toMatchSnapshot()
+      expect(code).contains(`modelValue: () => (_ctx.foo),`)
       expect(code).contains(
-        `modelValue: () => (_ctx.foo),
-    "onUpdate:modelValue": () => $event => (_ctx.foo = $event)`,
+        `"onUpdate:modelValue": () => $event => (_ctx.foo = $event)`,
       )
       expect(ir.block.operation).toMatchObject([
         {
@@ -233,9 +233,9 @@ describe('compiler: vModel transform', () => {
     test('v-model with arguments for component should work', () => {
       const { code, ir } = compileWithVModel('<Comp v-model:bar="foo" />')
       expect(code).toMatchSnapshot()
+      expect(code).contains(`bar: () => (_ctx.foo),`)
       expect(code).contains(
-        `bar: () => (_ctx.foo),
-    "onUpdate:bar": () => $event => (_ctx.foo = $event)`,
+        `"onUpdate:bar": () => $event => (_ctx.foo = $event)`,
       )
       expect(ir.block.operation).toMatchObject([
         {
index d936a634070e16af9a1ad485444687f7bb07f9af..00ee4df85e33977249c9280d25ae4abf8e049f19 100644 (file)
@@ -40,19 +40,19 @@ import { genModelHandler } from './modelValue'
 import { genBlock } from './block'
 
 export function genCreateComponent(
-  oper: CreateComponentIRNode,
+  operation: CreateComponentIRNode,
   context: CodegenContext,
 ): CodeFragment[] {
   const { vaporHelper } = context
 
   const tag = genTag()
-  const { root, props, slots, once } = oper
+  const { root, props, slots, once } = operation
   const rawProps = genRawProps(props, context)
   const rawSlots = genRawSlots(slots, context)
 
   return [
     NEWLINE,
-    `const n${oper.id} = `,
+    `const n${operation.id} = `,
     ...genCall(
       vaporHelper('createComponent'),
       tag,
@@ -61,20 +61,20 @@ export function genCreateComponent(
       root ? 'true' : false,
       once && 'true',
     ),
-    ...genDirectivesForElement(oper.id, context),
+    ...genDirectivesForElement(operation.id, context),
   ]
 
   function genTag() {
-    if (oper.dynamic) {
+    if (operation.dynamic) {
       return genCall(
         vaporHelper('resolveDynamicComponent'),
-        genExpression(oper.dynamic, context),
+        genExpression(operation.dynamic, context),
       )
-    } else if (oper.asset) {
-      return toValidAssetId(oper.tag, 'component')
+    } else if (operation.asset) {
+      return toValidAssetId(operation.tag, 'component')
     } else {
       return genExpression(
-        extend(createSimpleExpression(oper.tag, false), { ast: null }),
+        extend(createSimpleExpression(operation.tag, false), { ast: null }),
         context,
       )
     }
@@ -85,41 +85,65 @@ export function genRawProps(
   props: IRProps[],
   context: CodegenContext,
 ): CodeFragment[] | undefined {
-  const { vaporHelper } = context
-  const frag = props
-    .map(props => {
-      if (isArray(props)) {
-        if (!props.length) return
-        return genStaticProps(props, context)
-      } else {
-        let expr: CodeFragment[]
-        if (props.kind === IRDynamicPropsKind.ATTRIBUTE)
-          expr = genMulti(DELIMITERS_OBJECT, genProp(props, context))
-        else {
-          expr = genExpression(props.value, context)
-          if (props.handler) expr = genCall(vaporHelper('toHandlers'), expr)
-        }
-        return ['() => (', ...expr, ')']
-      }
-    })
-    .filter(
-      Boolean as any as (v: CodeFragment[] | undefined) => v is CodeFragment[],
+  const staticProps = props[0]
+  if (isArray(staticProps)) {
+    if (!staticProps.length && props.length === 1) {
+      return
+    }
+    return genStaticProps(
+      staticProps,
+      context,
+      genDynamicProps(props.slice(1), context),
     )
-  if (frag.length) {
-    return genMulti(DELIMITERS_ARRAY_NEWLINE, ...frag)
+  } else if (props.length) {
+    // all dynamic
+    return genStaticProps([], context, genDynamicProps(props, context))
   }
 }
 
 function genStaticProps(
   props: IRPropsStatic,
   context: CodegenContext,
+  dynamicProps?: CodeFragment[],
 ): CodeFragment[] {
+  const args = props.map(prop => genProp(prop, context, true))
+  if (dynamicProps) {
+    args.push([`$: `, ...dynamicProps])
+  }
   return genMulti(
-    props.length > 1 ? DELIMITERS_OBJECT_NEWLINE : DELIMITERS_OBJECT,
-    ...props.map(prop => genProp(prop, context, true)),
+    args.length > 1 ? DELIMITERS_OBJECT_NEWLINE : DELIMITERS_OBJECT,
+    ...args,
   )
 }
 
+function genDynamicProps(
+  props: IRProps[],
+  context: CodegenContext,
+): CodeFragment[] | undefined {
+  const { vaporHelper } = context
+  const frags: CodeFragment[][] = []
+  for (const p of props) {
+    let expr: CodeFragment[]
+    if (isArray(p)) {
+      if (p.length) {
+        frags.push(genStaticProps(p, context))
+      }
+      continue
+    } else {
+      if (p.kind === IRDynamicPropsKind.ATTRIBUTE)
+        expr = genMulti(DELIMITERS_OBJECT, genProp(p, context))
+      else {
+        expr = genExpression(p.value, context)
+        if (p.handler) expr = genCall(vaporHelper('toHandlers'), expr)
+      }
+    }
+    frags.push(['() => (', ...expr, ')'])
+  }
+  if (frags.length) {
+    return genMulti(DELIMITERS_ARRAY_NEWLINE, ...frags)
+  }
+}
+
 function genProp(prop: IRProp, context: CodegenContext, isStatic?: boolean) {
   const values = genPropValue(prop.values, context)
   return [
index 3bd360cea273fda2c899c5a2429ea3ed67671de9..d78ce73d09426276b36ca6e1fea8d1f3451f5ded 100644 (file)
@@ -76,10 +76,11 @@ export function createComponent(
   // check if we are the single root of the parent
   // if yes, inject parent attrs as dynamic props source
   if (isSingleRoot && currentInstance && currentInstance.hasFallthrough) {
+    const attrs = currentInstance.attrs
     if (rawProps) {
-      ;(rawProps.$ || (rawProps.$ = [])).push(currentInstance.attrs)
+      ;(rawProps.$ || (rawProps.$ = [])).push(() => attrs)
     } else {
-      rawProps = { $: [currentInstance.attrs] }
+      rawProps = { $: [() => attrs] } as RawProps
     }
   }
 
index 17fc893fc02e64034d212737e1e5af5d944e306c..3d410853252ccff3403fda8e58cfdda0dc414338 100644 (file)
@@ -8,14 +8,13 @@ import {
 } from '@vue/runtime-dom'
 import { normalizeEmitsOptions } from './componentEmits'
 
-export interface RawProps {
-  [key: string]: PropSource
+export type RawProps = Record<string, () => unknown> & {
   $?: DynamicPropsSource[]
 }
 
-type PropSource<T = any> = T | (() => T)
-
-type DynamicPropsSource = PropSource<Record<string, any>>
+type DynamicPropsSource =
+  | (() => Record<string, unknown>)
+  | Record<string, () => unknown>
 
 export function initStaticProps(
   comp: VaporComponent,
@@ -38,40 +37,24 @@ export function initStaticProps(
     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
-      }
+      Object.defineProperty(props, normalizedKey, {
+        enumerable: true,
+        get: needCast
+          ? () =>
+              resolvePropValue(
+                propsOptions,
+                normalizedKey,
+                source(),
+                instance,
+                resolveDefault,
+              )
+          : source,
+      })
     } else if (!isEmitListener(emitsOptions, key)) {
-      if (isFunction(source)) {
-        Object.defineProperty(attrs, key, {
-          enumerable: true,
-          get: source,
-        })
-      } else {
-        attrs[normalizedKey] = source
-      }
+      Object.defineProperty(attrs, key, {
+        enumerable: true,
+        get: source,
+      })
       hasAttrs = true
     }
   }
@@ -98,7 +81,9 @@ function resolveDefault(
 }
 
 // TODO optimization: maybe convert functions into computeds
-export function resolveSource(source: PropSource): Record<string, any> {
+export function resolveSource(
+  source: Record<string, any> | (() => Record<string, any>),
+): Record<string, any> {
   return isFunction(source) ? source() : source
 }
 
@@ -138,15 +123,18 @@ export function getDynamicPropsHandlers(
       : passThrough
 
     if (key in target) {
-      return castProp(resolveSource(target[key as string]))
+      return castProp(target[key as string]())
     }
-    if (target.$) {
-      let i = target.$.length
-      let source
+    const dynamicSources = target.$
+    if (dynamicSources) {
+      let i = dynamicSources.length
+      let source, isDynamic
       while (i--) {
-        source = resolveSource(target.$[i])
+        source = dynamicSources[i]
+        isDynamic = isFunction(source)
+        source = isDynamic ? (source as Function)() : source
         if (hasOwn(source, key)) {
-          return castProp(source[key])
+          return castProp(isDynamic ? source[key] : source[key]())
         }
       }
     }
@@ -174,12 +162,14 @@ export function getDynamicPropsHandlers(
     : null
 
   const hasAttr = (target: RawProps, key: string) => {
-    if (key === '$' || isProp(key) || isEmitListener(emitsOptions, key))
+    if (key === '$' || isProp(key) || isEmitListener(emitsOptions, key)) {
       return false
-    if (target.$) {
-      let i = target.$.length
+    }
+    const dynamicSources = target.$
+    if (dynamicSources) {
+      let i = dynamicSources.length
       while (i--) {
-        if (hasOwn(resolveSource(target.$[i]), key)) {
+        if (hasOwn(resolveSource(dynamicSources[i]), key)) {
           return true
         }
       }
@@ -201,10 +191,11 @@ export function getDynamicPropsHandlers(
     },
     ownKeys(target) {
       const keys = Object.keys(target)
-      if (target.$) {
-        let i = target.$.length
+      const dynamicSources = target.$
+      if (dynamicSources) {
+        let i = dynamicSources.length
         while (i--) {
-          keys.push(...Object.keys(resolveSource(target.$[i])))
+          keys.push(...Object.keys(resolveSource(dynamicSources[i])))
         }
       }
       return keys.filter(key => hasAttr(target, key))