ember.js icon indicating copy to clipboard operation
ember.js copied to clipboard

Runtime compiler does not correctly generate the scope bag, and is thus non-reactive (incorrectly reactive?)

Open NullVoxPopuli opened this issue 8 months ago • 4 comments

🐞 Describe the Bug

Been playing around in here: https://github.com/emberjs/ember.js/pull/20907 (because you can't catch a compiler error due to the usage of the microtask queue)

And it seems that the underlying glimmer-format for components just needs to change. For example, What is generated:

source: '({"id":null,"block":"[[[1,[32,0]]],[],[]]","moduleName":"(unknown template module)","scope":()=>[greeting],"isStrictMode":true})'

but this means that greeting is already consumed. Which we know from the template-compiler code, here:

    let scope = untrack(() => options.scope?.());

    if (!scope) {
      return evaluator;
    }

    return (source: string) => {
      const argNames = Object.keys(scope ?? {});
      const argValues = Object.values(scope ?? {});

      return new Function(...argNames, `return (${source})`)(...argValues);
    };

here, I added untrack because we should not be touching scope or any reactive values before a render occurs (else, we just re-run things over and over again).

The problem is obviously:

"scope":()=>[greeting]

when the input is:

return template(`{{greeting}}`, {
  scope: () => {
    return { greeting: this.str };
  },
});

so... what should happen is obviously just... not mutating the shape of the scope bag

instead of:

"scope":()=>[greeting]

we need to take the scope that was passed to template, and use that directly.

like this:

// in the template compiler
export function template(source, { scope }) {
  // .... 

  // using old terminology here, cause I don't remember what the new stuff is
  return setComponentTemplate(
    {
      block: '[...]',
      scope,
      isStrictMode: true, 
    },
    templateOnly(),
  );
}
  • Node.js/npm: -
  • OS: -
  • Browser: -

➕ Additional Context

Add any other context about the problem here.

NullVoxPopuli avatar May 30 '25 13:05 NullVoxPopuli

If scope names are dynamic, you do need to version the list of keys, but it should be able to just host/pass through values a unconsumed.

krisselden avatar Jun 02 '25 23:06 krisselden

I don't think scope names can be dynamic -- else it'd require re-compiling the component, which I think is fine (but also, how do you do dynamic scope in JS? (lots of eval?))

NullVoxPopuli avatar Jun 03 '25 02:06 NullVoxPopuli

The runtime template compiler is not subject to the same limitations as the ahead-of-time template compiler. There's no reason all of it needs to go through new Function(...). And we can emit the scope-bag-consuming expressions directly inside the closure like () => [scopeBag.foo]

ef4 avatar Jun 03 '25 18:06 ef4

how's that work?

NullVoxPopuli avatar Jun 03 '25 18:06 NullVoxPopuli