94 lines · 3.6 KB · JavaScript
RawDownload
#!/usr/bin/env node/** * Report React Compiler bailouts for the app's hottest components. * * The compiler silently skips any function it cannot prove safe (see the * comment in vite.config.ts), which for a long time included App, * DocumentCanvas, and the visual markdown editor — the exact components whose * re-render cost dominates typing latency. This script runs the same Babel * plugin the build uses and prints every CompileError with its location, so * bailouts are visible instead of silent. `react-compiler-guard.test.ts` * wires it into the test suite to keep the hot files at zero bailouts. * * Usage: node scripts/react-compiler-report.mjs [files...] */import fs from "node:fs";import { createRequire } from "node:module";import path from "node:path";import { isMain, projectRoot } from "./lib/util.mjs"; const REPO = projectRoot;// @babel/core is a transitive dep (of @vitejs/plugin-react) under pnpm, so// resolve it from the package that declares it — via the real path, since the// hoisted entry is a symlink and module lookup walks the physical tree.const require = createRequire(import.meta.url);const pluginReactRequire = createRequire(  path.join(fs.realpathSync(path.join(REPO, "node_modules/@vitejs/plugin-react")), "package.json"),);const { transformFileSync } = pluginReactRequire("@babel/core");const reactCompiler = require("babel-plugin-react-compiler");const linguiMacro = require("@lingui/babel-plugin-lingui-macro"); const GUARDED_FILES = [  "src/App.tsx",  "src/canvas/document-canvas.tsx",  "src/editor/markdown/engine/lattice-visual-editor.tsx",  "src/pdf/pdf-viewer.tsx",]; /** * @param {string} relative repo-relative file path * @returns {{ fn: string; line: number; reason: string }[]} */function compileErrors(relative) {  const events = [];  transformFileSync(path.join(REPO, relative), {    configFile: false,    babelrc: false,    // Same shape as @vitejs/plugin-react: the compiler runs over the raw TSX    // AST (parser-level typescript/jsx support, no TS-stripping preset).    parserOpts: { plugins: ["typescript", "jsx"] },    // As in vite.config.ts, Lingui macros expand before the compiler runs: it    // bails out on a raw `t` tagged template with interpolations, which the    // build never shows it.    plugins: [linguiMacro, [reactCompiler, {      target: "19",      // As in vite.config.ts and vitest.config.ts; see      // scripts/react-compiler-suppressions.ts.      eslintSuppressionRules: ["react-hooks-js/exhaustive-deps", "react-hooks-js/rules-of-hooks"],      logger: { logEvent: (_filename, event) => events.push(event) },    }]],    code: false,    ast: false,  });  return events    .filter((event) => event.kind === "CompileError")    .map((event) => ({      fn: event.fnName ?? "(anonymous)",      line: event.fnLoc?.start?.line ?? 0,      at: event.detail?.loc?.start?.line        ?? event.detail?.options?.loc?.start?.line        ?? event.detail?.details?.[0]?.loc?.start?.line        ?? 0,      reason: String(        event.detail?.reason        ?? event.detail?.options?.reason        ?? event.detail?.description        ?? "unknown",      ),    }));} if (isMain(import.meta.url)) {  const files = process.argv.slice(2).length ? process.argv.slice(2) : GUARDED_FILES;  let total = 0;  for (const file of files) {    const errors = compileErrors(file);    total += errors.length;    console.log(`${file}: ${errors.length} bailout(s)`);    for (const error of errors) {      console.log(`  L${error.line} ${error.fn} (at L${error.at}): ${error.reason}`);    }  }  process.exitCode = total > 0 ? 1 : 0;}