Skip to content

Commit 13d30da

Browse files
committed
Fix dynamic rebinding for hoisted JSX
1 parent 5705699 commit 13d30da

4 files changed

Lines changed: 38 additions & 15 deletions

File tree

input.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,12 @@ function Example(props) {
88
);
99
}
1010

11+
function App() {
12+
return (
13+
<div>
14+
<Example />
15+
</div>
16+
);
17+
}
18+
1119
const hello = <Example />;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type * as babel from '@babel/core';
2+
import type * as t from '@babel/types';
3+
4+
export function isStatementTopLevel(
5+
path: babel.NodePath<t.Statement>,
6+
): boolean {
7+
let blockParent = path.scope.getBlockParent();
8+
const programParent = path.scope.getProgramParent();
9+
// a FunctionDeclaration binding refers to itself as the block parent
10+
if (blockParent.path === path) {
11+
blockParent = blockParent.parent;
12+
}
13+
14+
return programParent === blockParent;
15+
}

src/babel/core/transform-jsx.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import * as t from '@babel/types';
21
import type * as babel from '@babel/core';
3-
import { getDescriptiveName } from './get-descriptive-name';
4-
import { isPathValid, unwrapNode } from './unwrap';
2+
import * as t from '@babel/types';
3+
import { isComponentishName } from './checks';
54
import { generateUniqueName } from './generate-unique-name';
5+
import { getDescriptiveName } from './get-descriptive-name';
66
import { getRootStatementPath } from './get-root-statement-path';
7-
import { isComponentishName } from './checks';
7+
import { isStatementTopLevel } from './is-statement-top-level';
8+
import { isPathValid, unwrapNode } from './unwrap';
89

910
const REFRESH_JSX_SKIP = /^\s*@refresh jsx-skip\s*$/;
1011

@@ -188,6 +189,15 @@ function extractJSXExpressionsFromJSXElement(
188189
/^[A-Z_]/.test(openingName.node.name)) ||
189190
isPathValid(openingName, t.isJSXMemberExpression)
190191
) {
192+
if (isPathValid(openingName, t.isJSXIdentifier)) {
193+
const binding = path.scope.getBinding(openingName.node.name);
194+
if (binding) {
195+
const statementPath = binding.path.getStatementParent();
196+
if (statementPath && isStatementTopLevel(statementPath)) {
197+
return;
198+
}
199+
}
200+
}
191201
const key = pushAttribute(
192202
state,
193203
convertJSXOpeningToExpression(openingName.node),

src/babel/index.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { getHMRDeclineCall } from './core/get-hmr-decline-call';
1414
import { getHotIdentifier } from './core/get-hot-identifier';
1515
import { getImportIdentifier } from './core/get-import-identifier';
1616
import { getStatementPath } from './core/get-statement-path';
17+
import { isStatementTopLevel } from './core/is-statement-top-level';
1718
import { isValidCallee } from './core/is-valid-callee';
1819
import { registerImportSpecifiers } from './core/register-import-specifiers';
1920
import { transformJSX } from './core/transform-jsx';
@@ -204,17 +205,6 @@ function setupProgram(
204205
return isDone;
205206
}
206207

207-
function isStatementTopLevel(path: babel.NodePath<t.Statement>): boolean {
208-
let blockParent = path.scope.getBlockParent();
209-
const programParent = path.scope.getProgramParent();
210-
// a FunctionDeclaration binding refers to itself as the block parent
211-
if (blockParent.path === path) {
212-
blockParent = blockParent.parent;
213-
}
214-
215-
return programParent === blockParent;
216-
}
217-
218208
function isValidFunction(
219209
node: t.Node,
220210
): node is t.ArrowFunctionExpression | t.FunctionExpression {

0 commit comments

Comments
 (0)