diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt
index ba680bbb57232..1600f35107339 100644
--- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt
+++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt
@@ -1,4 +1,5 @@
-function TestComponent(t0) {
+import { c as _c } from "react/compiler-runtime";
+export default function TestComponent(t0) {
const $ = _c(2);
const { x } = t0;
let t1;
diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt
index 2cbd09bba6179..1d59a120f9849 100644
--- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt
+++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt
@@ -1,4 +1,5 @@
-function MyApp() {
+import { c as _c } from "react/compiler-runtime";
+export default function MyApp() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt
index ba680bbb57232..638a2bcd22841 100644
--- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt
+++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt
@@ -1,4 +1,6 @@
-function TestComponent(t0) {
+"use memo";
+import { c as _c } from "react/compiler-runtime";
+export default function TestComponent(t0) {
const $ = _c(2);
const { x } = t0;
let t1;
diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt
index 2c69ddc1d65b8..ebd2d2b04678c 100644
--- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt
+++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt
@@ -1,3 +1,4 @@
-function TestComponent({ x }) {
+"use no memo";
+export default function TestComponent({ x }) {
return ;
}
diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/parse-flow-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/parse-flow-output.txt
new file mode 100644
index 0000000000000..c0907856babf7
--- /dev/null
+++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/parse-flow-output.txt
@@ -0,0 +1,14 @@
+import { c as _c } from "react/compiler-runtime";
+function useFoo(propVal) {
+ const $ = _c(2);
+ const t0 = (propVal.baz: number);
+ let t1;
+ if ($[0] !== t0) {
+ t1 =
{t0}
;
+ $[0] = t0;
+ $[1] = t1;
+ } else {
+ t1 = $[1];
+ }
+ return t1;
+}
\ No newline at end of file
diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/parse-typescript-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/parse-typescript-output.txt
new file mode 100644
index 0000000000000..c936c5ae2ed0a
--- /dev/null
+++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/parse-typescript-output.txt
@@ -0,0 +1,20 @@
+import { c as _c } from "react/compiler-runtime";
+function Foo() {
+ const $ = _c(2);
+ let t0;
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
+ t0 = foo();
+ $[0] = t0;
+ } else {
+ t0 = $[0];
+ }
+ const x = t0 as number;
+ let t1;
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
+ t1 = {x}
;
+ $[1] = t1;
+ } else {
+ t1 = $[1];
+ }
+ return t1;
+}
\ No newline at end of file
diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt
new file mode 100644
index 0000000000000..325e6972e1514
--- /dev/null
+++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt
@@ -0,0 +1,5 @@
+"use no memo";
+function TestComponent({ x }) {
+ "use memo";
+ return ;
+}
diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt
index 804bacab97e05..de6dd52680077 100644
--- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt
+++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt
@@ -1,3 +1,4 @@
+import { c as _c } from "react/compiler-runtime";
function TestComponent(t0) {
"use memo";
const $ = _c(2);
@@ -12,7 +13,7 @@ function TestComponent(t0) {
}
return t1;
}
-function anonymous_1(t0) {
+const TestComponent2 = (t0) => {
"use memo";
const $ = _c(2);
const { x } = t0;
@@ -25,4 +26,4 @@ function anonymous_1(t0) {
t1 = $[1];
}
return t1;
-}
+};
diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt
index 5fb66309fc70c..02c1367622185 100644
--- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt
+++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt
@@ -1,8 +1,8 @@
-function anonymous_1() {
+const TestComponent = function () {
"use no memo";
return ;
-}
-function anonymous_3({ x }) {
+};
+const TestComponent2 = ({ x }) => {
"use no memo";
return ;
-}
+};
diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts
index 846e6227bd1a1..05fe96d4b9048 100644
--- a/compiler/apps/playground/__tests__/e2e/page.spec.ts
+++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts
@@ -9,11 +9,11 @@ import {expect, test} from '@playwright/test';
import {encodeStore, type Store} from '../../lib/stores';
import {format} from 'prettier';
-function print(data: Array): Promise {
+function formatPrint(data: Array): Promise {
return format(data.join(''), {parser: 'babel'});
}
-const DIRECTIVE_TEST_CASES = [
+const TEST_CASE_INPUTS = [
{
name: 'module-scope-use-memo',
input: `
@@ -55,7 +55,7 @@ const TestComponent2 = ({ x }) => {
};`,
},
{
- name: 'function-scope-beats-module-scope',
+ name: 'todo-function-scope-does-not-beat-module-scope',
input: `
'use no memo';
function TestComponent({ x }) {
@@ -63,6 +63,26 @@ function TestComponent({ x }) {
return ;
}`,
},
+ {
+ name: 'parse-typescript',
+ input: `
+function Foo() {
+ const x = foo() as number;
+ return {x}
;
+}
+`,
+ noFormat: true,
+ },
+ {
+ name: 'parse-flow',
+ input: `
+// @flow
+function useFoo(propVal: {+baz: number}) {
+ return {(propVal.baz as number)}
;
+}
+ `,
+ noFormat: true,
+ },
];
test('editor should open successfully', async ({page}) => {
@@ -90,7 +110,7 @@ test('editor should compile from hash successfully', async ({page}) => {
});
const text =
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
- const output = await print(text);
+ const output = await formatPrint(text);
expect(output).not.toEqual('');
expect(output).toMatchSnapshot('01-user-output.txt');
@@ -115,14 +135,14 @@ test('reset button works', async ({page}) => {
});
const text =
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
- const output = await print(text);
+ const output = await formatPrint(text);
expect(output).not.toEqual('');
expect(output).toMatchSnapshot('02-default-output.txt');
});
-DIRECTIVE_TEST_CASES.forEach((t, idx) =>
- test(`directives work: ${t.name}`, async ({page}) => {
+TEST_CASE_INPUTS.forEach((t, idx) =>
+ test(`playground compiles: ${t.name}`, async ({page}) => {
const store: Store = {
source: t.input,
};
@@ -135,7 +155,12 @@ DIRECTIVE_TEST_CASES.forEach((t, idx) =>
const text =
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
- const output = await print(text);
+ let output: string;
+ if (t.noFormat) {
+ output = text.join('');
+ } else {
+ output = await formatPrint(text);
+ }
expect(output).not.toEqual('');
expect(output).toMatchSnapshot(`${t.name}-output.txt`);
diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx
index 82a40272bd312..785b9fd075d12 100644
--- a/compiler/apps/playground/components/Editor/EditorImpl.tsx
+++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx
@@ -5,23 +5,22 @@
* LICENSE file in the root directory of this source tree.
*/
-import {parse as babelParse} from '@babel/parser';
+import {parse as babelParse, ParseResult} from '@babel/parser';
import * as HermesParser from 'hermes-parser';
-import traverse, {NodePath} from '@babel/traverse';
import * as t from '@babel/types';
-import {
+import BabelPluginReactCompiler, {
CompilerError,
CompilerErrorDetail,
Effect,
ErrorSeverity,
parseConfigPragmaForTests,
ValueKind,
- runPlayground,
type Hook,
- findDirectiveDisablingMemoization,
- findDirectiveEnablingMemoization,
+ PluginOptions,
+ CompilerPipelineValue,
+ parsePluginOptions,
} from 'babel-plugin-react-compiler/src';
-import {type ReactFunctionType} from 'babel-plugin-react-compiler/src/HIR/Environment';
+import {type EnvironmentConfig} from 'babel-plugin-react-compiler/src/HIR/Environment';
import clsx from 'clsx';
import invariant from 'invariant';
import {useSnackbar} from 'notistack';
@@ -39,32 +38,18 @@ import {useStore, useStoreDispatch} from '../StoreContext';
import Input from './Input';
import {
CompilerOutput,
+ CompilerTransformOutput,
default as Output,
PrintedCompilerPipelineValue,
} from './Output';
import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR';
import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction';
+import {transformFromAstSync} from '@babel/core';
-type FunctionLike =
- | NodePath
- | NodePath
- | NodePath;
-enum MemoizeDirectiveState {
- Enabled = 'Enabled',
- Disabled = 'Disabled',
- Undefined = 'Undefined',
-}
-
-const MEMOIZE_ENABLED_OR_UNDEFINED_STATES = new Set([
- MemoizeDirectiveState.Enabled,
- MemoizeDirectiveState.Undefined,
-]);
-
-const MEMOIZE_ENABLED_OR_DISABLED_STATES = new Set([
- MemoizeDirectiveState.Enabled,
- MemoizeDirectiveState.Disabled,
-]);
-function parseInput(input: string, language: 'flow' | 'typescript'): any {
+function parseInput(
+ input: string,
+ language: 'flow' | 'typescript',
+): ParseResult {
// Extract the first line to quickly check for custom test directives
if (language === 'flow') {
return HermesParser.parse(input, {
@@ -77,95 +62,45 @@ function parseInput(input: string, language: 'flow' | 'typescript'): any {
return babelParse(input, {
plugins: ['typescript', 'jsx'],
sourceType: 'module',
- });
+ }) as ParseResult;
}
}
-function parseFunctions(
+function invokeCompiler(
source: string,
language: 'flow' | 'typescript',
-): Array<{
- compilationEnabled: boolean;
- fn: FunctionLike;
-}> {
- const items: Array<{
- compilationEnabled: boolean;
- fn: FunctionLike;
- }> = [];
- try {
- const ast = parseInput(source, language);
- traverse(ast, {
- FunctionDeclaration(nodePath) {
- items.push({
- compilationEnabled: shouldCompile(nodePath),
- fn: nodePath,
- });
- nodePath.skip();
- },
- ArrowFunctionExpression(nodePath) {
- items.push({
- compilationEnabled: shouldCompile(nodePath),
- fn: nodePath,
- });
- nodePath.skip();
- },
- FunctionExpression(nodePath) {
- items.push({
- compilationEnabled: shouldCompile(nodePath),
- fn: nodePath,
- });
- nodePath.skip();
- },
- });
- } catch (e) {
- console.error(e);
- CompilerError.throwInvalidJS({
- reason: String(e),
- description: null,
- loc: null,
- suggestions: null,
- });
- }
-
- return items;
-}
-
-function shouldCompile(fn: FunctionLike): boolean {
- const {body} = fn.node;
- if (t.isBlockStatement(body)) {
- const selfCheck = checkExplicitMemoizeDirectives(body.directives);
- if (selfCheck === MemoizeDirectiveState.Enabled) return true;
- if (selfCheck === MemoizeDirectiveState.Disabled) return false;
-
- const parentWithDirective = fn.findParent(parentPath => {
- if (parentPath.isBlockStatement() || parentPath.isProgram()) {
- const directiveCheck = checkExplicitMemoizeDirectives(
- parentPath.node.directives,
- );
- return MEMOIZE_ENABLED_OR_DISABLED_STATES.has(directiveCheck);
- }
- return false;
- });
-
- if (!parentWithDirective) return true;
- const parentDirectiveCheck = checkExplicitMemoizeDirectives(
- (parentWithDirective.node as t.Program | t.BlockStatement).directives,
- );
- return MEMOIZE_ENABLED_OR_UNDEFINED_STATES.has(parentDirectiveCheck);
- }
- return false;
-}
-
-function checkExplicitMemoizeDirectives(
- directives: Array,
-): MemoizeDirectiveState {
- if (findDirectiveEnablingMemoization(directives).length) {
- return MemoizeDirectiveState.Enabled;
- }
- if (findDirectiveDisablingMemoization(directives).length) {
- return MemoizeDirectiveState.Disabled;
+ environment: EnvironmentConfig,
+ logIR: (pipelineValue: CompilerPipelineValue) => void,
+): CompilerTransformOutput {
+ const opts: PluginOptions = parsePluginOptions({
+ logger: {
+ debugLogIRs: logIR,
+ logEvent: () => {},
+ },
+ environment,
+ compilationMode: 'all',
+ panicThreshold: 'all_errors',
+ });
+ const ast = parseInput(source, language);
+ let result = transformFromAstSync(ast, source, {
+ filename: '_playgroundFile.js',
+ highlightCode: false,
+ retainLines: true,
+ plugins: [[BabelPluginReactCompiler, opts]],
+ ast: true,
+ sourceType: 'module',
+ configFile: false,
+ sourceMaps: true,
+ babelrc: false,
+ });
+ if (result?.ast == null || result?.code == null || result?.map == null) {
+ throw new Error('Expected successful compilation');
}
- return MemoizeDirectiveState.Undefined;
+ return {
+ code: result.code,
+ sourceMaps: result.map,
+ language,
+ };
}
const COMMON_HOOKS: Array<[string, Hook]> = [
@@ -216,37 +151,6 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
],
];
-function isHookName(s: string): boolean {
- return /^use[A-Z0-9]/.test(s);
-}
-
-function getReactFunctionType(id: t.Identifier | null): ReactFunctionType {
- if (id != null) {
- if (isHookName(id.name)) {
- return 'Hook';
- }
-
- const isPascalCaseNameSpace = /^[A-Z].*/;
- if (isPascalCaseNameSpace.test(id.name)) {
- return 'Component';
- }
- }
- return 'Other';
-}
-
-function getFunctionIdentifier(
- fn:
- | NodePath
- | NodePath
- | NodePath,
-): t.Identifier | null {
- if (fn.isArrowFunctionExpression()) {
- return null;
- }
- const id = fn.get('id');
- return Array.isArray(id) === false && id.isIdentifier() ? id.node : null;
-}
-
function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
const results = new Map>();
const error = new CompilerError();
@@ -264,71 +168,25 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
} else {
language = 'typescript';
}
- let count = 0;
- const withIdentifier = (id: t.Identifier | null): t.Identifier => {
- if (id != null && id.name != null) {
- return id;
- } else {
- return t.identifier(`anonymous_${count++}`);
- }
- };
+ let transformOutput;
try {
// Extract the first line to quickly check for custom test directives
const pragma = source.substring(0, source.indexOf('\n'));
const config = parseConfigPragmaForTests(pragma);
- const parsedFunctions = parseFunctions(source, language);
- for (const func of parsedFunctions) {
- const id = withIdentifier(getFunctionIdentifier(func.fn));
- const fnName = id.name;
- if (!func.compilationEnabled) {
- upsert({
- kind: 'ast',
- fnName,
- name: 'CodeGen',
- value: {
- type: 'FunctionDeclaration',
- id:
- func.fn.isArrowFunctionExpression() ||
- func.fn.isFunctionExpression()
- ? withIdentifier(null)
- : func.fn.node.id,
- async: func.fn.node.async,
- generator: !!func.fn.node.generator,
- body: func.fn.node.body as t.BlockStatement,
- params: func.fn.node.params,
- },
- });
- continue;
- }
- for (const result of runPlayground(
- func.fn,
- {
- ...config,
- customHooks: new Map([...COMMON_HOOKS]),
- },
- getReactFunctionType(id),
- )) {
+
+ transformOutput = invokeCompiler(
+ source,
+ language,
+ {...config, customHooks: new Map([...COMMON_HOOKS])},
+ result => {
switch (result.kind) {
case 'ast': {
- upsert({
- kind: 'ast',
- fnName,
- name: result.name,
- value: {
- type: 'FunctionDeclaration',
- id: withIdentifier(result.value.id),
- async: result.value.async,
- generator: result.value.generator,
- body: result.value.body,
- params: result.value.params,
- },
- });
break;
}
case 'hir': {
upsert({
kind: 'hir',
- fnName,
+ fnName: result.value.id,
name: result.name,
value: printFunctionWithOutlined(result.value),
});
@@ -337,7 +195,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
case 'reactive': {
upsert({
kind: 'reactive',
- fnName,
+ fnName: result.value.id,
name: result.name,
value: printReactiveFunctionWithOutlined(result.value),
});
@@ -346,7 +204,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
case 'debug': {
upsert({
kind: 'debug',
- fnName,
+ fnName: null,
name: result.name,
value: result.value,
});
@@ -357,8 +215,8 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
throw new Error(`Unhandled result ${result}`);
}
}
- }
- }
+ },
+ );
} catch (err) {
/**
* error might be an invariant violation or other runtime error
@@ -385,7 +243,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
if (error.hasErrors()) {
return [{kind: 'err', results, error: error}, language];
}
- return [{kind: 'ok', results}, language];
+ return [{kind: 'ok', results, transformOutput}, language];
}
export default function Editor(): JSX.Element {
@@ -405,7 +263,7 @@ export default function Editor(): JSX.Element {
} catch (e) {
invariant(e instanceof Error, 'Only Error may be caught.');
enqueueSnackbar(e.message, {
- variant: 'message',
+ variant: 'warning',
...createMessage(
'Bad URL - fell back to the default Playground.',
MessageLevel.Info,
diff --git a/compiler/apps/playground/components/Editor/Output.tsx b/compiler/apps/playground/components/Editor/Output.tsx
index 97a63400d2b42..d4127c63cff05 100644
--- a/compiler/apps/playground/components/Editor/Output.tsx
+++ b/compiler/apps/playground/components/Editor/Output.tsx
@@ -5,8 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
-import generate from '@babel/generator';
-import * as t from '@babel/types';
import {
CodeIcon,
DocumentAddIcon,
@@ -21,17 +19,12 @@ import {memo, ReactNode, useEffect, useState} from 'react';
import {type Store} from '../../lib/stores';
import TabbedWindow from '../TabbedWindow';
import {monacoOptions} from './monacoOptions';
+import {BabelFileResult} from '@babel/core';
const MemoizedOutput = memo(Output);
export default MemoizedOutput;
export type PrintedCompilerPipelineValue =
- | {
- kind: 'ast';
- name: string;
- fnName: string | null;
- value: t.FunctionDeclaration;
- }
| {
kind: 'hir';
name: string;
@@ -41,8 +34,17 @@ export type PrintedCompilerPipelineValue =
| {kind: 'reactive'; name: string; fnName: string | null; value: string}
| {kind: 'debug'; name: string; fnName: string | null; value: string};
+export type CompilerTransformOutput = {
+ code: string;
+ sourceMaps: BabelFileResult['map'];
+ language: 'flow' | 'typescript';
+};
export type CompilerOutput =
- | {kind: 'ok'; results: Map>}
+ | {
+ kind: 'ok';
+ transformOutput: CompilerTransformOutput;
+ results: Map>;
+ }
| {
kind: 'err';
results: Map>;
@@ -61,7 +63,6 @@ async function tabify(
const tabs = new Map();
const reorderedTabs = new Map();
const concattedResults = new Map();
- let topLevelFnDecls: Array = [];
// Concat all top level function declaration results into a single tab for each pass
for (const [passName, results] of compilerOutput.results) {
for (const result of results) {
@@ -87,9 +88,6 @@ async function tabify(
}
break;
}
- case 'ast':
- topLevelFnDecls.push(result.value);
- break;
case 'debug': {
concattedResults.set(passName, result.value);
break;
@@ -114,13 +112,17 @@ async function tabify(
lastPassOutput = text;
}
// Ensure that JS and the JS source map come first
- if (topLevelFnDecls.length > 0) {
- /**
- * Make a synthetic Program so we can have a single AST with all the top level
- * FunctionDeclarations
- */
- const ast = t.program(topLevelFnDecls);
- const {code, sourceMapUrl} = await codegen(ast, source);
+ if (compilerOutput.kind === 'ok') {
+ const {transformOutput} = compilerOutput;
+ const sourceMapUrl = getSourceMapUrl(
+ transformOutput.code,
+ JSON.stringify(transformOutput.sourceMaps),
+ );
+ const code = await prettier.format(transformOutput.code, {
+ semi: true,
+ parser: transformOutput.language === 'flow' ? 'babel-flow' : 'babel-ts',
+ plugins: [parserBabel, prettierPluginEstree],
+ });
reorderedTabs.set(
'JS',
{
- const generated = generate(
- ast,
- {sourceMaps: true, sourceFileName: 'input.js'},
- source,
- );
- const sourceMapUrl = getSourceMapUrl(
- generated.code,
- JSON.stringify(generated.map),
- );
- const codegenOutput = await prettier.format(generated.code, {
- semi: true,
- parser: 'babel',
- plugins: [parserBabel, prettierPluginEstree],
- });
- return {code: codegenOutput, sourceMapUrl};
-}
-
function utf16ToUTF8(s: string): string {
return unescape(encodeURIComponent(s));
}
diff --git a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts
index 401cbd4bdf50f..c648c66043980 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts
+++ b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts
@@ -39,7 +39,10 @@ export default function BabelPluginReactCompiler(
) {
opts = injectReanimatedFlag(opts);
}
- if (isDev) {
+ if (
+ opts.environment.enableResetCacheOnSourceFileChanges !== false &&
+ isDev
+ ) {
opts = {
...opts,
environment: {
diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts
index 72ed9e7c866ce..fb951d25c5398 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts
+++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts
@@ -15,6 +15,7 @@ import {
} from '../HIR/Environment';
import {hasOwnProperty} from '../Utils/utils';
import {fromZodError} from 'zod-validation-error';
+import {CompilerPipelineValue} from './Pipeline';
const PanicThresholdOptionsSchema = z.enum([
/*
@@ -209,6 +210,7 @@ export type LoggerEvent =
export type Logger = {
logEvent: (filename: string | null, event: LoggerEvent) => void;
+ debugLogIRs?: (value: CompilerPipelineValue) => void;
};
export const defaultOptions: PluginOptions = {
diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts
index 8a97eea217b33..6995aec7f1825 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts
+++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts
@@ -112,7 +112,7 @@ export type CompilerPipelineValue =
| {kind: 'reactive'; name: string; value: ReactiveFunction}
| {kind: 'debug'; name: string; value: string};
-export function* run(
+function run(
func: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
>,
@@ -122,7 +122,7 @@ export function* run(
logger: Logger | null,
filename: string | null,
code: string | null,
-): Generator {
+): CodegenFunction {
const contextIdentifiers = findContextIdentifiers(func);
const env = new Environment(
func.scope,
@@ -134,12 +134,17 @@ export function* run(
code,
useMemoCacheIdentifier,
);
- yield log({
+ env.logger?.debugLogIRs?.({
+ kind: 'debug',
+ name: 'EnvironmentConfig',
+ value: prettyFormat(env.config),
+ });
+ printLog({
kind: 'debug',
name: 'EnvironmentConfig',
value: prettyFormat(env.config),
});
- const ast = yield* runWithEnvironment(func, env);
+ const ast = runWithEnvironment(func, env);
return ast;
}
@@ -147,17 +152,22 @@ export function* run(
* Note: this is split from run() to make `config` out of scope, so that all
* access to feature flags has to be through the Environment for consistency.
*/
-function* runWithEnvironment(
+function runWithEnvironment(
func: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
>,
env: Environment,
-): Generator {
+): CodegenFunction {
+ const log = (value: CompilerPipelineValue): CompilerPipelineValue => {
+ printLog(value);
+ env.logger?.debugLogIRs?.(value);
+ return value;
+ };
const hir = lower(func, env).unwrap();
- yield log({kind: 'hir', name: 'HIR', value: hir});
+ log({kind: 'hir', name: 'HIR', value: hir});
pruneMaybeThrows(hir);
- yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
+ log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
validateContextVariableLValues(hir);
validateUseMemo(hir);
@@ -168,35 +178,35 @@ function* runWithEnvironment(
!env.config.enableChangeDetectionForDebugging
) {
dropManualMemoization(hir);
- yield log({kind: 'hir', name: 'DropManualMemoization', value: hir});
+ log({kind: 'hir', name: 'DropManualMemoization', value: hir});
}
inlineImmediatelyInvokedFunctionExpressions(hir);
- yield log({
+ log({
kind: 'hir',
name: 'InlineImmediatelyInvokedFunctionExpressions',
value: hir,
});
mergeConsecutiveBlocks(hir);
- yield log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir});
+ log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir});
assertConsistentIdentifiers(hir);
assertTerminalSuccessorsExist(hir);
enterSSA(hir);
- yield log({kind: 'hir', name: 'SSA', value: hir});
+ log({kind: 'hir', name: 'SSA', value: hir});
eliminateRedundantPhi(hir);
- yield log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir});
+ log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir});
assertConsistentIdentifiers(hir);
constantPropagation(hir);
- yield log({kind: 'hir', name: 'ConstantPropagation', value: hir});
+ log({kind: 'hir', name: 'ConstantPropagation', value: hir});
inferTypes(hir);
- yield log({kind: 'hir', name: 'InferTypes', value: hir});
+ log({kind: 'hir', name: 'InferTypes', value: hir});
if (env.config.validateHooksUsage) {
validateHooksUsage(hir);
@@ -211,30 +221,30 @@ function* runWithEnvironment(
}
optimizePropsMethodCalls(hir);
- yield log({kind: 'hir', name: 'OptimizePropsMethodCalls', value: hir});
+ log({kind: 'hir', name: 'OptimizePropsMethodCalls', value: hir});
analyseFunctions(hir);
- yield log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
+ log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
inferReferenceEffects(hir);
- yield log({kind: 'hir', name: 'InferReferenceEffects', value: hir});
+ log({kind: 'hir', name: 'InferReferenceEffects', value: hir});
validateLocalsNotReassignedAfterRender(hir);
// Note: Has to come after infer reference effects because "dead" code may still affect inference
deadCodeElimination(hir);
- yield log({kind: 'hir', name: 'DeadCodeElimination', value: hir});
+ log({kind: 'hir', name: 'DeadCodeElimination', value: hir});
if (env.config.enableInstructionReordering) {
instructionReordering(hir);
- yield log({kind: 'hir', name: 'InstructionReordering', value: hir});
+ log({kind: 'hir', name: 'InstructionReordering', value: hir});
}
pruneMaybeThrows(hir);
- yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
+ log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
inferMutableRanges(hir);
- yield log({kind: 'hir', name: 'InferMutableRanges', value: hir});
+ log({kind: 'hir', name: 'InferMutableRanges', value: hir});
if (env.config.assertValidMutableRanges) {
assertValidMutableRanges(hir);
@@ -257,27 +267,27 @@ function* runWithEnvironment(
}
inferReactivePlaces(hir);
- yield log({kind: 'hir', name: 'InferReactivePlaces', value: hir});
+ log({kind: 'hir', name: 'InferReactivePlaces', value: hir});
rewriteInstructionKindsBasedOnReassignment(hir);
- yield log({
+ log({
kind: 'hir',
name: 'RewriteInstructionKindsBasedOnReassignment',
value: hir,
});
propagatePhiTypes(hir);
- yield log({
+ log({
kind: 'hir',
name: 'PropagatePhiTypes',
value: hir,
});
inferReactiveScopeVariables(hir);
- yield log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
+ log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
const fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir);
- yield log({
+ log({
kind: 'hir',
name: 'MemoizeFbtAndMacroOperandsInSameScope',
value: hir,
@@ -289,39 +299,39 @@ function* runWithEnvironment(
if (env.config.enableFunctionOutlining) {
outlineFunctions(hir, fbtOperands);
- yield log({kind: 'hir', name: 'OutlineFunctions', value: hir});
+ log({kind: 'hir', name: 'OutlineFunctions', value: hir});
}
alignMethodCallScopes(hir);
- yield log({
+ log({
kind: 'hir',
name: 'AlignMethodCallScopes',
value: hir,
});
alignObjectMethodScopes(hir);
- yield log({
+ log({
kind: 'hir',
name: 'AlignObjectMethodScopes',
value: hir,
});
pruneUnusedLabelsHIR(hir);
- yield log({
+ log({
kind: 'hir',
name: 'PruneUnusedLabelsHIR',
value: hir,
});
alignReactiveScopesToBlockScopesHIR(hir);
- yield log({
+ log({
kind: 'hir',
name: 'AlignReactiveScopesToBlockScopesHIR',
value: hir,
});
mergeOverlappingReactiveScopesHIR(hir);
- yield log({
+ log({
kind: 'hir',
name: 'MergeOverlappingReactiveScopesHIR',
value: hir,
@@ -329,7 +339,7 @@ function* runWithEnvironment(
assertValidBlockNesting(hir);
buildReactiveScopeTerminalsHIR(hir);
- yield log({
+ log({
kind: 'hir',
name: 'BuildReactiveScopeTerminalsHIR',
value: hir,
@@ -338,14 +348,14 @@ function* runWithEnvironment(
assertValidBlockNesting(hir);
flattenReactiveLoopsHIR(hir);
- yield log({
+ log({
kind: 'hir',
name: 'FlattenReactiveLoopsHIR',
value: hir,
});
flattenScopesWithHooksOrUseHIR(hir);
- yield log({
+ log({
kind: 'hir',
name: 'FlattenScopesWithHooksOrUseHIR',
value: hir,
@@ -353,7 +363,7 @@ function* runWithEnvironment(
assertTerminalSuccessorsExist(hir);
assertTerminalPredsExist(hir);
propagateScopeDependenciesHIR(hir);
- yield log({
+ log({
kind: 'hir',
name: 'PropagateScopeDependenciesHIR',
value: hir,
@@ -365,7 +375,7 @@ function* runWithEnvironment(
if (env.config.inlineJsxTransform) {
inlineJsxTransform(hir, env.config.inlineJsxTransform);
- yield log({
+ log({
kind: 'hir',
name: 'inlineJsxTransform',
value: hir,
@@ -373,7 +383,7 @@ function* runWithEnvironment(
}
const reactiveFunction = buildReactiveFunction(hir);
- yield log({
+ log({
kind: 'reactive',
name: 'BuildReactiveFunction',
value: reactiveFunction,
@@ -382,7 +392,7 @@ function* runWithEnvironment(
assertWellFormedBreakTargets(reactiveFunction);
pruneUnusedLabels(reactiveFunction);
- yield log({
+ log({
kind: 'reactive',
name: 'PruneUnusedLabels',
value: reactiveFunction,
@@ -390,35 +400,35 @@ function* runWithEnvironment(
assertScopeInstructionsWithinScopes(reactiveFunction);
pruneNonEscapingScopes(reactiveFunction);
- yield log({
+ log({
kind: 'reactive',
name: 'PruneNonEscapingScopes',
value: reactiveFunction,
});
pruneNonReactiveDependencies(reactiveFunction);
- yield log({
+ log({
kind: 'reactive',
name: 'PruneNonReactiveDependencies',
value: reactiveFunction,
});
pruneUnusedScopes(reactiveFunction);
- yield log({
+ log({
kind: 'reactive',
name: 'PruneUnusedScopes',
value: reactiveFunction,
});
mergeReactiveScopesThatInvalidateTogether(reactiveFunction);
- yield log({
+ log({
kind: 'reactive',
name: 'MergeReactiveScopesThatInvalidateTogether',
value: reactiveFunction,
});
pruneAlwaysInvalidatingScopes(reactiveFunction);
- yield log({
+ log({
kind: 'reactive',
name: 'PruneAlwaysInvalidatingScopes',
value: reactiveFunction,
@@ -426,7 +436,7 @@ function* runWithEnvironment(
if (env.config.enableChangeDetectionForDebugging != null) {
pruneInitializationDependencies(reactiveFunction);
- yield log({
+ log({
kind: 'reactive',
name: 'PruneInitializationDependencies',
value: reactiveFunction,
@@ -434,49 +444,49 @@ function* runWithEnvironment(
}
propagateEarlyReturns(reactiveFunction);
- yield log({
+ log({
kind: 'reactive',
name: 'PropagateEarlyReturns',
value: reactiveFunction,
});
pruneUnusedLValues(reactiveFunction);
- yield log({
+ log({
kind: 'reactive',
name: 'PruneUnusedLValues',
value: reactiveFunction,
});
promoteUsedTemporaries(reactiveFunction);
- yield log({
+ log({
kind: 'reactive',
name: 'PromoteUsedTemporaries',
value: reactiveFunction,
});
extractScopeDeclarationsFromDestructuring(reactiveFunction);
- yield log({
+ log({
kind: 'reactive',
name: 'ExtractScopeDeclarationsFromDestructuring',
value: reactiveFunction,
});
stabilizeBlockIds(reactiveFunction);
- yield log({
+ log({
kind: 'reactive',
name: 'StabilizeBlockIds',
value: reactiveFunction,
});
const uniqueIdentifiers = renameVariables(reactiveFunction);
- yield log({
+ log({
kind: 'reactive',
name: 'RenameVariables',
value: reactiveFunction,
});
pruneHoistedContexts(reactiveFunction);
- yield log({
+ log({
kind: 'reactive',
name: 'PruneHoistedContexts',
value: reactiveFunction,
@@ -497,9 +507,9 @@ function* runWithEnvironment(
uniqueIdentifiers,
fbtOperands,
}).unwrap();
- yield log({kind: 'ast', name: 'Codegen', value: ast});
+ log({kind: 'ast', name: 'Codegen', value: ast});
for (const outlined of ast.outlined) {
- yield log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn});
+ log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn});
}
/**
@@ -525,7 +535,7 @@ export function compileFn(
filename: string | null,
code: string | null,
): CodegenFunction {
- let generator = run(
+ return run(
func,
config,
fnType,
@@ -534,15 +544,9 @@ export function compileFn(
filename,
code,
);
- while (true) {
- const next = generator.next();
- if (next.done) {
- return next.value;
- }
- }
}
-export function log(value: CompilerPipelineValue): CompilerPipelineValue {
+function printLog(value: CompilerPipelineValue): CompilerPipelineValue {
switch (value.kind) {
case 'ast': {
logCodegenFunction(value.name, value.value);
@@ -566,14 +570,3 @@ export function log(value: CompilerPipelineValue): CompilerPipelineValue {
}
return value;
}
-
-export function* runPlayground(
- func: NodePath<
- t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
- >,
- config: EnvironmentConfig,
- fnType: ReactFunctionType,
-): Generator {
- const ast = yield* run(func, config, fnType, '_c', null, null, null);
- return ast;
-}
diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
index fa581d8ed8d81..48589b8be9a77 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
+++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
@@ -168,11 +168,19 @@ const EnvironmentConfigSchema = z.object({
customMacros: z.nullable(z.array(MacroSchema)).default(null),
/**
- * Enable a check that resets the memoization cache when the source code of the file changes.
- * This is intended to support hot module reloading (HMR), where the same runtime component
- * instance will be reused across different versions of the component source.
+ * Enable a check that resets the memoization cache when the source code of
+ * the file changes. This is intended to support hot module reloading (HMR),
+ * where the same runtime component instance will be reused across different
+ * versions of the component source.
+ *
+ * When set to
+ * - true: code for HMR support is always generated, regardless of NODE_ENV
+ * or `globalThis.__DEV__`
+ * - false: code for HMR support is not generated
+ * - null: (default) code for HMR support is conditionally generated dependent
+ * on `NODE_ENV` and `globalThis.__DEV__` at the time of compilation.
*/
- enableResetCacheOnSourceFileChanges: z.boolean().default(false),
+ enableResetCacheOnSourceFileChanges: z.nullable(z.boolean()).default(null),
/**
* Enable using information from existing useMemo/useCallback to understand when a value is done
@@ -708,7 +716,10 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig {
continue;
}
- if (typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean') {
+ if (
+ key !== 'enableResetCacheOnSourceFileChanges' &&
+ typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean'
+ ) {
// skip parsing non-boolean properties
continue;
}
@@ -718,9 +729,15 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig {
maybeConfig[key] = false;
}
}
-
const config = EnvironmentConfigSchema.safeParse(maybeConfig);
if (config.success) {
+ /**
+ * Unless explicitly enabled, do not insert HMR handling code
+ * in test fixtures or playground to reduce visual noise.
+ */
+ if (config.data.enableResetCacheOnSourceFileChanges == null) {
+ config.data.enableResetCacheOnSourceFileChanges = false;
+ }
return config.data;
}
CompilerError.invariant(false, {
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md
new file mode 100644
index 0000000000000..69c1b9bbbbc73
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md
@@ -0,0 +1,29 @@
+
+## Input
+
+```javascript
+// @compilationMode(all)
+'use no memo';
+
+function TestComponent({x}) {
+ 'use memo';
+ return ;
+}
+
+```
+
+## Code
+
+```javascript
+// @compilationMode(all)
+"use no memo";
+
+function TestComponent({ x }) {
+ "use memo";
+ return ;
+}
+
+```
+
+### Eval output
+(kind: exception) Fixture not implemented
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js
new file mode 100644
index 0000000000000..9b314e1f99d53
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js
@@ -0,0 +1,7 @@
+// @compilationMode(all)
+'use no memo';
+
+function TestComponent({x}) {
+ 'use memo';
+ return ;
+}
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts
index d634bd235f190..dc4d5d25a47e2 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts
@@ -25,6 +25,7 @@ describe('parseConfigPragmaForTests()', () => {
enableUseTypeAnnotations: true,
validateNoSetStateInPassiveEffects: true,
validateNoSetStateInRender: false,
+ enableResetCacheOnSourceFileChanges: false,
});
});
});
diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts
index 150df26e45818..188c244d9ef2c 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/index.ts
+++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts
@@ -17,8 +17,6 @@ export {
compileFn as compile,
compileProgram,
parsePluginOptions,
- run,
- runPlayground,
OPT_OUT_DIRECTIVES,
OPT_IN_DIRECTIVES,
findDirectiveEnablingMemoization,