Template refs to non-Ref class instances error in production builds, but not during development
Vue version
3.5.8
Link to minimal reproduction
https://play.vuejs.org/#__PROD__eNp9UsFq4zAQ/ZVZXZJCcA7dwBLSQroEtntolm5gL4JFlceJWlkSGtlOCPn3HdlpmsNSX6x574385o2PYhlC0TYo5mJBOpqQgDA14V666RQ2O0OgfYlQeiRwPu2M24JxUGILL42xJU34naBSxlImQvTlmZlLp60igiUcT3z2jhIEuAOHHSzHN9L1H/mzUwmUJQ8Yo4+5jdGsK71uanSp0BFVwpXFXI1HYcS9Z01nXOm7vnw4QJfvegStHI+BbE2R0craAyh3GMynLOGxWGLYkHIawVegYPCqXAlP6w3Xz1gV1xZzBG6UBpcfJmeX0xHUHGbAo56BiNX4BvLDwPqlNb4htsI+CDof3+iLdIvpEDsHzkXCOlgelSuARcg33EkRpLj/gdb6xTQvZjG9komJSMTRVmZbvJJ3vMhjbpZC+zoYi3EdkuHopZhDz2SOI/Hdzx5LscHJO653qN/+g7/SPmNS/IpIGFuU4sIlFbeYBnr1+wn3fL6QtS8by+pPyGckb5vscZA9NK5k21e63u1jHXxMvMENrfYJHb0PlY1m5anXS8F/8/dPRv+we1t87fukO3GKf1uM+U4O8LaYFd/E6R/KqQUI
Steps to reproduce
Run the reproduction, it explains the issue with comments:
<script setup>
// This code does nothing in dev builds, but fails in prod builds:
class A {}
const p = new A()
// What also errors:
// p = document.createElement('p')
// p = window
// By what I can see, basically anything that is an instance of a class and NOT a Ref.
// What doesn't error:
// p = 5
// p = { a: 5 }
// p = ref() // Obviously this works!
</script>
<template>
<p ref="p">Hello</p>
</template>
The code works fine in dev mode, but breaks in prod builds.
In this case, the component renders successfully in both cases (albeit with the error in prod); however, in actual projects of mine this has typically nuked the entire page.
What is expected?
Template refs pointing to something other than an actual vue Ref is not intentional code. This kind of code has happened in my projects a few times due to oversight; basically due to coding errors. The problem here is that the error is silent during development - the ref might be wrong, but it does not nuke the component and just fails silently. So you never fix it.
Consistency is expected here. I argue that this should fail also in dev, albeit with a nicer error message. It should point out that you have an element that's template ref'd to an object that doesn't make sense.
Alternatively, it should ignore the issue silently in production builds, just like it does in dev now.
What is actually happening?
In dev, the issue is ignored silently and with no repercussions, but in prod it fails loud.
System Info
System:
OS: macOS 14.6.1
CPU: (10) arm64 Apple M1 Pro
Memory: 116.27 MB / 16.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 18.19.0 - /opt/homebrew/opt/node@18/bin/node
Yarn: 1.22.22 - /opt/homebrew/opt/node@18/bin/yarn
npm: 10.2.3 - /opt/homebrew/opt/node@18/bin/npm
pnpm: 8.6.11 - ~/.local/share/pnpm/pnpm
bun: 1.1.4 - ~/.bun/bin/bun
Browsers:
Chrome: 129.0.6668.59
Safari: 17.6
Any additional comments?
No response
duplicate of https://github.com/vuejs/core/issues/11373
@edison1105 Are you sure this is a duplicate? I've looked through #11373, and also through #4866 (linked there), and both of these issues reference situations that use dynamically assigned template refs, i.e. code that uses :ref="x".
This is NOT what is going on here in my issue. I have a hardcoded reference to p in my code; perfectly statically analyzable. This issue has (at least by what I can tell) nothing to do with the "design flaw" Evan is referring to in the other two issues.
This issue is similar to the other two in the sense that it concerns template refs and dev/prod build differences, however the bug itself feels different. Maybe I'm wrong here and they are related; could you elaborate how this issue and the other two are caused by the same defect?
This here seems to be the code causing the divergence: (EDIT: It's not?)
I think it's destructuring owner from the rawRef here, which in my case (new A()) is obviously undefined. Thankfully in non-prod, the code catches this returns out of the function. However, in prod it continues and then runs into an error.
Is there a reason why it continues in production? Is there any scenario in which !owner is true and the code that follows it is still well-behaved?
Perhaps this is the intended code:
if (!owner) {
if (process.env.NODE_ENV !== "production") {
warn$1(
`Missing ref owner context. ref cannot be used on hoisted vnodes. A vnode with ref must be created inside the render function.`
);
}
return;
}
You can switch between DEV and PROD to inspect the compiled code.
At runtime
- in the DEV environment, set the ref value using
setupState["refName"] = el. - in the PROD environment, there is no
setupStateobject, so the ref value is set usingref.value = el. However,const p = new A()is analyzed assetup-maybe-ref, but it is not a ref. This results in inconsistencies between DEV and PROD environments. While this inconsistency differs somewhat from #11373 and #4866, fundamentally, it is also a design flaw.
Developers should follow the documentation
- declare a ref to hold the element reference
- useTemplateRef
Looked at #12031 and I think the correct change should be detecting such usage during development (when setting the template ref, if the key exists on setupState but is not a ref) and emit a warning.