diff --git a/compiler/forget/src/Babel/BabelPlugin.ts b/compiler/forget/src/Babel/BabelPlugin.ts index a24eb2ee0c..1bdbcb7791 100644 --- a/compiler/forget/src/Babel/BabelPlugin.ts +++ b/compiler/forget/src/Babel/BabelPlugin.ts @@ -80,6 +80,17 @@ export default function ReactForgetBabelPlugin( visitFn(fn, pass); }, + + ArrowFunctionExpression( + fn: BabelCore.NodePath, + pass: BabelPluginPass + ): void { + if (!shouldCompile(fn, pass)) { + return; + } + + visitFn(buildFunctionDeclaration(fn), pass); + }, }; return { @@ -135,6 +146,54 @@ function shouldCompile( return true; } +function buildFunctionDeclaration( + fn: BabelCore.NodePath +): BabelCore.NodePath { + invariant( + fn.parentPath.isVariableDeclarator(), + "ArrowFunctionExpression must be declared in variable declaration" + ); + const variableDeclarator = fn.parentPath; + + invariant( + variableDeclarator.parentPath.isVariableDeclaration(), + "ArrowFunctionExpression must be a single declaration" + ); + const variableDeclaration = variableDeclarator.parentPath; + + const id = variableDeclarator.get("id"); + invariant(id.isIdentifier(), "ArrowFunctionExpression must have an id"); + + const rewrittenFn = variableDeclaration.replaceWith( + t.functionDeclaration( + id.node, + fn.node.params, + buildBlockStatement(fn), + fn.node.generator, + fn.node.async + ) + )[0]; + fn.skip(); + return rewrittenFn; +} + +function buildBlockStatement( + fn: BabelCore.NodePath +): t.BlockStatement { + const body = fn.get("body"); + if (body.isExpression()) { + const wrappedBody = body.replaceWith( + t.blockStatement([t.returnStatement(body.node)]) + )[0]; + body.skip(); + + return wrappedBody.node; + } + + invariant(body.isBlockStatement(), "Body must be a BlockStatement"); + return body.node; +} + function buildGatingTest( uncompiled: BabelCore.NodePath, compiled: t.Identifier, diff --git a/compiler/forget/src/__tests__/fixtures/compiler/arrow-function-expr-gating-test.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/arrow-function-expr-gating-test.expect.md new file mode 100644 index 0000000000..4cdc0e29b8 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/arrow-function-expr-gating-test.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @gatingModule +const ErrorView = (error, _retry) => ; + +export default ErrorView; + +``` + +## Code + +```javascript +import isForgetEnabled from "ReactForgetFeatureFlag"; // @gatingModule +function ErrorView_uncompiled(error, _retry) { + return ; +} +function ErrorView_forget(error, _retry) { + const $ = React.unstable_useMemoCache(2); + const c_0 = $[0] !== error; + let t0; + if (c_0) { + t0 = ; + $[0] = error; + $[1] = t0; + } else { + t0 = $[1]; + } + return t0; +} +const ErrorView = isForgetEnabled ? ErrorView_forget : ErrorView_uncompiled; + +export default ErrorView; + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/arrow-function-expr-gating-test.js b/compiler/forget/src/__tests__/fixtures/compiler/arrow-function-expr-gating-test.js new file mode 100644 index 0000000000..f58800ee36 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/arrow-function-expr-gating-test.js @@ -0,0 +1,4 @@ +// @gatingModule +const ErrorView = (error, _retry) => ; + +export default ErrorView; diff --git a/compiler/forget/src/__tests__/fixtures/compiler/error._todo.multi-arrow-expr-export-default-gating-test.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/error._todo.multi-arrow-expr-export-default-gating-test.expect.md new file mode 100644 index 0000000000..597b7445eb --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/error._todo.multi-arrow-expr-export-default-gating-test.expect.md @@ -0,0 +1,24 @@ + +## Input + +```javascript +// @gatingModule +const ErrorView = (error, _retry) => ; + +export default Renderer = (props) => ( + + + + +); + +``` + + +## Error + +``` +ArrowFunctionExpression must be declared in variable declaration +``` + + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/error._todo.multi-arrow-expr-export-default-gating-test.js b/compiler/forget/src/__tests__/fixtures/compiler/error._todo.multi-arrow-expr-export-default-gating-test.js new file mode 100644 index 0000000000..3dc0baec95 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/error._todo.multi-arrow-expr-export-default-gating-test.js @@ -0,0 +1,9 @@ +// @gatingModule +const ErrorView = (error, _retry) => ; + +export default Renderer = (props) => ( + + + + +); diff --git a/compiler/forget/src/__tests__/fixtures/compiler/multi-arrow-expr-export-gating-test.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/multi-arrow-expr-export-gating-test.expect.md new file mode 100644 index 0000000000..cf5997c390 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/multi-arrow-expr-export-gating-test.expect.md @@ -0,0 +1,79 @@ + +## Input + +```javascript +// @gatingModule +const ErrorView = (error, _retry) => ; + +export const Renderer = (props) => ( + + + + +); + +``` + +## Code + +```javascript +import isForgetEnabled from "ReactForgetFeatureFlag"; // @gatingModule +function ErrorView_uncompiled(error, _retry) { + return ; +} +function ErrorView_forget(error, _retry) { + const $ = React.unstable_useMemoCache(2); + const c_0 = $[0] !== error; + let t0; + if (c_0) { + t0 = ; + $[0] = error; + $[1] = t0; + } else { + t0 = $[1]; + } + return t0; +} +const ErrorView = isForgetEnabled ? ErrorView_forget : ErrorView_uncompiled; +function Renderer_uncompiled(props) { + return ( + + + + + ); +} +function Renderer_forget(props) { + const $ = React.unstable_useMemoCache(3); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = ; + $[1] = t1; + } else { + t1 = $[1]; + } + let t2; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t2 = ( + + {t0} + {t1} + + ); + $[2] = t2; + } else { + t2 = $[2]; + } + return t2; +} +export const Renderer = isForgetEnabled ? Renderer_forget : Renderer_uncompiled; + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/multi-arrow-expr-export-gating-test.js b/compiler/forget/src/__tests__/fixtures/compiler/multi-arrow-expr-export-gating-test.js new file mode 100644 index 0000000000..6a983b22ad --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/multi-arrow-expr-export-gating-test.js @@ -0,0 +1,9 @@ +// @gatingModule +const ErrorView = (error, _retry) => ; + +export const Renderer = (props) => ( + + + + +); diff --git a/compiler/forget/src/__tests__/fixtures/compiler/multi-arrow-expr-gating-test.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/multi-arrow-expr-gating-test.expect.md new file mode 100644 index 0000000000..0e7ccd3538 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/multi-arrow-expr-gating-test.expect.md @@ -0,0 +1,82 @@ + +## Input + +```javascript +// @gatingModule +const ErrorView = (error, _retry) => ; + +const Renderer = (props) => ( + + + + +); + +export default Renderer; + +``` + +## Code + +```javascript +import isForgetEnabled from "ReactForgetFeatureFlag"; // @gatingModule +function ErrorView_uncompiled(error, _retry) { + return ; +} +function ErrorView_forget(error, _retry) { + const $ = React.unstable_useMemoCache(2); + const c_0 = $[0] !== error; + let t0; + if (c_0) { + t0 = ; + $[0] = error; + $[1] = t0; + } else { + t0 = $[1]; + } + return t0; +} +const ErrorView = isForgetEnabled ? ErrorView_forget : ErrorView_uncompiled; +function Renderer_uncompiled(props) { + return ( + + + + + ); +} +function Renderer_forget(props) { + const $ = React.unstable_useMemoCache(3); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = ; + $[1] = t1; + } else { + t1 = $[1]; + } + let t2; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t2 = ( + + {t0} + {t1} + + ); + $[2] = t2; + } else { + t2 = $[2]; + } + return t2; +} +const Renderer = isForgetEnabled ? Renderer_forget : Renderer_uncompiled; +export default Renderer; + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/multi-arrow-expr-gating-test.js b/compiler/forget/src/__tests__/fixtures/compiler/multi-arrow-expr-gating-test.js new file mode 100644 index 0000000000..1b99d393b3 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/multi-arrow-expr-gating-test.js @@ -0,0 +1,11 @@ +// @gatingModule +const ErrorView = (error, _retry) => ; + +const Renderer = (props) => ( + + + + +); + +export default Renderer;