diff --git a/frontend/reactPlugin.ts b/frontend/reactPlugin.ts index 044a1da6..ce3ddc66 100644 --- a/frontend/reactPlugin.ts +++ b/frontend/reactPlugin.ts @@ -18,9 +18,17 @@ import reactCompiler from "babel-plugin-react-compiler"; // reasons, both about the gate: a string is invisible to knip, which then reports the dependency as // unused, and — worse — a typo in that string is a silent no-op that ships an uncompiled bundle, // while a bad import is a resolve error on the tsc lane. +// +// The price of the import: plugin-react finds the compiler only by that string, so it doesn't know the +// compiler is there. Its one compiler-specific switch is Babel's `retainLines`, which it turns on for +// dev (and so for vitest) unless the compiler is present. With the compiler's re-printed JSX that eats +// the space at the end of a text line ("for example " rendered "for example/api/feed"). The +// production build never had it on. `generatorOpts` wins over the top-level option, so it is switched +// off here, for every mode. Pinned by src/jsxWhitespace.dom.test.tsx. export const reactWithCompiler = () => react({ babel: { plugins: [[reactCompiler, { target: "19" }]], + generatorOpts: { retainLines: false }, }, }); diff --git a/frontend/src/jsxWhitespace.dom.test.tsx b/frontend/src/jsxWhitespace.dom.test.tsx new file mode 100644 index 00000000..5fd0021e --- /dev/null +++ b/frontend/src/jsxWhitespace.dom.test.tsx @@ -0,0 +1,35 @@ +// @vitest-environment jsdom +// +// The React transform must not eat the space between a word and the element or expression that +// follows it on the same line. It did, in dev and in this test lane (never in the production build): +// @vitejs/plugin-react only recognises the React Compiler by its package NAME, and reactPlugin.ts +// passes the imported plugin, so the React plugin kept Babel's `retainLines` on. The compiler then +// printed "for example " and the next element on different lines, and the JSX transform trimmed the +// line-end space ("for example/api/feed" on the privacy page). reactPlugin.ts now turns +// `retainLines` off itself. The component goes through the same transform the app does. +import { describe, it, expect, afterEach } from "vitest"; +import { render, cleanup } from "@testing-library/react"; + +afterEach(cleanup); + +function Sentence({ value }: { value: string }) { + // The line breaks ARE the test: collapsed onto one line, the defect doesn't fire. + // prettier-ignore + return ( +

+ which part of the app was called (for example /api/feed, never the search + + status: {value} end + +

+ ); +} + +describe("JSX text whitespace through the React transform", () => { + it("keeps the space before an element and before an expression", () => { + const { container } = render(); + expect(container.textContent).toBe( + "which part of the app was called (for example /api/feed, never the searchstatus: ok end", + ); + }); +});