Runtime compiler does not correctly generate the scope bag, and is thus non-reactive (incorrectly reactive?)
🐞 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.
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.
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?))
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]
how's that work?