Subscribe on changes!

`watch()` does not work after building for production using `npm run build`

avatar
Feb 10th 2023

Vue version

3.2.45

Link to minimal reproduction

will add zip file to this issue

Steps to reproduce

  1. Run npm run dev
  2. Observe that watch is called
  3. Run npm run build and serve using npx http-server dist
  4. Observe that watch is not called

What is expected?

watch callback gets called

What is actually happening?

watch callback does not get called

System Info

No response

Any additional comments?

creating this issue to bring attention back to (https://github.com/vuejs/core/issues/5707)

looks like the bug affects both watch() and watchEffect()

avatar
Feb 10th 2023

Creating the minimal reproduction, I've learned that it has something to do with:

  • <Suspense> in App.vue
  • The use of await in HomeView.vue
avatar
Feb 10th 2023
  • Replacing await with void in HomeView.vue makes the problem go away.
  • <Suspense> was added in App.vue because without it, the use of await in HomeView.vue gives errors.
avatar
Feb 10th 2023

This seems to be unrelated to #5707.

The problem here is that the component's props object is not being properly updated when the route changes - so the watch of course can't run, as props didn't change.

Here's what I could find out quickly:

The renderer triggers a re-render of the HomeView component, because in the component's vnode, the prop has been updated. However the propsobject in the component itself has not been updated with that new value, and so the watch is never triggered.

Addign this to HomeView:

onBeforeUpdate(() => {
        const vnodeProps = getCurrentInstance()?.vnode.props
        console.log('updating...', { ...props }, { ...vnodeProps })
    })

results in this console entry:

Bildschirm­foto 2023-02-10 um 22 40 46

So, seems to be a problem with Suspense, maybe in combination with <RouterView>.

/cc @posva does this look familiar to some problem you have seen before?

avatar
Feb 10th 2023
Bildschirm­foto 2023-02-10 um 23 00 11

patchFlag is 0, so it should receive a full props diff, but optimized is true, so line 3823 evaluates to true, so the code enters that branch - where it then does nothing because patchFlag is 0(see line 3825).

That only happens when HomeView is async. When that component is not async, optimized is false, and thus a full props patch is being done, so the app works as expected in that case.

avatar
Feb 10th 2023

Have you been able to reproduce it @LinusBorg ? Is this now a confirmed bug?

avatar
Feb 10th 2023

Yes

avatar
Feb 11th 2023

There are other bugs with Suspense and keep alive that appear because the entering component renders before the leaving component. There are some open issues in vue router about this. But I don't recall any problem with a different behavior between dev and prod

avatar
Feb 11th 2023

Got it. when calling suspense.registerDep, we don't pass the current value of optimize. Instead, in registerDep, we use the closure's value of optimized that was passed to createSusenseBoundary()

https://github.com/vuejs/core/blob/1d09540798bdb22ce6d1579688e041c8b8c3f730/packages/runtime-core/src/renderer.ts#L1231

I think we would need to change registerDep to accept the optimized flag as a third argument?

registerDep(instance, setupRenderEffect, optimized = false) {

Not sure how to write a proper test for this though.

avatar
Feb 11th 2023

Thank you for triaging this and finding a fix so quickly, much appreciated @LinusBorg ❤️

avatar
Feb 11th 2023

The difference of behavior between dev and production is caused by the optimized parameter? @LinusBorg

avatar
Feb 11th 2023

yes