Skip to content

Commit 81c2ab3

Browse files
committed
Attach signatures at every nesting level
1 parent 38423eb commit 81c2ab3

File tree

3 files changed

+46
-52
lines changed

3 files changed

+46
-52
lines changed

packages/react-refresh/src/ReactFreshBabelPlugin.js

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -333,24 +333,23 @@ export default function(babel, opts = {}) {
333333
return args;
334334
}
335335

336-
// Traverse HOC calls upwards to the rootmost one.
337-
function findOuterCallPath(path) {
338-
let outerCallPath = null;
336+
function findHOCCallPathsAbove(path) {
337+
const calls = [];
339338
while (true) {
340339
if (!path) {
341-
return outerCallPath;
340+
return calls;
342341
}
343342
if (path.node.type === 'AssignmentExpression') {
344343
// Ignore registrations.
345344
path = path.parentPath;
346345
continue;
347346
}
348347
if (path.node.type === 'CallExpression') {
349-
outerCallPath = path;
348+
calls.push(path);
350349
path = path.parentPath;
351350
continue;
352351
}
353-
return outerCallPath; // Stop at other types.
352+
return calls; // Stop at other types.
354353
}
355354
}
356355

@@ -651,17 +650,16 @@ export default function(babel, opts = {}) {
651650
// Result: let Foo = () => {}; __signature(Foo, ...);
652651
} else {
653652
// let Foo = hoc(() => {})
654-
const outerCallPath = findOuterCallPath(path.parentPath);
655-
if (outerCallPath) {
656-
path = outerCallPath;
657-
}
658-
path.replaceWith(
659-
t.callExpression(
660-
sigCallID,
661-
createArgumentsForSignature(path.node, signature, path.scope),
662-
),
663-
);
664-
// Result: let Foo = __signature(hoc(() => {}), ...)
653+
const paths = [path, ...findHOCCallPathsAbove(path.parentPath)];
654+
paths.forEach(p => {
655+
p.replaceWith(
656+
t.callExpression(
657+
sigCallID,
658+
createArgumentsForSignature(p.node, signature, p.scope),
659+
),
660+
);
661+
});
662+
// Result: let Foo = __signature(hoc(__signature(() => {}, ...)), ...)
665663
}
666664
},
667665
},

packages/react-refresh/src/ReactFreshRuntime.js

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -612,57 +612,53 @@ export function _getMountedRootCount() {
612612
// function Hello() {
613613
// const [foo, setFoo] = useState(0);
614614
// const value = useCustomHook();
615-
// _s(); /* Second call triggers collecting the custom Hook list.
615+
// _s(); /* Call without arguments triggers collecting the custom Hook list.
616616
// * This doesn't happen during the module evaluation because we
617617
// * don't want to change the module order with inline requires.
618618
// * Next calls are noops. */
619619
// return <h1>Hi</h1>;
620620
// }
621621
//
622-
// /* First call specifies the signature: */
622+
// /* Call with arguments attaches the signature to the type: */
623623
// _s(
624624
// Hello,
625625
// 'useState{[foo, setFoo]}(0)',
626626
// () => [useCustomHook], /* Lazy to avoid triggering inline requires */
627627
// );
628-
type SignatureStatus = 'needsSignature' | 'needsCustomHooks' | 'resolved';
629628
export function createSignatureFunctionForTransform() {
630629
if (__DEV__) {
631-
// We'll fill in the signature in two steps.
632-
// First, we'll know the signature itself. This happens outside the component.
633-
// Then, we'll know the references to custom Hooks. This happens inside the component.
634-
// After that, the returned function will be a fast path no-op.
635-
let status: SignatureStatus = 'needsSignature';
636630
let savedType;
637631
let hasCustomHooks;
632+
let didCollectHooks = false;
638633
return function<T>(
639634
type: T,
640635
key: string,
641636
forceReset?: boolean,
642637
getCustomHooks?: () => Array<Function>,
643638
): T {
644-
switch (status) {
645-
case 'needsSignature':
646-
if (type !== undefined) {
647-
// If we received an argument, this is the initial registration call.
648-
savedType = type;
649-
hasCustomHooks = typeof getCustomHooks === 'function';
650-
setSignature(type, key, forceReset, getCustomHooks);
651-
// The next call we expect is from inside a function, to fill in the custom Hooks.
652-
status = 'needsCustomHooks';
653-
}
654-
break;
655-
case 'needsCustomHooks':
656-
if (hasCustomHooks) {
657-
collectCustomHooksForSignature(savedType);
658-
}
659-
status = 'resolved';
660-
break;
661-
case 'resolved':
662-
// Do nothing. Fast path for all future renders.
663-
break;
639+
if (typeof key === 'string') {
640+
// We're in the initial phase that associates signatures
641+
// with the functions. Note this may be called multiple times
642+
// in HOC chains like _s(hoc1(_s(hoc2(_s(actualFunction))))).
643+
if (!savedType) {
644+
// We're in the innermost call, so this is the actual type.
645+
savedType = type;
646+
hasCustomHooks = typeof getCustomHooks === 'function';
647+
}
648+
// Set the signature for all types (even wrappers!) in case
649+
// they have no signatures of their own. This is to prevent
650+
// problems like https://github.com/facebook/react/issues/20417.
651+
setSignature(type, key, forceReset, getCustomHooks);
652+
return type;
653+
} else {
654+
// We're in the _s() call without arguments, which means
655+
// this is the time to collect custom Hook signatures.
656+
// Only do this once. This path is hot and runs *inside* every render!
657+
if (!didCollectHooks && hasCustomHooks) {
658+
didCollectHooks = true;
659+
collectCustomHooksForSignature(savedType);
660+
}
664661
}
665-
return type;
666662
};
667663
} else {
668664
throw new Error(

packages/react-refresh/src/__tests__/__snapshots__/ReactFreshBabelPlugin-test.js.snap

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ _s4(Bar, "useContext{}");
3838
3939
_c2 = Bar;
4040
41-
const Baz = _s5(memo(_c3 = () => {
41+
const Baz = _s5(memo(_c3 = _s5(() => {
4242
_s5();
4343
4444
return useContext(X);
45-
}), "useContext{}");
45+
}, "useContext{}")), "useContext{}");
4646
4747
_c4 = Baz;
4848
@@ -110,21 +110,21 @@ exports[`ReactFreshBabelPlugin generates signatures for function expressions cal
110110
var _s = $RefreshSig$(),
111111
_s2 = $RefreshSig$();
112112
113-
export const A = _s(React.memo(_c2 = React.forwardRef(_c = (props, ref) => {
113+
export const A = _s(React.memo(_c2 = _s(React.forwardRef(_c = _s((props, ref) => {
114114
_s();
115115
116116
const [foo, setFoo] = useState(0);
117117
React.useEffect(() => {});
118118
return <h1 ref={ref}>{foo}</h1>;
119-
})), "useState{[foo, setFoo](0)}\\nuseEffect{}");
119+
}, "useState{[foo, setFoo](0)}\\nuseEffect{}")), "useState{[foo, setFoo](0)}\\nuseEffect{}")), "useState{[foo, setFoo](0)}\\nuseEffect{}");
120120
_c3 = A;
121-
export const B = _s2(React.memo(_c5 = React.forwardRef(_c4 = function (props, ref) {
121+
export const B = _s2(React.memo(_c5 = _s2(React.forwardRef(_c4 = _s2(function (props, ref) {
122122
_s2();
123123
124124
const [foo, setFoo] = useState(0);
125125
React.useEffect(() => {});
126126
return <h1 ref={ref}>{foo}</h1>;
127-
})), "useState{[foo, setFoo](0)}\\nuseEffect{}");
127+
}, "useState{[foo, setFoo](0)}\\nuseEffect{}")), "useState{[foo, setFoo](0)}\\nuseEffect{}")), "useState{[foo, setFoo](0)}\\nuseEffect{}");
128128
_c6 = B;
129129
130130
function hoc() {

0 commit comments

Comments
 (0)