Files
peter 63a0fecc8b refactor(frontend): the flat components/ folder moves into shell/, modules/<page>/ and features/<name>/
A pure move (git mv, history kept): 333 files leave components/, which now holds only the platform
layer (ui/) and the legal pages. The shell is the frame (App, header, nav rail, palette, boot); each
page lives in modules/<page>/ with its rail, store and tests; what several pages share sits in
features/<name>/; the shared-link page is watch/. Domain-free helpers went straight to ui/
(ViewSwitcher, FilterRail, the confirm dialog, the language switcher, the copy rows).

Every relative specifier was rewritten (imports, dynamic imports, vi.mock, new URL), and the
contract tests that scan sources now scan the whole app source, not only components/ — each glob
was counted before and after so none went green over fewer files. The locale-format rule matched
`/src/components/` only, so the move would have taken ~300 files out from under its i18n-singleton
check; it now covers every folder that holds components, with a test per folder.
2026-10-22 23:12:45 +02:00

43 lines
2.8 KiB
TypeScript

import react from "@vitejs/plugin-react";
import reactCompiler from "babel-plugin-react-compiler";
// ONE React transform, shared by vite.config.ts and vitest.config.ts.
//
// It is a shared factory rather than two `react()` calls because the two configs must not drift:
// if the compiler ran only in the production build, every vitest run would be exercising code the
// user never receives, and a compiler-induced behaviour change would ship green. That is precisely
// the defect `0.93.2` was released to fix — a gate and a build image that had quietly stopped being
// the same thing — so the rule here is that the gate compiles the code the same way the build does.
//
// The React Compiler auto-memoises: it infers what `useCallback`/`useMemo` were being hand-written
// for and applies the equivalent itself. It is enabled repo-wide with no opt-out list because it
// bails out on its own for anything it cannot prove safe — SILENTLY: `panicThreshold` is "none" and
// there is no logger here, so a skipped component still works and simply re-renders everything under
// it. This comment used to say "its rules are also linted"; it was false while `react-hooks/refs` was
// off, and 95 functions sat uncompiled behind it. What actually holds the line now is
// src/compilerCoverage.contract.test.ts, which runs THIS compiler over the shipped source
// and fails on any skip not on its list; the react-hooks lint rules are the early warning. None of
// them is off (three run at the preset's warn, which `--max-warnings 0` fails — docs/gates.md), but
// three of the compiler's diagnostics cannot report under the installed plugin (eslint.config.js
// says which), so the ratchet is the line, not the lint. `target` follows the installed React major and
// must move with it.
//
// The plugin is IMPORTED rather than named by the usual `"babel-plugin-react-compiler"` string. Two
// 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 <code>" 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 },
},
});