]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
chore: Merge branch 'main' into minor
authorEvan You <yyx990803@gmail.com>
Fri, 14 Jun 2024 10:32:28 +0000 (12:32 +0200)
committerEvan You <yyx990803@gmail.com>
Fri, 14 Jun 2024 10:32:28 +0000 (12:32 +0200)
31 files changed:
1  2 
CHANGELOG.md
package.json
packages/compiler-core/__tests__/transforms/vOn.spec.ts
packages/compiler-core/package.json
packages/compiler-core/src/transforms/vFor.ts
packages/compiler-core/src/utils.ts
packages/compiler-sfc/package.json
packages/compiler-sfc/src/compileScript.ts
packages/reactivity/__tests__/computed.spec.ts
packages/reactivity/__tests__/effect.spec.ts
packages/reactivity/__tests__/readonly.spec.ts
packages/reactivity/src/index.ts
packages/reactivity/src/ref.ts
packages/runtime-core/src/apiCreateApp.ts
packages/runtime-core/src/apiSetupHelpers.ts
packages/runtime-core/src/apiWatch.ts
packages/runtime-core/src/component.ts
packages/runtime-core/src/componentPublicInstance.ts
packages/runtime-core/src/components/BaseTransition.ts
packages/runtime-core/src/directives.ts
packages/runtime-core/src/hmr.ts
packages/runtime-core/src/hydration.ts
packages/runtime-core/src/index.ts
packages/runtime-core/src/renderer.ts
packages/runtime-core/src/scheduler.ts
packages/runtime-core/src/vnode.ts
packages/runtime-dom/package.json
packages/runtime-dom/src/directives/vModel.ts
packages/server-renderer/src/render.ts
packages/vue-compat/package.json
packages/vue/__tests__/e2e/Transition.spec.ts

diff --cc CHANGELOG.md
Simple merge
diff --cc package.json
index d454391c703a75da39a071acba81334c8b896ff7,6b6050d23a17fc6b3755bc122e428b4271d6b588..4205f42fe9b241e84867b173eb490b73c1fb84b9
@@@ -1,7 -1,7 +1,7 @@@
  {
    "private": true,
 -  "version": "3.4.28",
 +  "version": "3.5.0-alpha.2",
-   "packageManager": "pnpm@9.1.2",
+   "packageManager": "pnpm@9.2.0",
    "type": "module",
    "scripts": {
      "dev": "node scripts/dev.js",
index 4a285e627b191a6e1898f62ae339bb9a4b6c0b49,27d5027533b37d67abc025512a0645f95e8ea08e..66097f29f0f0600591f51c6e2411526dca5f7afb
@@@ -598,10 -599,21 +599,21 @@@ describe('compiler: transform v-on', (
            cacheHandlers: true,
          },
        )
--      expect(root.cached).not.toBe(2)
 -      expect(root.cached).toBe(1)
++      expect(root.cached.length).not.toBe(2)
 +      expect(root.cached.length).toBe(1)
      })
  
 -      expect(root.cached).toBe(0)
+     test('unicode identifier should not be cached (v-for)', () => {
+       const { root } = parseWithVOn(
+         `<div v-for="项 in items" :key="value"><div v-on:click="foo(项)"/></div>`,
+         {
+           prefixIdentifiers: true,
+           cacheHandlers: true,
+         },
+       )
++      expect(root.cached.length).toBe(0)
+     })
      test('inline function expression handler', () => {
        const { root, node } = parseWithVOn(`<div v-on:click="() => foo()" />`, {
          prefixIdentifiers: true,
Simple merge
Simple merge
Simple merge
index e2325be54d2d190ba074a4d85dc6404ebd82a62a,10c09109fdbd8153efc2e6f1f9ad8fa417965d57..dc3df3eaaea90f4bba81622ab51e3479a3e1fd59
@@@ -577,279 -615,32 +577,304 @@@ describe('reactivity/computed', () => 
  
      v.value += ' World'
      await nextTick()
 -    expect(serializeInner(root)).toBe('Hello World World World World')
 -    expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
 +    expect(serializeInner(root)).toBe('Hello World World World')
 +    // expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
 +  })
 +
 +  test('should not trigger if value did not change', () => {
 +    const src = ref(0)
 +    const c = computed(() => src.value % 2)
 +    const spy = vi.fn()
 +    effect(() => {
 +      spy(c.value)
 +    })
 +    expect(spy).toHaveBeenCalledTimes(1)
 +    src.value = 2
 +
 +    // should not trigger
 +    expect(spy).toHaveBeenCalledTimes(1)
 +
 +    src.value = 3
 +    src.value = 5
 +    // should trigger because latest value changes
 +    expect(spy).toHaveBeenCalledTimes(2)
 +  })
 +
 +  test('chained computed trigger', () => {
 +    const effectSpy = vi.fn()
 +    const c1Spy = vi.fn()
 +    const c2Spy = vi.fn()
 +
 +    const src = ref(0)
 +    const c1 = computed(() => {
 +      c1Spy()
 +      return src.value % 2
 +    })
 +    const c2 = computed(() => {
 +      c2Spy()
 +      return c1.value + 1
 +    })
 +
 +    effect(() => {
 +      effectSpy(c2.value)
 +    })
 +
 +    expect(c1Spy).toHaveBeenCalledTimes(1)
 +    expect(c2Spy).toHaveBeenCalledTimes(1)
 +    expect(effectSpy).toHaveBeenCalledTimes(1)
 +
 +    src.value = 1
 +    expect(c1Spy).toHaveBeenCalledTimes(2)
 +    expect(c2Spy).toHaveBeenCalledTimes(2)
 +    expect(effectSpy).toHaveBeenCalledTimes(2)
 +  })
 +
 +  test('chained computed avoid re-compute', () => {
 +    const effectSpy = vi.fn()
 +    const c1Spy = vi.fn()
 +    const c2Spy = vi.fn()
 +
 +    const src = ref(0)
 +    const c1 = computed(() => {
 +      c1Spy()
 +      return src.value % 2
 +    })
 +    const c2 = computed(() => {
 +      c2Spy()
 +      return c1.value + 1
 +    })
 +
 +    effect(() => {
 +      effectSpy(c2.value)
 +    })
 +
 +    expect(effectSpy).toHaveBeenCalledTimes(1)
 +    src.value = 2
 +    src.value = 4
 +    src.value = 6
 +    expect(c1Spy).toHaveBeenCalledTimes(4)
 +    // c2 should not have to re-compute because c1 did not change.
 +    expect(c2Spy).toHaveBeenCalledTimes(1)
 +    // effect should not trigger because c2 did not change.
 +    expect(effectSpy).toHaveBeenCalledTimes(1)
 +  })
 +
 +  test('chained computed value invalidation', () => {
 +    const effectSpy = vi.fn()
 +    const c1Spy = vi.fn()
 +    const c2Spy = vi.fn()
 +
 +    const src = ref(0)
 +    const c1 = computed(() => {
 +      c1Spy()
 +      return src.value % 2
 +    })
 +    const c2 = computed(() => {
 +      c2Spy()
 +      return c1.value + 1
 +    })
 +
 +    effect(() => {
 +      effectSpy(c2.value)
 +    })
 +
 +    expect(effectSpy).toHaveBeenCalledTimes(1)
 +    expect(effectSpy).toHaveBeenCalledWith(1)
 +    expect(c2.value).toBe(1)
 +
 +    expect(c1Spy).toHaveBeenCalledTimes(1)
 +    expect(c2Spy).toHaveBeenCalledTimes(1)
 +
 +    src.value = 1
 +    // value should be available sync
 +    expect(c2.value).toBe(2)
 +    expect(c2Spy).toHaveBeenCalledTimes(2)
 +  })
 +
 +  test('sync access of invalidated chained computed should not prevent final effect from running', () => {
 +    const effectSpy = vi.fn()
 +    const c1Spy = vi.fn()
 +    const c2Spy = vi.fn()
 +
 +    const src = ref(0)
 +    const c1 = computed(() => {
 +      c1Spy()
 +      return src.value % 2
 +    })
 +    const c2 = computed(() => {
 +      c2Spy()
 +      return c1.value + 1
 +    })
 +
 +    effect(() => {
 +      effectSpy(c2.value)
 +    })
 +    expect(effectSpy).toHaveBeenCalledTimes(1)
 +
 +    src.value = 1
 +    // sync access c2
 +    c2.value
 +    expect(effectSpy).toHaveBeenCalledTimes(2)
 +  })
 +
 +  it('computed should force track in untracked zone', () => {
 +    const n = ref(0)
 +    const spy1 = vi.fn()
 +    const spy2 = vi.fn()
 +
 +    let c: ComputedRef
 +    effect(() => {
 +      spy1()
 +      pauseTracking()
 +      n.value
 +      c = computed(() => n.value + 1)
 +      // access computed now to force refresh
 +      c.value
 +      effect(() => spy2(c.value))
 +      n.value
 +      resetTracking()
 +    })
 +
 +    expect(spy1).toHaveBeenCalledTimes(1)
 +    expect(spy2).toHaveBeenCalledTimes(1)
 +
 +    n.value++
 +    // outer effect should not trigger
 +    expect(spy1).toHaveBeenCalledTimes(1)
 +    // inner effect should trigger
 +    expect(spy2).toHaveBeenCalledTimes(2)
 +  })
 +
 +  // not recommended behavior, but needed for backwards compatibility
 +  // used in VueUse asyncComputed
 +  it('computed side effect should be able trigger', () => {
 +    const a = ref(false)
 +    const b = ref(false)
 +    const c = computed(() => {
 +      a.value = true
 +      return b.value
 +    })
 +    effect(() => {
 +      if (a.value) {
 +        b.value = true
 +      }
 +    })
 +    expect(b.value).toBe(false)
 +    // accessing c triggers change
 +    c.value
 +    expect(b.value).toBe(true)
 +    expect(c.value).toBe(true)
 +  })
 +
 +  it('chained computed should work when accessed before having subs', () => {
 +    const n = ref(0)
 +    const c = computed(() => n.value)
 +    const d = computed(() => c.value + 1)
 +    const spy = vi.fn()
 +
 +    // access
 +    d.value
 +
 +    let dummy
 +    effect(() => {
 +      spy()
 +      dummy = d.value
 +    })
 +    expect(spy).toHaveBeenCalledTimes(1)
 +    expect(dummy).toBe(1)
 +
 +    n.value++
 +    expect(spy).toHaveBeenCalledTimes(2)
 +    expect(dummy).toBe(2)
 +  })
 +
 +  // #10236
 +  it('chained computed should still refresh after owner component unmount', async () => {
 +    const a = ref(0)
 +    const spy = vi.fn()
 +
 +    const Child = {
 +      setup() {
 +        const b = computed(() => a.value + 1)
 +        const c = computed(() => b.value + 1)
 +        // access
 +        c.value
 +        onUnmounted(() => spy(c.value))
 +        return () => {}
 +      },
 +    }
 +
 +    const show = ref(true)
 +    const Parent = {
 +      setup() {
 +        return () => (show.value ? h(Child) : null)
 +      },
 +    }
 +
 +    render(h(Parent), nodeOps.createElement('div'))
 +
 +    a.value++
 +    show.value = false
 +
 +    await nextTick()
 +    expect(spy).toHaveBeenCalledWith(3)
 +  })
 +
 +  // case: radix-vue `useForwardExpose` sets a template ref during mount,
 +  // and checks for the element's closest form element in a computed.
 +  // the computed is expected to only evaluate after mount.
 +  it('computed deps should only be refreshed when the subscribing effect is run, not when scheduled', async () => {
 +    const calls: string[] = []
 +    const a = ref(0)
 +    const b = computed(() => {
 +      calls.push('b eval')
 +      return a.value + 1
 +    })
 +
 +    const App = {
 +      setup() {
 +        onMounted(() => {
 +          calls.push('mounted')
 +        })
 +        return () =>
 +          h(
 +            'div',
 +            {
 +              ref: () => (a.value = 1),
 +            },
 +            b.value,
 +          )
 +      },
 +    }
 +
 +    render(h(App), nodeOps.createElement('div'))
 +
 +    await nextTick()
 +    expect(calls).toMatchObject(['b eval', 'mounted', 'b eval'])
    })
 -    // computed won't trigger compute until accessed
 -    c.value
+   it('debug: onTrigger (ref)', () => {
+     let events: DebuggerEvent[] = []
+     const onTrigger = vi.fn((e: DebuggerEvent) => {
+       events.push(e)
+     })
+     const obj = ref(1)
+     const c = computed(() => obj.value, { onTrigger })
 -      effect: c.effect,
++    // computed won't track until it has a subscriber
++    effect(() => c.value)
+     obj.value++
+     expect(c.value).toBe(2)
+     expect(onTrigger).toHaveBeenCalledTimes(1)
+     expect(events[0]).toEqual({
++      effect: c,
+       target: toRaw(obj),
+       type: TriggerOpTypes.SET,
+       key: 'value',
+       oldValue: 1,
+       newValue: 2,
+     })
+   })
  })
Simple merge
index bbe613a8cbfa5baa02a10ed0822a44be7f2445a1,e47b8aa55823cb667222f7385bb308dfe6c9037e..48ada16037b0bfb2245f43059c3e77fd4a099393
@@@ -5,9 -13,7 +5,10 @@@ import 
    isFunction,
    isObject,
  } from '@vue/shared'
 +import { Dep, getDepFromReactive } from './dep'
  import {
++  type Builtin,
 +  type ShallowReactiveMarker,
    isProxy,
    isReactive,
    isReadonly,
index 12e63211a49863542bd7a51225e83810b841c806,b3373dc8662ba1fb5d8cdb9a2ba796caad663ff4..3b6c047f3cbf93f50fe8828f44b0e134b0417a8c
@@@ -54,8 -50,10 +54,11 @@@ export interface App<HostElement = any
      namespace?: boolean | ElementNamespace,
    ): ComponentPublicInstance
    unmount(): void
-   provide<T>(key: InjectionKey<T> | string, value: T): this
 +  onUnmount(cb: () => void): void
+   provide<T, K = InjectionKey<T> | string | number>(
+     key: K,
+     value: K extends InjectionKey<infer V> ? V : T,
+   ): this
  
    /**
     * Runs a function with the app as active instance. This allows using of `inject()` within the function to get access
Simple merge
index d7cfc5bea25b725fb79fabb581463ce0e2ef426f,fd227774d5ef7f97d71b183a191513831b34b241..41fce67d0c5c61af55abd23f5ee3525ba41b9338
@@@ -1,8 -1,7 +1,8 @@@
  import {
 +  type Component,
    type ComponentInternalInstance,
    type Data,
-   getExposeProxy,
+   getComponentPublicInstance,
    isStatefulComponent,
  } from './component'
  import { nextTick, queueJob } from './scheduler'
index afc7d3c1d28badca2b47b030906026006db11ae3,b2618c03a6695368e1f940e223f6b170310b8f08..c6fd28290924c8d6a3b4901e6eaf21cb3ded0da2
@@@ -26,29 -26,19 +26,29 @@@ import { mapCompatDirectiveHook } from 
  import { pauseTracking, resetTracking } from '@vue/reactivity'
  import { traverse } from './apiWatch'
  
 -export interface DirectiveBinding<V = any> {
 +export interface DirectiveBinding<
 +  Value = any,
 +  Modifiers extends string = string,
 +  Arg extends string = string,
 +> {
-   instance: ComponentPublicInstance | null
+   instance: ComponentPublicInstance | Record<string, any> | null
 -  value: V
 -  oldValue: V | null
 -  arg?: string
 -  modifiers: DirectiveModifiers
 -  dir: ObjectDirective<any, V>
 +  value: Value
 +  oldValue: Value | null
 +  arg?: Arg
 +  modifiers: DirectiveModifiers<Modifiers>
 +  dir: ObjectDirective<any, Value>
  }
  
 -export type DirectiveHook<T = any, Prev = VNode<any, T> | null, V = any> = (
 -  el: T,
 -  binding: DirectiveBinding<V>,
 -  vnode: VNode<any, T>,
 +export type DirectiveHook<
 +  HostElement = any,
 +  Prev = VNode<any, HostElement> | null,
 +  Value = any,
 +  Modifiers extends string = string,
 +  Arg extends string = string,
 +> = (
 +  el: HostElement,
 +  binding: DirectiveBinding<Value, Modifiers, Arg>,
 +  vnode: VNode<any, HostElement>,
    prevVNode: Prev,
  ) => void
  
index 08f861f6b13a1ffbaa7232a44363aa74b869d5ae,8196eb891953d9c4ea6380d47e552d28cbdabdf7..56aa3c64b157e00084f524d4892726e2198df6e7
@@@ -137,7 -138,12 +137,11 @@@ function reload(id: string, newComp: HM
        // 4. Force the parent instance to re-render. This will cause all updated
        // components to be unmounted and re-mounted. Queue the update so that we
        // don't end up forcing the same parent to re-render multiple times.
-       queueJob(instance.parent.update)
 -      instance.parent.effect.dirty = true
+       queueJob(() => {
+         instance.parent!.update()
+         // #6930 avoid infinite recursion
+         hmrDirtyComponents.delete(oldComp)
+       })
      } else if (instance.appContext.reload) {
        // root instance mounted via createApp() has a reload method
        instance.appContext.reload()
Simple merge
index 0bba1bcb0e6e0ad7245d82ac3993315103e24646,ccb89085c40969aaaeb92ce76b581d2dea476a89..49bfd6b8baeeba1a3b1b2f113b5a4dbbc7668790
@@@ -2266,7 -2273,9 +2274,9 @@@ function baseCreateRenderer
        unregisterHMR(instance)
      }
  
-     const { bum, scope, job, subTree, um } = instance
 -    const { bum, scope, update, subTree, um, m, a } = instance
++    const { bum, scope, job, subTree, um, m, a } = instance
+     invalidateMount(m)
+     invalidateMount(a)
  
      // beforeUnmount hook
      if (bum) {
@@@ -2539,3 -2543,9 +2550,10 @@@ function locateNonHydratedAsyncRoot
      }
    }
  }
 -    for (let i = 0; i < hooks.length; i++) hooks[i].active = false
+ export function invalidateMount(hooks: LifecycleHook) {
+   if (hooks) {
++    for (let i = 0; i < hooks.length; i++)
++      hooks[i].flags! |= SchedulerJobFlags.DISPOSED
+   }
+ }
index 28ebef95eef073ddef97809aa293a723e78ef4ca,4ae1c6d46e750b93eafbb1fe7269acb747867c90..1c42b1779eae72dc2c8355432f6677663b980cd9
@@@ -192,14 -185,11 +192,12 @@@ export function flushPostFlushCbs(seen?
        postFlushIndex < activePostFlushCbs.length;
        postFlushIndex++
      ) {
-       if (
-         __DEV__ &&
-         checkRecursiveUpdates(seen!, activePostFlushCbs[postFlushIndex])
-       ) {
+       const cb = activePostFlushCbs[postFlushIndex]
+       if (__DEV__ && checkRecursiveUpdates(seen!, cb)) {
          continue
        }
-       activePostFlushCbs[postFlushIndex]()
-       activePostFlushCbs[postFlushIndex].flags! &= ~SchedulerJobFlags.QUEUED
 -      if (cb.active !== false) cb()
++      if (!(cb.flags! & SchedulerJobFlags.DISPOSED)) cb()
++      cb.flags! &= ~SchedulerJobFlags.QUEUED
      }
      activePostFlushCbs = null
      postFlushIndex = 0
Simple merge
Simple merge
Simple merge