The React Compiler silently skipped Channels and DataTable (a ref written during render; the refs lint is off), and even once compiled it dropped the memo of the rows and columns (they reach filterRows/optionsFromColumns past the page's hooks) and the columns read the open row. Every open, close and reposition of the right-click menu rebuilt the whole table: on "All" rows ~0.9 s to open and ~0.6 s to close (dev). Now: effect events instead of the render-written refs, explicit memos for rows, columns and their inputs, a module-scope keyOf, and the open row reaches its "…" through a small store. Measured on 397 rows (dev): ~75 ms to open, ~35 ms to close. A contract test runs the real compiler over both files and fails on any skipped function. The dropped memos have no automated guard: they need the full page rendered with its providers; the measurement above is the evidence.
67 lines
2.6 KiB
TypeScript
67 lines
2.6 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { transformSync } from "@babel/core";
|
|
import reactCompiler from "babel-plugin-react-compiler";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
// The React Compiler bails out of a component it cannot prove safe WITHOUT a word: the component
|
|
// still works, it just re-renders everything under it on every render. Its main tripwire, a ref read
|
|
// or written during render, is not linted here (`react-hooks/refs` is off, see eslint.config.js), so
|
|
// nothing caught it. The channel manager is where it hurt (the owner, 2026-10-03): Channels and
|
|
// DataTable both bailed out, every open, close and reposition of a row's right-click menu rebuilt
|
|
// the rows and columns, and on "All" rows the menu took ~0.9 s to open and ~0.6 s to close.
|
|
//
|
|
// This runs the real compiler (the options reactPlugin.ts gives it) over the files that must stay
|
|
// compiled and fails on any function it skips, with the compiler's own reason.
|
|
const MUST_COMPILE = ["components/Channels.tsx", "components/ui/DataTable.tsx"];
|
|
|
|
function bailouts(path: string): string[] {
|
|
const source = readFileSync(new URL(`../${path}`, import.meta.url), "utf8");
|
|
const found: string[] = [];
|
|
transformSync(source, {
|
|
filename: path,
|
|
babelrc: false,
|
|
configFile: false,
|
|
parserOpts: { plugins: ["jsx", "typescript"] },
|
|
plugins: [
|
|
[
|
|
reactCompiler,
|
|
{
|
|
target: "19",
|
|
logger: {
|
|
logEvent: (
|
|
_file: string | null,
|
|
e: { kind: string; fnLoc?: unknown; detail?: unknown },
|
|
) => {
|
|
if (
|
|
e.kind !== "CompileError" &&
|
|
e.kind !== "CompileSkip" &&
|
|
e.kind !== "PipelineError"
|
|
)
|
|
return;
|
|
const loc = e.fnLoc as { start?: { line?: number } } | undefined;
|
|
const d = e.detail as
|
|
| { options?: { reason?: string; details?: { message?: string }[] } }
|
|
| { reason?: string; details?: { message?: string }[] }
|
|
| undefined;
|
|
const o =
|
|
d && "options" in d
|
|
? d.options
|
|
: (d as { reason?: string; details?: { message?: string }[] });
|
|
found.push(
|
|
`line ${loc?.start?.line}: ${o?.details?.[0]?.message ?? o?.reason ?? e.kind}`,
|
|
);
|
|
},
|
|
},
|
|
},
|
|
],
|
|
],
|
|
});
|
|
return found;
|
|
}
|
|
|
|
describe("the React Compiler", () => {
|
|
it.each(MUST_COMPILE)("compiles every function in %s", (path) => {
|
|
expect(bailouts(path)).toEqual([]);
|
|
});
|
|
});
|