Skip to content

Commit

Permalink
Fix dynamic rebinding for hoisted JSX
Browse files Browse the repository at this point in the history
  • Loading branch information
lxsmnsyc committed Feb 7, 2024
1 parent 5705699 commit 13d30da
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 15 deletions.
8 changes: 8 additions & 0 deletions input.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,12 @@ function Example(props) {
);
}

function App() {
return (
<div>
<Example />
</div>
);
}

const hello = <Example />;
15 changes: 15 additions & 0 deletions src/babel/core/is-statement-top-level.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type * as babel from '@babel/core';
import type * as t from '@babel/types';

export function isStatementTopLevel(
path: babel.NodePath<t.Statement>,
): boolean {
let blockParent = path.scope.getBlockParent();
const programParent = path.scope.getProgramParent();
// a FunctionDeclaration binding refers to itself as the block parent
if (blockParent.path === path) {
blockParent = blockParent.parent;
}

return programParent === blockParent;
}
18 changes: 14 additions & 4 deletions src/babel/core/transform-jsx.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import * as t from '@babel/types';
import type * as babel from '@babel/core';
import { getDescriptiveName } from './get-descriptive-name';
import { isPathValid, unwrapNode } from './unwrap';
import * as t from '@babel/types';
import { isComponentishName } from './checks';
import { generateUniqueName } from './generate-unique-name';
import { getDescriptiveName } from './get-descriptive-name';
import { getRootStatementPath } from './get-root-statement-path';
import { isComponentishName } from './checks';
import { isStatementTopLevel } from './is-statement-top-level';
import { isPathValid, unwrapNode } from './unwrap';

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

Expand Down Expand Up @@ -188,6 +189,15 @@ function extractJSXExpressionsFromJSXElement(
/^[A-Z_]/.test(openingName.node.name)) ||
isPathValid(openingName, t.isJSXMemberExpression)
) {
if (isPathValid(openingName, t.isJSXIdentifier)) {
const binding = path.scope.getBinding(openingName.node.name);
if (binding) {
const statementPath = binding.path.getStatementParent();
if (statementPath && isStatementTopLevel(statementPath)) {
return;
}
}
}
const key = pushAttribute(
state,
convertJSXOpeningToExpression(openingName.node),
Expand Down
12 changes: 1 addition & 11 deletions src/babel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { getHMRDeclineCall } from './core/get-hmr-decline-call';
import { getHotIdentifier } from './core/get-hot-identifier';
import { getImportIdentifier } from './core/get-import-identifier';
import { getStatementPath } from './core/get-statement-path';
import { isStatementTopLevel } from './core/is-statement-top-level';
import { isValidCallee } from './core/is-valid-callee';
import { registerImportSpecifiers } from './core/register-import-specifiers';
import { transformJSX } from './core/transform-jsx';
Expand Down Expand Up @@ -204,17 +205,6 @@ function setupProgram(
return isDone;
}

function isStatementTopLevel(path: babel.NodePath<t.Statement>): boolean {
let blockParent = path.scope.getBlockParent();
const programParent = path.scope.getProgramParent();
// a FunctionDeclaration binding refers to itself as the block parent
if (blockParent.path === path) {
blockParent = blockParent.parent;
}

return programParent === blockParent;
}

function isValidFunction(
node: t.Node,
): node is t.ArrowFunctionExpression | t.FunctionExpression {
Expand Down

0 comments on commit 13d30da

Please sign in to comment.