Skip to content

Commit

Permalink
Merge pull request #1286 from gaearon/12.2
Browse files Browse the repository at this point in the history
12.2
  • Loading branch information
theKashey committed Jul 4, 2019
2 parents f155bb0 + 49851be commit 2232839
Show file tree
Hide file tree
Showing 10 changed files with 519 additions and 126 deletions.
3 changes: 3 additions & 0 deletions src/configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ const configuration = {
// Disable "hot-replacement-render"
disableHotRenderer: false,

// @private
integratedResolver: false,

// Disable "hot-replacement-render" when injection into react-dom is made
disableHotRendererWhenInjected: true,

Expand Down
108 changes: 23 additions & 85 deletions src/fresh/babel.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@

const SIGNATURE = '__signature__';

export default function(babel) {
export default function (babel) {
const {types: t} = babel;

const registrationsByProgramPath = new Map();

function createRegistration(programPath, persistentID) {
const handle = programPath.scope.generateUidIdentifier('c');
if (!registrationsByProgramPath.has(programPath)) {
Expand Down Expand Up @@ -193,7 +194,15 @@ export default function(babel) {
}
}

function getHookCallsSignature(functionNode) {
function getCalleeName(callee) {
if (callee.type === 'MemberExpression' && callee.object.type === 'Identifier') {
return callee.object.name;
}

return callee.name;
}

function getHookCallsSignature(functionNode, scope) {
const fnHookCalls = hookCalls.get(functionNode);
if (fnHookCalls === undefined) {
return null;
Expand All @@ -206,11 +215,12 @@ export default function(babel) {
};
}

function createArgumentsForSignature(node, signature) {
function createArgumentsForSignature(node, signature, scope) {
const {key, customHooks} = signature;
const args = [node, t.stringLiteral(key)];
if (customHooks.length > 0) {
args.push(t.arrowFunctionExpression([], t.arrayExpression(customHooks)));
const hooksInScope = customHooks.filter(call => scope.hasBinding(getCalleeName(call)));
if (hooksInScope.length > 0) {
args.push(t.arrowFunctionExpression([], t.arrayExpression(hooksInScope)));
}
return args;
}
Expand Down Expand Up @@ -240,6 +250,7 @@ export default function(babel) {
return;
}
const fnScope = path.scope.getFunctionParent();

if (fnScope === null) {
return;
}
Expand All @@ -249,6 +260,7 @@ export default function(babel) {
if (!hookCalls.has(fnNode)) {
hookCalls.set(fnNode, []);
}

let hookCallsForFn = hookCalls.get(fnNode);
let key = '';
if (path.parent.type === 'VariableDeclarator') {
Expand Down Expand Up @@ -290,7 +302,7 @@ export default function(babel) {

// Make sure we're not mutating the same tree twice.
// This can happen if another Babel plugin replaces parents.
if (seenForRegistration.has(node)) {
if (seenForRegistration.has(node)) {
return;
}
seenForRegistration.add(node);
Expand Down Expand Up @@ -384,7 +396,7 @@ export default function(babel) {
if (id === null) {
return;
}
const signature = getHookCallsSignature(node);
const signature = getHookCallsSignature(node, path.scope);
if (signature === null) {
return;
}
Expand Down Expand Up @@ -415,7 +427,7 @@ export default function(babel) {
t.expressionStatement(
t.callExpression(
t.identifier(SIGNATURE),
createArgumentsForSignature(id, signature),
createArgumentsForSignature(id, signature, insertAfterPath.scope),
),
),
);
Expand All @@ -424,7 +436,7 @@ export default function(babel) {
'ArrowFunctionExpression|FunctionExpression': {
exit(path) {
const node = path.node;
const signature = getHookCallsSignature(node);
const signature = getHookCallsSignature(node, path.scope);
if (signature === null) {
return;
}
Expand Down Expand Up @@ -457,7 +469,7 @@ export default function(babel) {
t.expressionStatement(
t.callExpression(
t.identifier(SIGNATURE),
createArgumentsForSignature(path.parent.id, signature),
createArgumentsForSignature(path.parent.id, signature, insertAfterPath.scope),
),
),
);
Expand All @@ -467,87 +479,13 @@ export default function(babel) {
path.replaceWith(
t.callExpression(
t.identifier(SIGNATURE),
createArgumentsForSignature(node, signature),
createArgumentsForSignature(node, signature, path.scope),
),
);
// Result: let Foo = hoc(__signature(() => {}, ...))
}
},
},
VariableDeclaration(path) {
return;
const node = path.node;
let programPath;
let insertAfterPath;
switch (path.parent.type) {
case 'Program':
insertAfterPath = path;
programPath = path.parentPath;
break;
case 'ExportNamedDeclaration':
insertAfterPath = path.parentPath;
programPath = insertAfterPath.parentPath;
break;
case 'ExportDefaultDeclaration':
insertAfterPath = path.parentPath;
programPath = insertAfterPath.parentPath;
break;
default:
return;
}

// Make sure we're not mutating the same tree twice.
// This can happen if another Babel plugin replaces parents.
if (seenForRegistration.has(node)) {
return;
}
seenForRegistration.add(node);
// Don't mutate the tree above this point.

const declPaths = path.get('declarations');
if (declPaths.length !== 1) {
return;
}
const declPath = declPaths[0];
const inferredName = declPath.node.id.name;
findInnerComponents(
inferredName,
declPath,
(persistentID, targetExpr, targetPath) => {
if (targetPath === null) {
// For case like:
// export const Something = hoc(Foo)
// we don't want to wrap Foo inside the call.
// Instead we assume it's registered at definition.
return;
}
const handle = createRegistration(programPath, persistentID);
if (
(targetExpr.type === 'ArrowFunctionExpression' ||
targetExpr.type === 'FunctionExpression') &&
targetPath.parent.type === 'VariableDeclarator'
) {
// Special case when a function would get an inferred name:
// let Foo = () => {}
// let Foo = function() {}
// We'll register it on next line so that
// we don't mess up the inferred 'Foo' function name.
insertAfterPath.insertAfter(
t.expressionStatement(
t.assignmentExpression('=', handle, declPath.node.id),
),
);
// Result: let Foo = () => {}; _c1 = Foo;
} else {
// let Foo = hoc(() => {})
targetPath.replaceWith(
t.assignmentExpression('=', handle, targetExpr),
);
// Result: let Foo = _c1 = hoc(() => {})
}
},
);
},
Program: {
enter(path) {
// This is a separate early visitor because we need to collect Hook calls
Expand Down
30 changes: 30 additions & 0 deletions src/internal/reactUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,34 @@ export const isForwardType = ({ type }) =>
type && typeof type === 'object' && '$$typeof' in type && type.$$typeof === ForwardType && ForwardType;
export const isContextType = type => isContextConsumer(type) || isContextProvider(type);

export const getElementType = type => {
const element = { type };

if (isContextConsumer(element)) {
return 'Consumer';
}
if (isContextProvider(element)) {
return 'Provider';
}
if (isLazyType(element)) {
return 'Lazy';
}
if (isMemoType(element)) {
return 'Memo';
}
if (isForwardType(element)) {
return 'Forward';
}

if (isReactClass(type)) {
return 'Class';
}

if (typeof element === 'function') {
return 'FC';
}

return 'unknown';
};

export const getContextProvider = type => type && type._context;
4 changes: 2 additions & 2 deletions src/reactHotLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,12 @@ const reactHotLoader = {
configuration.showReactDomPatchNotification = false;

if (ReactDOM.setHotTypeResolver) {
configuration.intergratedResolver = true;
configuration.integratedResolver = true;
ReactDOM.setHotTypeResolver(resolveType);
}
}

if (!configuration.intergratedResolver) {
if (!configuration.integratedResolver) {
/* eslint-enable */
if (!React.createElement.isPatchedByReactHotLoader) {
const originalCreateElement = React.createElement;
Expand Down
90 changes: 55 additions & 35 deletions src/reconciler/componentComparator.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { getIdByType, getProxyByType, getSignature, isColdType, updateProxyById } from './proxies';
import { hotComparisonOpen } from '../global/generation';
import { isForwardType, isMemoType, isReactClass, isReloadableComponent } from '../internal/reactUtils';
import {
getElementType,
isContextType,
isForwardType,
isLazyType,
isMemoType,
isReactClass,
isReloadableComponent,
} from '../internal/reactUtils';
import { areSwappable } from './utils';
import { PROXY_KEY, UNWRAP_PROXY } from '../proxy';
import { resolveType } from './resolver';
Expand All @@ -13,31 +21,36 @@ const getInnerComponentType = component => {
};

function haveEqualSignatures(prevType, nextType) {
const prevSignature = getSignature(prevType);
const nextSignature = getSignature(nextType);
try {
const prevSignature = getSignature(prevType);
const nextSignature = getSignature(nextType);

if (prevSignature === undefined && nextSignature === undefined) {
return true;
}
if (prevSignature === undefined || nextSignature === undefined) {
return false;
}
if (prevSignature.key !== nextSignature.key) {
return false;
}

// TODO: we might need to calculate previous signature earlier in practice,
// such as during the first time a component is resolved. We'll revisit this.
const prevCustomHooks = prevSignature.getCustomHooks();
const nextCustomHooks = nextSignature.getCustomHooks();
if (prevCustomHooks.length !== nextCustomHooks.length) {
return false;
}
if (prevSignature === undefined && nextSignature === undefined) {
return true;
}
if (prevSignature === undefined || nextSignature === undefined) {
return false;
}
if (prevSignature.key !== nextSignature.key) {
return false;
}

for (let i = 0; i < nextCustomHooks.length; i++) {
if (!haveEqualSignatures(prevCustomHooks[i], nextCustomHooks[i])) {
// TODO: we might need to calculate previous signature earlier in practice,
// such as during the first time a component is resolved. We'll revisit this.
const prevCustomHooks = prevSignature.getCustomHooks();
const nextCustomHooks = nextSignature.getCustomHooks();
if (prevCustomHooks.length !== nextCustomHooks.length) {
return false;
}

for (let i = 0; i < nextCustomHooks.length; i++) {
if (!haveEqualSignatures(prevCustomHooks[i], nextCustomHooks[i])) {
return false;
}
}
} catch (e) {
logger.error('React-Hot-Loader: error occurred while comparing hook signature', e);
return false;
}

return true;
Expand All @@ -56,15 +69,8 @@ const areSignaturesCompatible = (a, b) => {
return true;
};

const compareRegistered = (a, b) => {
if (getIdByType(a) === getIdByType(b)) {
if (getProxyByType(a) !== getProxyByType(b)) {
return false;
}
}

return areSignaturesCompatible(a, b);
};
const compareRegistered = (a, b) =>
getIdByType(a) === getIdByType(b) && getProxyByType(a) === getProxyByType(b) && areSignaturesCompatible(a, b);

const areDeepSwappable = (oldType, newType) => {
const type = { type: oldType };
Expand Down Expand Up @@ -92,11 +98,17 @@ const areDeepSwappable = (oldType, newType) => {
const compareComponents = (oldType, newType, setNewType, baseType) => {
let defaultResult = oldType === newType;

if ((oldType && !newType) || (!oldType && newType)) {
return false;
if (
(oldType && !newType) ||
(!oldType && newType) ||
typeof oldType !== typeof newType ||
getElementType(oldType) !== getElementType(newType) ||
0
) {
return defaultResult;
}

if (getIdByType(oldType)) {
if (getIdByType(newType) || getIdByType(oldType)) {
if (!compareRegistered(oldType, newType)) {
return false;
}
Expand Down Expand Up @@ -140,6 +152,14 @@ const compareComponents = (oldType, newType, setNewType, baseType) => {
return defaultResult;
}

if (isLazyType({ type: oldType })) {
return defaultResult;
}

if (isContextType({ type: oldType })) {
return defaultResult;
}

if (
typeof newType === 'function' &&
(defaultResult ||
Expand All @@ -164,7 +184,7 @@ const emptyMap = new WeakMap();

export const hotComponentCompare = (oldType, preNewType, setNewType, baseType) => {
const hotActive = hotComparisonOpen();
const newType = configuration.intergratedResolver ? resolveType(preNewType) : preNewType;
const newType = configuration.integratedResolver ? resolveType(preNewType) : preNewType;
let result = oldType === newType;

if (!hotActive) {
Expand Down
Loading

0 comments on commit 2232839

Please sign in to comment.