@samitouri / QOS-React / commits / da996a15be

[compiler][be] Move e2e tests to BabelPlugin transformer (#32706)

Clean up jest-e2e setup since https://github.com/facebook/react/pull/32663 and other features need program context (e.g. changing imports) --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32706). * #32663 * __->__ #32706

mofeiZ committed Mar 21, 2025 at 20:05 UTC da996a15be4f14aeb9726037f4559ff1cb3c2600
6 files changed +26 -135
compiler/packages/babel-plugin-react-compiler/scripts/jest/e2e-forget.config.js
+1 -4
@@ -7,7 +7,4 @@
7
8 const makeE2EConfig = require('../jest/makeE2EConfig');
9
10 -const config = makeE2EConfig('e2e with forget', true);
11 -config.setupFilesAfterEnv = ['<rootDir>/../scripts/jest/setupEnvE2E.js'];
12 -
13 -module.exports = config;
10 +module.exports = makeE2EConfig('e2e with forget', true);
compiler/packages/babel-plugin-react-compiler/scripts/jest/makeTransform.ts
+19 -115
@@ -5,19 +5,16 @@
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 -import {jsx} from '@babel/plugin-syntax-jsx';
8 import babelJest from 'babel-jest';
10 -import {compile} from 'babel-plugin-react-compiler';
11 -import {execSync} from 'child_process';
12 -
13 -import type {NodePath, Visitor} from '@babel/traverse';
14 -import type {CallExpression, FunctionDeclaration} from '@babel/types';
15 -import * as t from '@babel/types';
9 import {
17 - EnvironmentConfig,
10 validateEnvironmentConfig,
11 + EnvironmentConfig,
12 } from 'babel-plugin-react-compiler';
20 -import {basename} from 'path';
13 +import {execSync} from 'child_process';
14 +
15 +import type {NodePath, Visitor} from '@babel/traverse';
16 +import type {CallExpression} from '@babel/types';
17 +import BabelPluginReactCompiler from 'babel-plugin-react-compiler';
18
19 /**
20 * -- IMPORTANT --
@@ -28,10 +25,19 @@ import {basename} from 'path';
25 const e2eTransformerCacheKey = 1;
26 const forgetOptions: EnvironmentConfig = validateEnvironmentConfig({
27 enableAssumeHooksFollowRulesOfReact: true,
31 - enableFunctionOutlining: false,
28 });
29 const debugMode = process.env['DEBUG_FORGET_COMPILER'] != null;
30
31 +const compilerCacheKey = execSync(
32 + 'yarn --silent --cwd ../.. hash packages/babel-plugin-react-compiler/dist',
33 +)
34 + .toString()
35 + .trim();
36 +
37 +if (debugMode) {
38 + console.log('cachebreaker', compilerCacheKey);
39 +}
40 +
41 module.exports = (useForget: boolean) => {
42 function createTransformer() {
43 return babelJest.createTransformer({
@@ -42,15 +48,14 @@ module.exports = (useForget: boolean) => {
48 plugins: [
49 useForget
50 ? [
45 - ReactForgetFunctionTransform,
51 + BabelPluginReactCompiler,
52 {
53 + environment: forgetOptions,
54 /*
55 * Jest hashes the babel config as a cache breaker.
56 * (see https://github.com/jestjs/jest/blob/v29.6.2/packages/babel-jest/src/index.ts#L84)
57 */
51 - compilerCacheKey: execSync(
52 - 'yarn --silent --cwd ../.. hash packages/babel-plugin-react-compiler/dist',
53 - ).toString(),
58 + compilerCacheKey,
59 transformOptionsCacheKey: forgetOptions,
60 e2eTransformerCacheKey,
61 },
@@ -105,104 +110,3 @@ module.exports = (useForget: boolean) => {
110 createTransformer,
111 };
112 };
108 -
109 -// Mostly copied from react/scripts/babel/transform-forget.js
110 -function isReactComponentLike(fn: NodePath<FunctionDeclaration>): boolean {
111 - let isReactComponent = false;
112 - let hasNoUseForgetDirective = false;
113 -
114 - /*
115 - * React components start with an upper case letter,
116 - * React hooks start with `use`
117 - */
118 - if (
119 - fn.node.id == null ||
120 - (fn.node.id.name[0].toUpperCase() !== fn.node.id.name[0] &&
121 - !/^use[A-Z0-9]/.test(fn.node.id.name))
122 - ) {
123 - return false;
124 - }
125 -
126 - fn.traverse({
127 - DirectiveLiteral(path) {
128 - if (path.node.value === 'use no forget') {
129 - hasNoUseForgetDirective = true;
130 - }
131 - },
132 -
133 - JSX(path) {
134 - // Is there is a JSX node created in the current function context?
135 - if (path.scope.getFunctionParent()?.path.node === fn.node) {
136 - isReactComponent = true;
137 - }
138 - },
139 -
140 - CallExpression(path) {
141 - // Is there hook usage?
142 - if (
143 - path.node.callee.type === 'Identifier' &&
144 - !/^use[A-Z0-9]/.test(path.node.callee.name)
145 - ) {
146 - isReactComponent = true;
147 - }
148 - },
149 - });
150 -
151 - if (hasNoUseForgetDirective) {
152 - return false;
153 - }
154 -
155 - return isReactComponent;
156 -}
157 -
158 -function ReactForgetFunctionTransform() {
159 - const compiledFns = new Set();
160 - const visitor = {
161 - FunctionDeclaration(fn: NodePath<FunctionDeclaration>, state: any): void {
162 - if (compiledFns.has(fn.node)) {
163 - return;
164 - }
165 -
166 - if (!isReactComponentLike(fn)) {
167 - return;
168 - }
169 - if (debugMode) {
170 - const filename = basename(state.file.opts.filename);
171 - if (fn.node.loc && fn.node.id) {
172 - console.log(
173 - ` Compiling ${filename}:${fn.node.loc.start.line}:${fn.node.loc.start.column} ${fn.node.id.name}`,
174 - );
175 - } else {
176 - console.log(` Compiling ${filename} ${fn.node.id?.name}`);
177 - }
178 - }
179 -
180 - const compiled = compile(
181 - fn,
182 - forgetOptions,
183 - 'Other',
184 - 'all_features',
185 - '_c',
186 - null,
187 - null,
188 - null,
189 - );
190 - compiledFns.add(compiled);
191 -
192 - const fun = t.functionDeclaration(
193 - compiled.id,
194 - compiled.params,
195 - compiled.body,
196 - compiled.generator,
197 - compiled.async,
198 - );
199 - fn.replaceWith(fun);
200 - fn.skip();
201 - },
202 - };
203 - return {
204 - name: 'react-forget-e2e',
205 - inherits: jsx,
206 - visitor,
207 - };
208 -}
compiler/packages/babel-plugin-react-compiler/scripts/jest/setupEnvE2E.js deleted
-16
@@ -1,16 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - */
7 -
8 -const ReactCompilerRuntime = require('react/compiler-runtime');
9 -
10 -/*
11 - * Our e2e babel transform currently only compiles functions, not programs.
12 - * As a result, our e2e transpiled code does not contain an import for the
13 - * memo cache function. As a temporary hack, we add a `_c` global, which is
14 - * the name that is used for the import by default.
15 - */
16 -globalThis._c = ReactCompilerRuntime.c;
compiler/packages/babel-plugin-react-compiler/src/__tests__/e2e/constant-prop.e2e.js
+4
@@ -12,6 +12,7 @@ globalThis.constantValue = 'global test value';
12
13 test('literal-constant-propagation', () => {
14 function Component() {
15 + 'use memo';
16 const x = 'test value 1';
17 return <div>{x}</div>;
18 }
@@ -38,6 +39,7 @@ test('literal-constant-propagation', () => {
39
40 test('global-constant-propagation', () => {
41 function Component() {
42 + 'use memo';
43 const x = constantValue;
44
45 return <div>{x}</div>;
@@ -65,6 +67,7 @@ test('global-constant-propagation', () => {
67
68 test('lambda-constant-propagation', () => {
69 function Component() {
70 + 'use memo';
71 const x = 'test value 1';
72 const getDiv = () => <div>{x}</div>;
73 return getDiv();
@@ -92,6 +95,7 @@ test('lambda-constant-propagation', () => {
95
96 test('lambda-constant-propagation-of-phi-node', () => {
97 function Component({noopCallback}) {
98 + 'use memo';
99 const x = 'test value 1';
100 if (constantValue) {
101 noopCallback();
compiler/packages/babel-plugin-react-compiler/src/__tests__/e2e/update-button.e2e.js
+1
@@ -16,6 +16,7 @@ function Button({label}) {
16
17 let currentTheme = 'light';
18 function useTheme() {
19 + 'use memo';
20 return currentTheme;
21 }
22
compiler/packages/babel-plugin-react-compiler/src/__tests__/e2e/update-expressions.e2e.js
+1
@@ -10,6 +10,7 @@ import * as React from 'react';
10 import {expectLogsAndClear, log} from './expectLogs';
11
12 function Counter(props) {
13 + 'use memo';
14 let value = props.value;
15 let a = value++;
16 expect(a).toBe(props.value); // postfix