Accept literal scope values produced by bundler constant inlining - #117
Conversation
453a3b6 to
7431498
Compare
|
Rebased onto main now that #118 is in. The rebase surfaced one real interaction: under Ember 7's object-form wire scope, the literal substitution in |
| // name | ||
| path.replaceWith(babel.types.identifier(scopeLocals.get(path.node.name))); | ||
| if (scopeLocals.has(path.node.name)) { | ||
| // Ember 7's wire format emits the scope as an object whose values are |
There was a problem hiding this comment.
why is this here / is it important?
| .map(([name, identifier]) => | ||
| t.objectProperty(t.identifier(name), t.identifier(identifier), false, name !== 'this') | ||
| .map(([name, value]) => | ||
| typeof value === 'string' |
|
|
||
| // the expression shapes a bundler's constant inlining can substitute for a | ||
| // reference to an imported constant | ||
| function isLiteralValue(node: t.ObjectProperty['value']): node is t.Expression { |
There was a problem hiding this comment.
return type is too wide
There was a problem hiding this comment.
does babel have a type for all literal types
ef4
left a comment
There was a problem hiding this comment.
Writing down the things we discussed on the embroider weekly meeting so I don't forget them.
| // Each hbs name in the template's scope maps to either the name of a JS | ||
| // binding or, when a bundler's constant inlining has replaced the original | ||
| // binding with its statically-known value, a literal expression. | ||
| #mapping: Record<string, string | t.Expression> = {}; |
There was a problem hiding this comment.
Either we support arbitrary expressions (in which case the separate string case is unnecessary) or we don't (in which case the t.Expression type is a lie). The implementation right now is half-way between the two.
| // name | ||
| path.replaceWith(babel.types.identifier(scopeLocals.get(path.node.name))); | ||
| if (scopeLocals.has(path.node.name)) { | ||
| // Ember 7's wire format emits the scope as an object whose values are |
There was a problem hiding this comment.
This comment is concerning because this PR is supposed to have nothing to do with ember 7 support and because ember 7 support changes should be test-suite-only here, not effecting the actual implementation.
|
|
||
| // the expression shapes a bundler's constant inlining can substitute for a | ||
| // reference to an imported constant | ||
| function isLiteralValue(node: t.ObjectProperty['value']): node is t.Expression { |
There was a problem hiding this comment.
Type signatures doesn't match implementation. And implementation is probably not needed because babel already has type discrimination functions like this.
7431498 to
d4e281d
Compare
|
Addressed the review feedback; rebased, so this now sits on top of #120. @ef4's point about ember 7 in the implementation — you were right that it didn't belong here. Two separate things were bundled into that block:
With #120 as the base, this PR is literals-only again. @ef4 on export type LiteralScopeValue =
| Exclude<t.Literal, t.RegExpLiteral | t.TemplateLiteral>
| SignedNumericLiteral;
export type ScopeValue = string | LiteralScopeValue;We don't support arbitrary expressions, so the type now says exactly what we do support. @NullVoxPopuli on "does babel have a type for all literal types" — yes, @NullVoxPopuli's "why string?" on Full suite green locally (133 tests). |
14edde9 to
0aa9cfb
Compare
d4e281d to
bc767bf
Compare
10da85d to
d39da4d
Compare
bc767bf to
5268733
Compare
ac86529 to
116f7d5
Compare
5268733 to
7351892
Compare
b3ce77e to
0e17675
Compare
A bundler's constant inlining (e.g. rolldown's default
optimization.inlineConst) can legally rewrite
import { EMPTY } from './constants.js';
precompileTemplate('{{EMPTY}}', { scope: () => ({ EMPTY }) })
into
precompileTemplate('{{EMPTY}}', { scope: () => ({ EMPTY: "-" }) })
before this plugin ever sees the module, which parseScope rejected with
"may only contain direct references to in-scope values". This is the
literal-value analog of the renamed-identifier case fixed in 2.0.2: the
scope property still carries everything the compiler needs, the
template-visible name and the value it resolves to.
parseScope now accepts literal values (string, number, boolean, null,
bigint, and signed numbers), ScopeLocals carries them as expressions
alongside plain identifier names, wire-format output emits the literal
directly into the scope array, and hbs-format output re-emits it in the
rebuilt scope object.
Fixes emberjs#116
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
7351892 to
7edd354
Compare
Fixes #116
Based on #120 — please review that first; this PR is stacked on top of it.
A bundler's constant inlining — e.g. rolldown's default
optimization.inlineConst— can legally rewriteinto
before this plugin ever sees the module (typically in a v2 addon published with
targetFormat: 'hbs'), andparseScopethen fails the consuming app's build with "Scope objects forprecompileTemplatemay only contain direct references to in-scope values … Found StringLiteral".This is the literal-value analog of #14: rollup rewrote
{ FieldComponent }to{ FieldComponent: HeadlessFormFieldComponent }and #17/#19 taughtparseScopeto accept renamed identifier references. A literal-valued scope property likewise still carries everything the compiler needs — the template-visible name and the value it resolves to.Changes
parseScopeaccepts constant values:LiteralScopeValueist.Literalminus regexes (a regex literal would construct a new stateful object per call) and template literals (they can interpolate arbitrary bindings), plus-/+-signed numbers, which areUnaryExpressions rather than literals. Anything else still errors, with the message extended to mention literals.ScopeLocalsmapping values are nowScopeValue = string | LiteralScopeValue, so an entry can carry the constant instead of a JS identifier name.remapAndBindIdentifiersreplaces the local's identifier in the compiler output with the value, producingscope: () => ["—"].targetFormat: 'hbs':buildScopere-emits the value in the rebuilt scope object, so the output round-trips through a later wire compile.localsthe same way identifier entries are.Tests cover both target formats, pruning, the still-rejected
CallExpressioncase, and the deliberately-excluded template-literal and regex shapes.🤖 Generated with Claude Code