@samitouri / QOS-React-2 / commits / 33999c4317

[compiler][be] Test runner (snap) now uses tsup bundled plugin (#32758)

Currently, `babel-plugin-react-compiler` is bundled with (almost) all external dependencies. This is because babel traversal and ast logic is not forward-compatible. Since `babel-plugin-react-compiler` needs to be compatible with babel pipelines across a wide semvar range, we (1) set this package's babel dependency to an early version and (2) inline babel libraries into our bundle. A few other packages in `react/compiler` depend on the compiler. This PR moves `snap`, our test fixture compiler and evaluator, to use the bundled version of `babel-plugin-react-compiler`. This decouples the babel version used by `snap` with the version used by `babel-plugin-react-compiler`, which means that `snap` now can test features from newer babel versions (see https://github.com/facebook/react/pull/32742). --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32758). * #32759 * __->__ #32758

mofeiZ committed Mar 26, 2025 at 14:27 UTC 33999c43177e13580730c2fad94a77f4b0e08ef2
11 files changed +105 -85
compiler/packages/babel-plugin-react-compiler/package.json
+1 -1
@@ -12,7 +12,7 @@
12 "build": "rimraf dist && tsup",
13 "test": "./scripts/link-react-compiler-runtime.sh && yarn snap:ci",
14 "jest": "yarn build && ts-node node_modules/.bin/jest",
15 - "snap": "node ../snap/dist/main.js",
15 + "snap": "yarn workspace snap run snap",
16 "snap:build": "yarn workspace snap run build",
17 "snap:ci": "yarn snap:build && yarn snap",
18 "ts:analyze-trace": "scripts/ts-analyze-trace.sh",
compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts
+1 -1
@@ -32,5 +32,5 @@ export {
32 } from './HIRBuilder';
33 export {mergeConsecutiveBlocks} from './MergeConsecutiveBlocks';
34 export {mergeOverlappingReactiveScopesHIR} from './MergeOverlappingReactiveScopesHIR';
35 -export {printFunction, printHIR} from './PrintHIR';
35 +export {printFunction, printHIR, printFunctionWithOutlined} from './PrintHIR';
36 export {pruneUnusedLabelsHIR} from './PruneUnusedLabelsHIR';
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts
+4 -1
@@ -14,7 +14,10 @@ export {extractScopeDeclarationsFromDestructuring} from './ExtractScopeDeclarati
14 export {inferReactiveScopeVariables} from './InferReactiveScopeVariables';
15 export {memoizeFbtAndMacroOperandsInSameScope} from './MemoizeFbtAndMacroOperandsInSameScope';
16 export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesThatInvalidateTogether';
17 -export {printReactiveFunction} from './PrintReactiveFunction';
17 +export {
18 + printReactiveFunction,
19 + printReactiveFunctionWithOutlined,
20 +} from './PrintReactiveFunction';
21 export {promoteUsedTemporaries} from './PromoteUsedTemporaries';
22 export {propagateEarlyReturns} from './PropagateEarlyReturns';
23 export {pruneAllReactiveScopes} from './PruneAllReactiveScopes';
compiler/packages/babel-plugin-react-compiler/src/index.ts
+5 -1
@@ -32,13 +32,17 @@ export {
32 ValueKind,
33 parseConfigPragmaForTests,
34 printHIR,
35 + printFunctionWithOutlined,
36 validateEnvironmentConfig,
37 type EnvironmentConfig,
38 type ExternalFunction,
39 type Hook,
40 type SourceLocation,
41 } from './HIR';
41 -export {printReactiveFunction} from './ReactiveScopes';
42 +export {
43 + printReactiveFunction,
44 + printReactiveFunctionWithOutlined,
45 +} from './ReactiveScopes';
46 declare global {
47 let __DEV__: boolean | null | undefined;
48 }
compiler/packages/babel-plugin-react-compiler/tsconfig.json
+1 -1
@@ -4,7 +4,7 @@
4 "module": "ES2015",
5 "moduleResolution": "Bundler",
6 "rootDir": "src",
7 - "outDir": "dist",
7 + "noEmit": true,
8 "jsx": "react-jsxdev",
9 // weaken strictness from preset
10 "importsNotUsedAsValues": "remove",
compiler/packages/snap/package.json
+2 -2
@@ -11,6 +11,7 @@
11 "scripts": {
12 "postinstall": "./scripts/link-react-compiler-runtime.sh && perl -p -i -e 's/react\\.element/react.transitional.element/' ../../node_modules/fbt/lib/FbtReactUtil.js && perl -p -i -e 's/didWarnAboutUsingAct = false;/didWarnAboutUsingAct = true;/' ../../node_modules/react-dom/cjs/react-dom-test-utils.development.js",
13 "build": "rimraf dist && concurrently -n snap,runtime \"tsc --build\" \"yarn --silent workspace react-compiler-runtime build\"",
14 + "snap": "node dist/main.js",
15 "test": "echo 'no tests'",
16 "prettier": "prettier --write 'src/**/*.ts'"
17 },
@@ -40,8 +41,7 @@
41 },
42 "devDependencies": {
43 "@babel/core": "^7.19.1",
43 - "@babel/parser": "^7.19.1",
44 - "@babel/plugin-syntax-typescript": "^7.18.6",
44 + "@babel/parser": "^7.20.15",
45 "@babel/plugin-transform-modules-commonjs": "^7.18.6",
46 "@babel/preset-react": "^7.18.6",
47 "@babel/traverse": "^7.19.1",
compiler/packages/snap/src/constants.ts
+9 -27
@@ -9,35 +9,17 @@ import path from 'path';
9
10 // We assume this is run from `babel-plugin-react-compiler`
11 export const PROJECT_ROOT = path.normalize(
12 - path.join(process.cwd(), '..', '..'),
12 + path.join(process.cwd(), '..', 'babel-plugin-react-compiler'),
13 );
14 -export const COMPILER_PATH = path.join(
15 - process.cwd(),
16 - 'dist',
17 - 'Babel',
18 - 'BabelPlugin.js',
19 -);
20 -export const COMPILER_INDEX_PATH = path.join(process.cwd(), 'dist', 'index');
21 -export const PRINT_HIR_PATH = path.join(
22 - process.cwd(),
23 - 'dist',
24 - 'HIR',
25 - 'PrintHIR.js',
26 -);
27 -export const PRINT_REACTIVE_IR_PATH = path.join(
28 - process.cwd(),
29 - 'dist',
30 - 'ReactiveScopes',
31 - 'PrintReactiveFunction.js',
32 -);
33 -export const PARSE_CONFIG_PRAGMA_PATH = path.join(
34 - process.cwd(),
35 - 'dist',
36 - 'HIR',
37 - 'Environment.js',
14 +
15 +export const PROJECT_SRC = path.normalize(
16 + path.join(PROJECT_ROOT, 'dist', 'index.js'),
17 );
18 +export const PRINT_HIR_IMPORT = 'printFunctionWithOutlined';
19 +export const PRINT_REACTIVE_IR_IMPORT = 'printReactiveFunction';
20 +export const PARSE_CONFIG_PRAGMA_IMPORT = 'parseConfigPragmaForTests';
21 export const FIXTURES_PATH = path.join(
40 - process.cwd(),
22 + PROJECT_ROOT,
23 'src',
24 '__tests__',
25 'fixtures',
@@ -45,4 +27,4 @@ export const FIXTURES_PATH = path.join(
27 );
28 export const SNAPSHOT_EXTENSION = '.expect.md';
29 export const FILTER_FILENAME = 'testfilter.txt';
48 -export const FILTER_PATH = path.join(process.cwd(), FILTER_FILENAME);
30 +export const FILTER_PATH = path.join(PROJECT_ROOT, FILTER_FILENAME);
compiler/packages/snap/src/runner-watch.ts
+21 -11
@@ -8,15 +8,16 @@
8 import watcher from '@parcel/watcher';
9 import path from 'path';
10 import ts from 'typescript';
11 -import {FILTER_FILENAME, FIXTURES_PATH} from './constants';
11 +import {FILTER_FILENAME, FIXTURES_PATH, PROJECT_ROOT} from './constants';
12 import {TestFilter, readTestFilter} from './fixture-utils';
13 +import {execSync} from 'child_process';
14
15 export function watchSrc(
16 onStart: () => void,
17 onComplete: (isSuccess: boolean) => void,
18 ): ts.WatchOfConfigFile<ts.SemanticDiagnosticsBuilderProgram> {
19 const configPath = ts.findConfigFile(
19 - /*searchPath*/ './',
20 + /*searchPath*/ PROJECT_ROOT,
21 ts.sys.fileExists,
22 'tsconfig.json',
23 );
@@ -26,10 +27,7 @@ export function watchSrc(
27 const createProgram = ts.createSemanticDiagnosticsBuilderProgram;
28 const host = ts.createWatchCompilerHost(
29 configPath,
29 - ts.convertCompilerOptionsFromJson(
30 - {module: 'commonjs', outDir: 'dist', sourceMap: true},
31 - '.',
32 - ).options,
30 + undefined,
31 ts.sys,
32 createProgram,
33 () => {}, // we manually report errors in afterProgramCreate
@@ -41,9 +39,11 @@ export function watchSrc(
39 onStart();
40 return origCreateProgram(rootNames, options, host, oldProgram);
41 };
44 - const origPostProgramCreate = host.afterProgramCreate;
42 host.afterProgramCreate = program => {
46 - origPostProgramCreate!(program);
43 + /**
44 + * Avoid calling original postProgramCreate because it always emits tsc
45 + * compilation output
46 + */
47
48 // syntactic diagnostics refer to javascript syntax
49 const errors = program
@@ -172,13 +172,23 @@ function subscribeTsc(
172 // Notify the user when compilation starts but don't clear the screen yet
173 console.log('\nCompiling...');
174 },
175 - isSuccess => {
175 + isTypecheckSuccess => {
176 + let isCompilerBuildValid = false;
177 + if (isTypecheckSuccess) {
178 + try {
179 + execSync('yarn build', {cwd: PROJECT_ROOT});
180 + console.log('Built compiler successfully with tsup');
181 + isCompilerBuildValid = true;
182 + } catch (e) {
183 + console.warn('Failed to build compiler with tsup:', e);
184 + }
185 + }
186 // Bump the compiler version after a build finishes
187 // and re-run tests
178 - if (isSuccess) {
188 + if (isCompilerBuildValid) {
189 state.compilerVersion++;
190 }
181 - state.isCompilerBuildValid = isSuccess;
191 + state.isCompilerBuildValid = isCompilerBuildValid;
192 state.mode.action = RunnerAction.Test;
193 onChange(state);
194 },
compiler/packages/snap/src/runner-worker.ts
+30 -23
@@ -12,16 +12,19 @@ import type {printFunctionWithOutlined as PrintFunctionWithOutlined} from 'babel
12 import type {printReactiveFunctionWithOutlined as PrintReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction';
13 import {TransformResult, transformFixtureInput} from './compiler';
14 import {
15 - COMPILER_PATH,
16 - COMPILER_INDEX_PATH,
17 - PARSE_CONFIG_PRAGMA_PATH,
18 - PRINT_HIR_PATH,
19 - PRINT_REACTIVE_IR_PATH,
15 + PARSE_CONFIG_PRAGMA_IMPORT,
16 + PRINT_HIR_IMPORT,
17 + PRINT_REACTIVE_IR_IMPORT,
18 + PROJECT_SRC,
19 } from './constants';
20 import {TestFixture, getBasename, isExpectError} from './fixture-utils';
21 import {TestResult, writeOutputToString} from './reporter';
22 import {runSprout} from './sprout';
24 -import {CompilerPipelineValue} from 'babel-plugin-react-compiler/src';
23 +import type {
24 + CompilerPipelineValue,
25 + Effect,
26 + ValueKind,
27 +} from 'babel-plugin-react-compiler/src';
28 import chalk from 'chalk';
29
30 const originalConsoleError = console.error;
@@ -61,22 +64,29 @@ async function compile(
64 let compileResult: TransformResult | null = null;
65 let error: string | null = null;
66 try {
67 + const importedCompilerPlugin = require(PROJECT_SRC) as Record<
68 + string,
69 + unknown
70 + >;
71 +
72 // NOTE: we intentionally require lazily here so that we can clear the require cache
73 // and load fresh versions of the compiler when `compilerVersion` changes.
66 - const {default: BabelPluginReactCompiler} = require(COMPILER_PATH) as {
67 - default: PluginObj;
68 - };
69 - const {Effect: EffectEnum, ValueKind: ValueKindEnum} = require(
70 - COMPILER_INDEX_PATH,
71 - );
72 - const {printFunctionWithOutlined} = require(PRINT_HIR_PATH) as {
73 - printFunctionWithOutlined: typeof PrintFunctionWithOutlined;
74 - };
75 - const {printReactiveFunctionWithOutlined} = require(
76 - PRINT_REACTIVE_IR_PATH,
77 - ) as {
78 - printReactiveFunctionWithOutlined: typeof PrintReactiveFunctionWithOutlined;
79 - };
74 + const BabelPluginReactCompiler = importedCompilerPlugin[
75 + 'default'
76 + ] as PluginObj;
77 + const EffectEnum = importedCompilerPlugin['Effect'] as typeof Effect;
78 + const ValueKindEnum = importedCompilerPlugin[
79 + 'ValueKind'
80 + ] as typeof ValueKind;
81 + const printFunctionWithOutlined = importedCompilerPlugin[
82 + PRINT_HIR_IMPORT
83 + ] as typeof PrintFunctionWithOutlined;
84 + const printReactiveFunctionWithOutlined = importedCompilerPlugin[
85 + PRINT_REACTIVE_IR_IMPORT
86 + ] as typeof PrintReactiveFunctionWithOutlined;
87 + const parseConfigPragmaForTests = importedCompilerPlugin[
88 + PARSE_CONFIG_PRAGMA_IMPORT
89 + ] as typeof ParseConfigPragma;
90
91 let lastLogged: string | null = null;
92 const debugIRLogger = shouldLog
@@ -106,9 +116,6 @@ async function compile(
116 }
117 }
118 : () => {};
109 - const {parseConfigPragmaForTests} = require(PARSE_CONFIG_PRAGMA_PATH) as {
110 - parseConfigPragmaForTests: typeof ParseConfigPragma;
111 - };
119
120 // only try logging if we filtered out all but one fixture,
121 // since console log order is non-deterministic
compiler/packages/snap/src/runner.ts
+22 -15
@@ -12,7 +12,7 @@ import * as readline from 'readline';
12 import ts from 'typescript';
13 import yargs from 'yargs';
14 import {hideBin} from 'yargs/helpers';
15 -import {FILTER_PATH} from './constants';
15 +import {FILTER_PATH, PROJECT_ROOT} from './constants';
16 import {TestFilter, getFixtures, readTestFilter} from './fixture-utils';
17 import {TestResult, TestResults, report, update} from './reporter';
18 import {
@@ -22,6 +22,7 @@ import {
22 watchSrc,
23 } from './runner-watch';
24 import * as runnerWorker from './runner-worker';
25 +import {execSync} from 'child_process';
26
27 const WORKER_PATH = require.resolve('./runner-worker.js');
28 const NUM_WORKERS = cpus().length - 1;
@@ -205,23 +206,29 @@ export async function main(opts: RunnerOptions): Promise<void> {
206 const tsWatch: ts.WatchOfConfigFile<ts.SemanticDiagnosticsBuilderProgram> =
207 watchSrc(
208 () => {},
208 - async (compileSuccess: boolean) => {
209 - let isSuccess = compileSuccess;
210 - if (compileSuccess) {
211 - const testFilter = opts.filter ? await readTestFilter() : null;
212 - const results = await runFixtures(worker, testFilter, 0);
213 - if (opts.update) {
214 - update(results);
215 - } else {
216 - const testSuccess = report(results);
217 - isSuccess &&= testSuccess;
218 - }
219 - } else {
209 + async (isTypecheckSuccess: boolean) => {
210 + let isSuccess = false;
211 + if (!isTypecheckSuccess) {
212 console.error(
221 - 'Found errors in Forget source code, skipping test fixtures.',
213 + 'Found typescript errors in Forget source code, skipping test fixtures.',
214 );
215 + } else {
216 + try {
217 + execSync('yarn build', {cwd: PROJECT_ROOT});
218 + console.log('Built compiler successfully with tsup');
219 + const testFilter = opts.filter ? await readTestFilter() : null;
220 + const results = await runFixtures(worker, testFilter, 0);
221 + if (opts.update) {
222 + update(results);
223 + isSuccess = true;
224 + } else {
225 + isSuccess = report(results);
226 + }
227 + } catch (e) {
228 + console.warn('Failed to build compiler with tsup:', e);
229 + }
230 }
224 - tsWatch.close();
231 + tsWatch?.close();
232 await worker.end();
233 process.exit(isSuccess ? 0 : 1);
234 },
compiler/yarn.lock
+9 -2
@@ -657,7 +657,7 @@
657 js-tokens "^4.0.0"
658 picocolors "^1.0.0"
659
660 -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.1", "@babel/parser@^7.2.0":
660 +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.2.0":
661 version "7.19.1"
662 resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.1.tgz#6f6d6c2e621aad19a92544cc217ed13f1aac5b4c"
663 integrity sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==
@@ -667,6 +667,13 @@
667 resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.3.tgz#8dd36d17c53ff347f9e55c328710321b49479a9a"
668 integrity sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ==
669
670 +"@babel/parser@^7.20.15":
671 + version "7.27.0"
672 + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.0.tgz#3d7d6ee268e41d2600091cbd4e145ffee85a44ec"
673 + integrity sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==
674 + dependencies:
675 + "@babel/types" "^7.27.0"
676 +
677 "@babel/parser@^7.20.7":
678 version "7.21.2"
679 resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.2.tgz#dacafadfc6d7654c3051a66d6fe55b6cb2f2a0b3"
@@ -1696,7 +1703,7 @@
1703 debug "^4.3.1"
1704 globals "^11.1.0"
1705
1699 -"@babel/types@7.26.3", "@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.22.0", "@babel/types@^7.22.3", "@babel/types@^7.22.4", "@babel/types@^7.22.5", "@babel/types@^7.24.7", "@babel/types@^7.25.0", "@babel/types@^7.25.6", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.10", "@babel/types@^7.26.3", "@babel/types@^7.26.9", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.4":
1706 +"@babel/types@7.26.3", "@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.22.0", "@babel/types@^7.22.3", "@babel/types@^7.22.4", "@babel/types@^7.22.5", "@babel/types@^7.24.7", "@babel/types@^7.25.0", "@babel/types@^7.25.6", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.10", "@babel/types@^7.26.3", "@babel/types@^7.26.9", "@babel/types@^7.27.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.4":
1707 version "7.26.3"
1708 resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0"
1709 integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==