diff --git a/.changeset/babel-evaluated-number-literals.md b/.changeset/babel-evaluated-number-literals.md new file mode 100644 index 000000000..168139cb1 --- /dev/null +++ b/.changeset/babel-evaluated-number-literals.md @@ -0,0 +1,5 @@ +--- +"@solidjs/babel-plugin": patch +--- + +Evaluated negative and non-finite numbers (`c={-1}`, `d={NaN}`) are static again. Since Babel 8 support, `evaluateAndInline` built them with `t.valueToNode`, which gives `-1` / `0 / 0` expressions rather than numeric literals, so they stopped being inlined into templates (SSR emitted `ssrAttribute("c", -1)` instead of `c="-1"`) and diverged from `@solidjs/compiler` (the parity probes' "exotic literal holes"). They are now NumericLiteral nodes again, built directly where @babel/types 8's builder would reject the value; both Babel majors print them as before. diff --git a/packages/babel-plugin/src/dom/element.ts b/packages/babel-plugin/src/dom/element.ts index 148e2b10b..53f0d002c 100644 --- a/packages/babel-plugin/src/dom/element.ts +++ b/packages/babel-plugin/src/dom/element.ts @@ -11,6 +11,7 @@ import { VoidElements } from "../../../web/src/constants.js"; import { + numberLiteral, evaluateAndInline, getAttributeNamed, getTagName, @@ -997,7 +998,7 @@ function transformAttributes( ((type = typeof evaluated) === "string" || type === "number" || type === "boolean") ) { if (type === "number" && key.startsWith("prop:")) { - value = t.jsxExpressionContainer(t.valueToNode(evaluated) as babelTypes.Expression); + value = t.jsxExpressionContainer(numberLiteral(evaluated)); } else if (type === "boolean") { value = t.jsxExpressionContainer(t.booleanLiteral(evaluated)); } else value = t.stringLiteral(String(evaluated)); diff --git a/packages/babel-plugin/src/shared/utils.ts b/packages/babel-plugin/src/shared/utils.ts index ae3953e41..c4f92ece4 100644 --- a/packages/babel-plugin/src/shared/utils.ts +++ b/packages/babel-plugin/src/shared/utils.ts @@ -759,6 +759,18 @@ const templateEscapes = new Map([ ["\u2029", "\\u2029"] ]); +// A NumericLiteral for any evaluated number, so `-1` and `NaN` stay static +// literals downstream. @babel/types 8's builder rejects negative and +// non-finite values (`t.valueToNode` builds `-1` / `0 / 0` expressions +// instead), so those are built as plain nodes; generators print them as +// `-1` / `NaN` / `Infinity`, as Babel 7's builder did. +export function numberLiteral(value: number): t.NumericLiteral { + if (value >= 0 && Number.isFinite(value) && !Object.is(value, -0)) return t.numericLiteral(value); + const node = { type: "NumericLiteral", value } as t.NumericLiteral; + if (Object.is(value, -0)) node.extra = { raw: "-0", rawValue: -0 }; + return node; +} + export function evaluateAndInline( value: EvaluateAndInlineNode | null | undefined, valueNode: NodePath @@ -786,7 +798,7 @@ export function evaluateAndInline( if (typeof r.value === "string") { valueNode.replaceWith(t.stringLiteral(r.value)); } else if (typeof r.value === "number") { - valueNode.replaceWith(t.valueToNode(r.value)); + valueNode.replaceWith(numberLiteral(r.value)); } else if (typeof r.value === "boolean") { valueNode.replaceWith(t.booleanLiteral(r.value)); } diff --git a/packages/babel-plugin/test/numeric-literals.spec.js b/packages/babel-plugin/test/numeric-literals.spec.js index 57067a3ef..f785c13b8 100644 --- a/packages/babel-plugin/test/numeric-literals.spec.js +++ b/packages/babel-plugin/test/numeric-literals.spec.js @@ -26,3 +26,32 @@ describe("evaluated numeric literals", () => { } }); }); + +describe("evaluated numeric attributes stay static", () => { + const attrs = `const a =
; +const b = ;`; + const compile = generate => + babel.transformSync(attrs, { + plugins: [[plugin, { generate, moduleName: "r-dom" }]], + configFile: false, + babelrc: false, + filename: "numeric.jsx" + }).code; + + test("dom", () => { + const code = compile("dom"); + expect(code).toContain("
"); + expect(code).toContain("_el$2.value = -1;"); + }); + + test("ssr", () => { + expect(compile("ssr")).toContain('c=\\"-1\\" d=\\"NaN\\" e=\\"0\\"'); + }); + + test("universal", () => { + const code = compile("universal"); + expect(code).toContain("c: -1"); + expect(code).toContain("d: NaN"); + expect(code).toContain("e: -0"); + }); +});