Subscribe on changes!

Local variables references in dev script setup don't work

avatar
May 16th 2022

Link to minimal reproduction

https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCB7IHJlZiB9IGZyb20gJ3Z1ZSdcblxubGV0IHggPSAwO1xuZnVuY3Rpb24gaW5jKCkgeyB4KysgfVxuZnVuY3Rpb24gc2hvdygpIHsgYWxlcnQoJ3ggaXMgJyArIHgpIH1cbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG4gIDxkaXY+XG4gICAgPGJ1dHRvbiBAY2xpY2s9aW5jPkluYyAod29ya3MpPC9idXR0b24+ICAgIFxuICAgIDxidXR0b24gQGNsaWNrPXgrKz5JbmMgKGRvZXMgbm90IHdvcmspPC9idXR0b24+XG4gICAgPGJ1dHRvbiBAY2xpY2s9c2hvdz5TaG93IHggdmFsdWUgKGNvcnJlY3QpPC9idXR0b24+XG4gIDwvZGl2PiAgICBcbjwvdGVtcGxhdGU+IiwiaW1wb3J0LW1hcC5qc29uIjoie1xuICBcImltcG9ydHNcIjoge1xuICAgIFwidnVlXCI6IFwiaHR0cHM6Ly9zZmMudnVlanMub3JnL3Z1ZS5ydW50aW1lLmVzbS1icm93c2VyLmpzXCJcbiAgfVxufSJ9

Steps to reproduce

In dev mode, try to use a local variable that is not a reference, i.e. a plain value, not an object, nor array nor ref, directly from a script setup template.

The linked repro is a repro, but not inside the SFC playground because it compiles to non-dev!

The repro has two buttons that increment a local variable, and one to show its value in an alert box. Buttons that go through functions with a capture inside setup function work. Button that manipulate the variable directly don't.

What is expected?

In build mode, which is what SFC playground runs, clicking Inc buttons increment the value by 1, as can be visualised with the third button.

What is actually happening?

In dev mode, the non-working button (the one without a local function that captures the x variable, does not increment the value. Neither would it read the correct value if that was a case.

The reason is that in dev mode, for the dev tools, a regular script setup object is built like so:

 const __returned__ = { props, emit, x };

Of course, this just puts a copy of the current value of local variable in the setup object. Any reference to this x value in template is totally disconnected from the variable in code.

To clarify why this works in build mode, this is because the SFC compiler doesn't use a setup object at all but rather creates a render function that immediately closes over the local variables, like so:

let x = 0;

return (_ctx, _cache) => {
  return (_openBlock(), _createElementBlock("div", null, [
    _createElementVNode("button", {
      onClick: _cache[0] || (_cache[0] = $event => (_isRef(x) ? x.value++ : x++))
    }, "Inc (does not work)"),
  ]))

System Info

System:
    OS: Windows 10 10.0.19042
    CPU: (12) x64 Intel(R) Core(TM) i7-9850H CPU @ 2.60GHz
    Memory: 4.68 GB / 15.79 GB
  Binaries:
    Node: 16.6.0 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.10 - ~\AppData\Roaming\npm\yarn.CMD
    npm: 7.19.1 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Spartan (44.19041.1266.0), Chromium (101.0.1210.39), ChromiumDev (103.0.1253.0)
    Internet Explorer: 11.0.19041.1566
  npmPackages:
    vue: ^3.2.33 => 3.2.33

Any additional comments?

This might be a bit ugly to fix, I see two solutions:

  1. The dev-mode setup object should contain get/set properties that actually proxy local variables that are let not const.

  2. User JS code is rewritten so that all returned let local variables live on the __returned__ object from the start, and all setup code referencing these variables is rewritten to dereference that object. That requires modifying the user code so it's quite more tricky than option 1.