[Babel] Desugar ArrowFunctionExpression

Rewrite ArrowFunctionExpression to FunctionDeclaration and compile it. This lets 
us reuse all the export gating logic, rather than writing separate, specific 
logic for ArrowFunctionExpression.
This commit is contained in:
Sathya Gunasekaran
2023-03-20 17:42:58 +00:00
parent ff571d8d85
commit 9b5ff25b3b
9 changed files with 314 additions and 0 deletions
+59
View File
@@ -80,6 +80,17 @@ export default function ReactForgetBabelPlugin(
visitFn(fn, pass);
},
ArrowFunctionExpression(
fn: BabelCore.NodePath<t.ArrowFunctionExpression>,
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<t.ArrowFunctionExpression>
): BabelCore.NodePath<t.FunctionDeclaration> {
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.ArrowFunctionExpression>
): 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<t.FunctionDeclaration>,
compiled: t.Identifier,
@@ -0,0 +1,37 @@
## Input
```javascript
// @gatingModule
const ErrorView = (error, _retry) => <MessageBox error={error}></MessageBox>;
export default ErrorView;
```
## Code
```javascript
import isForgetEnabled from "ReactForgetFeatureFlag"; // @gatingModule
function ErrorView_uncompiled(error, _retry) {
return <MessageBox error={error}></MessageBox>;
}
function ErrorView_forget(error, _retry) {
const $ = React.unstable_useMemoCache(2);
const c_0 = $[0] !== error;
let t0;
if (c_0) {
t0 = <MessageBox error={error}></MessageBox>;
$[0] = error;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
const ErrorView = isForgetEnabled ? ErrorView_forget : ErrorView_uncompiled;
export default ErrorView;
```
@@ -0,0 +1,4 @@
// @gatingModule
const ErrorView = (error, _retry) => <MessageBox error={error}></MessageBox>;
export default ErrorView;
@@ -0,0 +1,24 @@
## Input
```javascript
// @gatingModule
const ErrorView = (error, _retry) => <MessageBox error={error}></MessageBox>;
export default Renderer = (props) => (
<Foo>
<Bar></Bar>
<ErrorView></ErrorView>
</Foo>
);
```
## Error
```
ArrowFunctionExpression must be declared in variable declaration
```
@@ -0,0 +1,9 @@
// @gatingModule
const ErrorView = (error, _retry) => <MessageBox error={error}></MessageBox>;
export default Renderer = (props) => (
<Foo>
<Bar></Bar>
<ErrorView></ErrorView>
</Foo>
);
@@ -0,0 +1,79 @@
## Input
```javascript
// @gatingModule
const ErrorView = (error, _retry) => <MessageBox error={error}></MessageBox>;
export const Renderer = (props) => (
<Foo>
<Bar></Bar>
<ErrorView></ErrorView>
</Foo>
);
```
## Code
```javascript
import isForgetEnabled from "ReactForgetFeatureFlag"; // @gatingModule
function ErrorView_uncompiled(error, _retry) {
return <MessageBox error={error}></MessageBox>;
}
function ErrorView_forget(error, _retry) {
const $ = React.unstable_useMemoCache(2);
const c_0 = $[0] !== error;
let t0;
if (c_0) {
t0 = <MessageBox error={error}></MessageBox>;
$[0] = error;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
const ErrorView = isForgetEnabled ? ErrorView_forget : ErrorView_uncompiled;
function Renderer_uncompiled(props) {
return (
<Foo>
<Bar></Bar>
<ErrorView></ErrorView>
</Foo>
);
}
function Renderer_forget(props) {
const $ = React.unstable_useMemoCache(3);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <Bar></Bar>;
$[0] = t0;
} else {
t0 = $[0];
}
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <ErrorView></ErrorView>;
$[1] = t1;
} else {
t1 = $[1];
}
let t2;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = (
<Foo>
{t0}
{t1}
</Foo>
);
$[2] = t2;
} else {
t2 = $[2];
}
return t2;
}
export const Renderer = isForgetEnabled ? Renderer_forget : Renderer_uncompiled;
```
@@ -0,0 +1,9 @@
// @gatingModule
const ErrorView = (error, _retry) => <MessageBox error={error}></MessageBox>;
export const Renderer = (props) => (
<Foo>
<Bar></Bar>
<ErrorView></ErrorView>
</Foo>
);
@@ -0,0 +1,82 @@
## Input
```javascript
// @gatingModule
const ErrorView = (error, _retry) => <MessageBox error={error}></MessageBox>;
const Renderer = (props) => (
<Foo>
<Bar></Bar>
<ErrorView></ErrorView>
</Foo>
);
export default Renderer;
```
## Code
```javascript
import isForgetEnabled from "ReactForgetFeatureFlag"; // @gatingModule
function ErrorView_uncompiled(error, _retry) {
return <MessageBox error={error}></MessageBox>;
}
function ErrorView_forget(error, _retry) {
const $ = React.unstable_useMemoCache(2);
const c_0 = $[0] !== error;
let t0;
if (c_0) {
t0 = <MessageBox error={error}></MessageBox>;
$[0] = error;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
const ErrorView = isForgetEnabled ? ErrorView_forget : ErrorView_uncompiled;
function Renderer_uncompiled(props) {
return (
<Foo>
<Bar></Bar>
<ErrorView></ErrorView>
</Foo>
);
}
function Renderer_forget(props) {
const $ = React.unstable_useMemoCache(3);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <Bar></Bar>;
$[0] = t0;
} else {
t0 = $[0];
}
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <ErrorView></ErrorView>;
$[1] = t1;
} else {
t1 = $[1];
}
let t2;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = (
<Foo>
{t0}
{t1}
</Foo>
);
$[2] = t2;
} else {
t2 = $[2];
}
return t2;
}
const Renderer = isForgetEnabled ? Renderer_forget : Renderer_uncompiled;
export default Renderer;
```
@@ -0,0 +1,11 @@
// @gatingModule
const ErrorView = (error, _retry) => <MessageBox error={error}></MessageBox>;
const Renderer = (props) => (
<Foo>
<Bar></Bar>
<ErrorView></ErrorView>
</Foo>
);
export default Renderer;