731 lines
25 KiB
JavaScript
731 lines
25 KiB
JavaScript
|
|
'use strict';
|
||
|
|
|
||
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||
|
|
|
||
|
|
// the same as before
|
||
|
|
var PREFIX = '__reactstandin__';
|
||
|
|
var REGENERATE_METHOD = PREFIX + 'regenerateByEval';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||
|
|
*
|
||
|
|
* This source code is licensed under the MIT license found in the
|
||
|
|
* LICENSE file in the root directory of this source tree.
|
||
|
|
*/
|
||
|
|
|
||
|
|
var SIGNATURE = '__signature__';
|
||
|
|
|
||
|
|
function fresh (babel) {
|
||
|
|
var t = babel.types;
|
||
|
|
|
||
|
|
|
||
|
|
var registrationsByProgramPath = new Map();
|
||
|
|
|
||
|
|
function createRegistration(programPath, persistentID) {
|
||
|
|
var handle = programPath.scope.generateUidIdentifier('c');
|
||
|
|
if (!registrationsByProgramPath.has(programPath)) {
|
||
|
|
registrationsByProgramPath.set(programPath, []);
|
||
|
|
}
|
||
|
|
var registrations = registrationsByProgramPath.get(programPath);
|
||
|
|
registrations.push({
|
||
|
|
handle: handle,
|
||
|
|
persistentID: persistentID
|
||
|
|
});
|
||
|
|
return handle;
|
||
|
|
}
|
||
|
|
|
||
|
|
function isComponentishName(name) {
|
||
|
|
return typeof name === 'string' && name[0] >= 'A' && name[0] <= 'Z';
|
||
|
|
}
|
||
|
|
|
||
|
|
function findInnerComponents(inferredName, path, callback) {
|
||
|
|
var node = path.node;
|
||
|
|
switch (node.type) {
|
||
|
|
case 'Identifier':
|
||
|
|
{
|
||
|
|
if (!isComponentishName(node.name)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
// export default hoc(Foo)
|
||
|
|
// const X = hoc(Foo)
|
||
|
|
callback(inferredName, node, null);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
case 'FunctionDeclaration':
|
||
|
|
{
|
||
|
|
// function Foo() {}
|
||
|
|
// export function Foo() {}
|
||
|
|
// export default function Foo() {}
|
||
|
|
callback(inferredName, node.id, null);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
case 'ArrowFunctionExpression':
|
||
|
|
{
|
||
|
|
if (node.body.type === 'ArrowFunctionExpression') {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
// let Foo = () => {}
|
||
|
|
// export default hoc1(hoc2(() => {}))
|
||
|
|
callback(inferredName, node, path);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
case 'FunctionExpression':
|
||
|
|
{
|
||
|
|
// let Foo = function() {}
|
||
|
|
// const Foo = hoc1(forwardRef(function renderFoo() {}))
|
||
|
|
// export default memo(function() {})
|
||
|
|
callback(inferredName, node, path);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
case 'CallExpression':
|
||
|
|
{
|
||
|
|
var argsPath = path.get('arguments');
|
||
|
|
if (argsPath === undefined || argsPath.length === 0) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
var calleePath = path.get('callee');
|
||
|
|
switch (calleePath.node.type) {
|
||
|
|
case 'MemberExpression':
|
||
|
|
case 'Identifier':
|
||
|
|
{
|
||
|
|
var calleeSource = calleePath.getSource();
|
||
|
|
var firstArgPath = argsPath[0];
|
||
|
|
var innerName = inferredName + '$' + calleeSource;
|
||
|
|
var foundInside = findInnerComponents(innerName, firstArgPath, callback);
|
||
|
|
if (!foundInside) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
// const Foo = hoc1(hoc2(() => {}))
|
||
|
|
// export default memo(React.forwardRef(function() {}))
|
||
|
|
callback(inferredName, node, path);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
default:
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
case 'VariableDeclarator':
|
||
|
|
{
|
||
|
|
var init = node.init;
|
||
|
|
if (init === null) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
var name = node.id.name;
|
||
|
|
if (!isComponentishName(name)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (init.type === 'Identifier' || init.type === 'MemberExpression') {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
var initPath = path.get('init');
|
||
|
|
var _foundInside = findInnerComponents(inferredName, initPath, callback);
|
||
|
|
if (_foundInside) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
// See if this identifier is used in JSX. Then it's a component.
|
||
|
|
var binding = path.scope.getBinding(name);
|
||
|
|
if (binding === undefined) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var isLikelyUsedAsType = false;
|
||
|
|
var referencePaths = binding.referencePaths;
|
||
|
|
for (var i = 0; i < referencePaths.length; i++) {
|
||
|
|
var ref = referencePaths[i];
|
||
|
|
if (ref.node.type !== 'JSXIdentifier' && ref.node.type !== 'Identifier') {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
var refParent = ref.parent;
|
||
|
|
if (refParent.type === 'JSXOpeningElement') {
|
||
|
|
isLikelyUsedAsType = true;
|
||
|
|
} else if (refParent.type === 'CallExpression') {
|
||
|
|
var callee = refParent.callee;
|
||
|
|
var fnName = void 0;
|
||
|
|
switch (callee.type) {
|
||
|
|
case 'Identifier':
|
||
|
|
fnName = callee.name;
|
||
|
|
break;
|
||
|
|
case 'MemberExpression':
|
||
|
|
fnName = callee.property.name;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
switch (fnName) {
|
||
|
|
case 'createElement':
|
||
|
|
case 'jsx':
|
||
|
|
case 'jsxDEV':
|
||
|
|
case 'jsxs':
|
||
|
|
isLikelyUsedAsType = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (isLikelyUsedAsType) {
|
||
|
|
// const X = ... + later <X />
|
||
|
|
callback(inferredName, init, initPath);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
function isBuiltinHook(hookName) {
|
||
|
|
switch (hookName) {
|
||
|
|
case 'useState':
|
||
|
|
case 'React.useState':
|
||
|
|
case 'useReducer':
|
||
|
|
case 'React.useReducer':
|
||
|
|
case 'useEffect':
|
||
|
|
case 'React.useEffect':
|
||
|
|
case 'useLayoutEffect':
|
||
|
|
case 'React.useLayoutEffect':
|
||
|
|
case 'useMemo':
|
||
|
|
case 'React.useMemo':
|
||
|
|
case 'useCallback':
|
||
|
|
case 'React.useCallback':
|
||
|
|
case 'useRef':
|
||
|
|
case 'React.useRef':
|
||
|
|
case 'useContext':
|
||
|
|
case 'React.useContext':
|
||
|
|
case 'useImperativeMethods':
|
||
|
|
case 'React.useImperativeMethods':
|
||
|
|
case 'useDebugValue':
|
||
|
|
case 'React.useDebugValue':
|
||
|
|
return true;
|
||
|
|
default:
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function getCalleeName(callee) {
|
||
|
|
if (callee.type === 'MemberExpression' && callee.object.type === 'Identifier') {
|
||
|
|
return callee.object.name;
|
||
|
|
}
|
||
|
|
|
||
|
|
return callee.name;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getHookCallsSignature(functionNode, scope) {
|
||
|
|
var fnHookCalls = hookCalls.get(functionNode);
|
||
|
|
if (fnHookCalls === undefined) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
key: fnHookCalls.map(function (call) {
|
||
|
|
return call.name + '{' + call.key + '}';
|
||
|
|
}).join('\n'),
|
||
|
|
customHooks: fnHookCalls.filter(function (call) {
|
||
|
|
return !isBuiltinHook(call.name);
|
||
|
|
}).filter(function (call) {
|
||
|
|
return scope.parent.hasBinding(call.name);
|
||
|
|
}).map(function (call) {
|
||
|
|
return t.cloneDeep(call.callee);
|
||
|
|
})
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function createArgumentsForSignature(node, signature, scope) {
|
||
|
|
var key = signature.key,
|
||
|
|
customHooks = signature.customHooks;
|
||
|
|
|
||
|
|
var args = [node, t.stringLiteral(key)];
|
||
|
|
var hooksInScope = customHooks.filter(function (call) {
|
||
|
|
return scope.hasBinding(getCalleeName(call));
|
||
|
|
});
|
||
|
|
if (hooksInScope.length > 0) {
|
||
|
|
args.push(t.arrowFunctionExpression([], t.arrayExpression(hooksInScope)));
|
||
|
|
}
|
||
|
|
return args;
|
||
|
|
}
|
||
|
|
|
||
|
|
var seenForRegistration = new WeakSet();
|
||
|
|
var seenForSignature = new WeakSet();
|
||
|
|
|
||
|
|
var hookCalls = new WeakMap();
|
||
|
|
var HookCallsVisitor = {
|
||
|
|
CallExpression: function CallExpression(path) {
|
||
|
|
var node = path.node;
|
||
|
|
var callee = node.callee;
|
||
|
|
|
||
|
|
// Note: this visitor MUST NOT mutate the tree in any way.
|
||
|
|
// It runs early in a separate traversal and should be very fast.
|
||
|
|
|
||
|
|
var name = null;
|
||
|
|
switch (callee.type) {
|
||
|
|
case 'Identifier':
|
||
|
|
name = callee.name;
|
||
|
|
break;
|
||
|
|
case 'MemberExpression':
|
||
|
|
name = callee.property.name;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (name === null || !/^use[A-Z]/.test(name)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var fnScope = path.scope.getFunctionParent();
|
||
|
|
|
||
|
|
if (fnScope === null) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// This is a Hook call. Record it.
|
||
|
|
var fnNode = fnScope.block;
|
||
|
|
if (!hookCalls.has(fnNode)) {
|
||
|
|
hookCalls.set(fnNode, []);
|
||
|
|
}
|
||
|
|
|
||
|
|
var hookCallsForFn = hookCalls.get(fnNode);
|
||
|
|
var key = '';
|
||
|
|
if (path.parent.type === 'VariableDeclarator') {
|
||
|
|
// TODO: if there is no LHS, consider some other heuristic.
|
||
|
|
key = path.parentPath.get('id').getSource();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Some built-in Hooks reset on edits to arguments.
|
||
|
|
var args = path.get('arguments');
|
||
|
|
if (name === 'useState' && args.length > 0) {
|
||
|
|
// useState second argument is initial state.
|
||
|
|
key += '(' + args[0].getSource() + ')';
|
||
|
|
} else if (name === 'useReducer' && args.length > 1) {
|
||
|
|
// useReducer second argument is initial state.
|
||
|
|
key += '(' + args[1].getSource() + ')';
|
||
|
|
}
|
||
|
|
|
||
|
|
hookCallsForFn.push({
|
||
|
|
callee: path.node.callee,
|
||
|
|
name: name,
|
||
|
|
key: key
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return {
|
||
|
|
visitor: {
|
||
|
|
ExportDefaultDeclaration: function ExportDefaultDeclaration(path) {
|
||
|
|
var node = path.node;
|
||
|
|
var decl = node.declaration;
|
||
|
|
var declPath = path.get('declaration');
|
||
|
|
if (decl.type !== 'CallExpression') {
|
||
|
|
// For now, we only support possible HOC calls here.
|
||
|
|
// Named function declarations are handled in FunctionDeclaration.
|
||
|
|
// Anonymous direct exports like export default function() {}
|
||
|
|
// are currently ignored.
|
||
|
|
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.
|
||
|
|
|
||
|
|
// This code path handles nested cases like:
|
||
|
|
// export default memo(() => {})
|
||
|
|
// In those cases it is more plausible people will omit names
|
||
|
|
// so they're worth handling despite possible false positives.
|
||
|
|
// More importantly, it handles the named case:
|
||
|
|
// export default memo(function Named() {})
|
||
|
|
var inferredName = '%default%';
|
||
|
|
var programPath = path.parentPath;
|
||
|
|
findInnerComponents(inferredName, declPath, function (persistentID, targetExpr, targetPath) {
|
||
|
|
if (targetPath === null) {
|
||
|
|
// For case like:
|
||
|
|
// export default hoc(Foo)
|
||
|
|
// we don't want to wrap Foo inside the call.
|
||
|
|
// Instead we assume it's registered at definition.
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var handle = createRegistration(programPath, persistentID);
|
||
|
|
targetPath.replaceWith(t.assignmentExpression('=', handle, targetExpr));
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
FunctionDeclaration: {
|
||
|
|
enter: function enter(path) {
|
||
|
|
return;
|
||
|
|
var node = path.node;
|
||
|
|
var programPath = void 0;
|
||
|
|
var insertAfterPath = void 0;
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
var id = node.id;
|
||
|
|
if (id === null) {
|
||
|
|
// We don't currently handle anonymous default exports.
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var inferredName = id.name;
|
||
|
|
if (!isComponentishName(inferredName)) {
|
||
|
|
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.
|
||
|
|
|
||
|
|
// export function Named() {}
|
||
|
|
// function Named() {}
|
||
|
|
findInnerComponents(inferredName, path, function (persistentID, targetExpr) {
|
||
|
|
var handle = createRegistration(programPath, persistentID);
|
||
|
|
insertAfterPath.insertAfter(t.expressionStatement(t.assignmentExpression('=', handle, targetExpr)));
|
||
|
|
});
|
||
|
|
},
|
||
|
|
exit: function exit(path) {
|
||
|
|
//return;
|
||
|
|
var node = path.node;
|
||
|
|
var id = node.id;
|
||
|
|
if (id === null) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var signature = getHookCallsSignature(node, path.scope);
|
||
|
|
if (signature === null) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Make sure we're not mutating the same tree twice.
|
||
|
|
// This can happen if another Babel plugin replaces parents.
|
||
|
|
if (seenForSignature.has(node)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
seenForSignature.add(node);
|
||
|
|
|
||
|
|
// Unlike with __register__, this needs to work for nested
|
||
|
|
// declarations too. So we need to search for a path where
|
||
|
|
// we can insert a statement rather than hardcoding it.
|
||
|
|
var insertAfterPath = null;
|
||
|
|
path.find(function (p) {
|
||
|
|
if (p.parentPath.isBlock()) {
|
||
|
|
insertAfterPath = p;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
if (insertAfterPath === null) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
insertAfterPath.insertAfter(t.expressionStatement(t.callExpression(t.identifier(SIGNATURE), createArgumentsForSignature(id, signature, insertAfterPath.scope))));
|
||
|
|
}
|
||
|
|
},
|
||
|
|
'ArrowFunctionExpression|FunctionExpression': {
|
||
|
|
exit: function exit(path) {
|
||
|
|
var node = path.node;
|
||
|
|
var signature = getHookCallsSignature(node, path.scope);
|
||
|
|
if (signature === null) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Make sure we're not mutating the same tree twice.
|
||
|
|
// This can happen if another Babel plugin replaces parents.
|
||
|
|
if (seenForSignature.has(node)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
seenForSignature.add(node);
|
||
|
|
// Don't mutate the tree above this point.
|
||
|
|
|
||
|
|
if (path.parent.type === 'VariableDeclarator') {
|
||
|
|
var insertAfterPath = null;
|
||
|
|
path.find(function (p) {
|
||
|
|
if (p.parentPath.isBlock()) {
|
||
|
|
insertAfterPath = p;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
if (insertAfterPath === null) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
// Special case when a function would get an inferred name:
|
||
|
|
// let Foo = () => {}
|
||
|
|
// let Foo = function() {}
|
||
|
|
// We'll add signature it on next line so that
|
||
|
|
// we don't mess up the inferred 'Foo' function name.
|
||
|
|
insertAfterPath.insertAfter(t.expressionStatement(t.callExpression(t.identifier(SIGNATURE), createArgumentsForSignature(path.parent.id, signature, insertAfterPath.scope))));
|
||
|
|
// Result: let Foo = () => {}; __signature(Foo, ...);
|
||
|
|
} else {
|
||
|
|
// let Foo = hoc(() => {})
|
||
|
|
path.replaceWith(t.callExpression(t.identifier(SIGNATURE), createArgumentsForSignature(node, signature, path.scope)));
|
||
|
|
// Result: let Foo = hoc(__signature(() => {}, ...))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
Program: {
|
||
|
|
enter: function enter(path) {
|
||
|
|
// This is a separate early visitor because we need to collect Hook calls
|
||
|
|
// and "const [foo, setFoo] = ..." signatures before the destructuring
|
||
|
|
// transform mangles them. This extra traversal is not ideal for perf,
|
||
|
|
// but it's the best we can do until we stop transpiling destructuring.
|
||
|
|
path.traverse(HookCallsVisitor);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
|
||
|
|
return typeof obj;
|
||
|
|
} : function (obj) {
|
||
|
|
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
|
||
|
|
};
|
||
|
|
|
||
|
|
var templateOptions = {
|
||
|
|
placeholderPattern: /^([A-Z0-9]+)([A-Z0-9_]+)$/
|
||
|
|
};
|
||
|
|
|
||
|
|
/* eslint-disable */
|
||
|
|
var shouldIgnoreFile = function shouldIgnoreFile(file) {
|
||
|
|
return !!file.split('\\').join('/').match(/node_modules\/(react|react-dom|react-hot-loader)([\/]|$)/);
|
||
|
|
};
|
||
|
|
|
||
|
|
/* eslint-enable */
|
||
|
|
|
||
|
|
function plugin(args) {
|
||
|
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||
|
|
|
||
|
|
// This is a Babel plugin, but the user put it in the Webpack config.
|
||
|
|
if (this && this.callback) {
|
||
|
|
throw new Error('React Hot Loader: You are erroneously trying to use a Babel plugin ' + 'as a Webpack loader. We recommend that you use Babel, ' + 'remove "react-hot-loader/babel" from the "loaders" section ' + 'of your Webpack configuration, and instead add ' + '"react-hot-loader/babel" to the "plugins" section of your .babelrc file. ' + 'If you prefer not to use Babel, replace "react-hot-loader/babel" with ' + '"react-hot-loader/webpack" in the "loaders" section of your Webpack configuration. ');
|
||
|
|
}
|
||
|
|
var t = args.types,
|
||
|
|
template = args.template;
|
||
|
|
var _options$safetyNet = options.safetyNet,
|
||
|
|
safetyNet = _options$safetyNet === undefined ? true : _options$safetyNet;
|
||
|
|
|
||
|
|
|
||
|
|
var buildRegistration = template('reactHotLoader.register(ID, NAME, FILENAME);', templateOptions);
|
||
|
|
|
||
|
|
var signatureHeader = template('var __signature__ = typeof reactHotLoaderGlobal !== \'undefined\' ? reactHotLoaderGlobal.default.signature : function (a) {return a;}', templateOptions);
|
||
|
|
|
||
|
|
var headerTemplate = template('(function () {\n var enterModule = (typeof reactHotLoaderGlobal !== \'undefined\' ? reactHotLoaderGlobal.enterModule : undefined);\n enterModule && enterModule(module);\n }())', templateOptions);
|
||
|
|
var footerTemplate = template('(function () {\n var leaveModule = (typeof reactHotLoaderGlobal !== \'undefined\' ? reactHotLoaderGlobal.leaveModule : undefined);\n leaveModule && leaveModule(module);\n }())', templateOptions);
|
||
|
|
var evalTemplate = template('this[key]=eval(code);', templateOptions);
|
||
|
|
|
||
|
|
// We're making the IIFE we insert at the end of the file an unused variable
|
||
|
|
// because it otherwise breaks the output of the babel-node REPL (#359).
|
||
|
|
|
||
|
|
var buildTagger = template(' \n(function () { \n \n var reactHotLoader = (typeof reactHotLoaderGlobal !== \'undefined\' ? reactHotLoaderGlobal.default : undefined);\n \n if (!reactHotLoader) {\n return;\n }\n\n REGISTRATIONS \n}());\n ', templateOptions);
|
||
|
|
|
||
|
|
// Gather top-level variables, functions, and classes.
|
||
|
|
// Try our best to avoid variables from require().
|
||
|
|
// Ideally we only want to find components defined by the user.
|
||
|
|
function shouldRegisterBinding(binding) {
|
||
|
|
var _binding$path = binding.path,
|
||
|
|
type = _binding$path.type,
|
||
|
|
node = _binding$path.node,
|
||
|
|
parent = _binding$path.parent;
|
||
|
|
|
||
|
|
switch (type) {
|
||
|
|
case 'FunctionDeclaration':
|
||
|
|
case 'ClassDeclaration':
|
||
|
|
case 'VariableDeclaration':
|
||
|
|
return true;
|
||
|
|
case 'VariableDeclarator':
|
||
|
|
{
|
||
|
|
var init = node.init;
|
||
|
|
|
||
|
|
if (t.isCallExpression(init) && init.callee.name === 'require') {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (parent.declare) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
default:
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
var REGISTRATIONS = Symbol('registrations');
|
||
|
|
return {
|
||
|
|
visitor: {
|
||
|
|
ExportDefaultDeclaration: function ExportDefaultDeclaration(path, state) {
|
||
|
|
var file = state.file;
|
||
|
|
// Default exports with names are going
|
||
|
|
// to be in scope anyway so no need to bother.
|
||
|
|
|
||
|
|
if (path.node.declaration.id) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Move export default right hand side to a variable
|
||
|
|
// so we can later refer to it and tag it with __source.
|
||
|
|
var id = path.scope.generateUidIdentifier('default');
|
||
|
|
var expression = t.isExpression(path.node.declaration) ? path.node.declaration : t.toExpression(path.node.declaration);
|
||
|
|
path.scope.registerDeclaration(path.insertBefore(t.variableDeclaration('const', [t.variableDeclarator(id, expression)]))[0]);
|
||
|
|
path.node.declaration = id; // eslint-disable-line no-param-reassign
|
||
|
|
|
||
|
|
// It won't appear in scope.bindings
|
||
|
|
// so we'll manually remember it exists.
|
||
|
|
state[REGISTRATIONS].push(buildRegistration({
|
||
|
|
ID: id,
|
||
|
|
NAME: t.stringLiteral('default'),
|
||
|
|
FILENAME: t.stringLiteral(file.opts.filename)
|
||
|
|
}));
|
||
|
|
},
|
||
|
|
|
||
|
|
|
||
|
|
Program: {
|
||
|
|
enter: function enter(_ref, state) {
|
||
|
|
var scope = _ref.scope,
|
||
|
|
node = _ref.node;
|
||
|
|
var file = state.file;
|
||
|
|
|
||
|
|
state[REGISTRATIONS] = []; // eslint-disable-line no-param-reassign
|
||
|
|
|
||
|
|
node.body.unshift(signatureHeader());
|
||
|
|
|
||
|
|
// Everything in the top level scope, when reasonable,
|
||
|
|
// is going to get tagged with __source.
|
||
|
|
/* eslint-disable guard-for-in,no-restricted-syntax */
|
||
|
|
for (var id in scope.bindings) {
|
||
|
|
var binding = scope.bindings[id];
|
||
|
|
if (shouldRegisterBinding(binding)) {
|
||
|
|
state[REGISTRATIONS].push(buildRegistration({
|
||
|
|
ID: binding.identifier,
|
||
|
|
NAME: t.stringLiteral(id),
|
||
|
|
FILENAME: t.stringLiteral(file.opts.filename)
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/* eslint-enable */
|
||
|
|
},
|
||
|
|
exit: function exit(_ref2, state) {
|
||
|
|
var node = _ref2.node;
|
||
|
|
var file = state.file;
|
||
|
|
|
||
|
|
var registrations = state[REGISTRATIONS];
|
||
|
|
state[REGISTRATIONS] = [];
|
||
|
|
|
||
|
|
// inject the code only if applicable
|
||
|
|
if (registrations && registrations.length && !shouldIgnoreFile(file.opts.filename)) {
|
||
|
|
if (safetyNet) {
|
||
|
|
node.body.unshift(headerTemplate());
|
||
|
|
}
|
||
|
|
// Inject the generated tagging code at the very end
|
||
|
|
// so that it is as minimally intrusive as possible.
|
||
|
|
node.body.push(t.emptyStatement());
|
||
|
|
node.body.push(buildTagger({ REGISTRATIONS: registrations }));
|
||
|
|
node.body.push(t.emptyStatement());
|
||
|
|
|
||
|
|
if (safetyNet) {
|
||
|
|
node.body.push(footerTemplate());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
Class: function Class(classPath) {
|
||
|
|
var classBody = classPath.get('body');
|
||
|
|
var hasRegenerateMethod = false;
|
||
|
|
var hasMethods = false;
|
||
|
|
|
||
|
|
classBody.get('body').forEach(function (path) {
|
||
|
|
var node = path.node;
|
||
|
|
|
||
|
|
// don't apply transform to static class properties
|
||
|
|
|
||
|
|
if (node.static) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (node.key.name !== REGENERATE_METHOD) {
|
||
|
|
hasMethods = true;
|
||
|
|
} else {
|
||
|
|
hasRegenerateMethod = true;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
if (hasMethods && !hasRegenerateMethod) {
|
||
|
|
var regenerateMethod = t.classMethod('method', t.identifier(REGENERATE_METHOD), [t.identifier('key'), t.identifier('code')], t.blockStatement([evalTemplate()]));
|
||
|
|
|
||
|
|
classBody.pushContainer('body', regenerateMethod);
|
||
|
|
|
||
|
|
classBody.get('body').forEach(function (path) {
|
||
|
|
var node = path.node;
|
||
|
|
|
||
|
|
|
||
|
|
if (node.key.name === REGENERATE_METHOD) {
|
||
|
|
path.addComment('leading', ' @ts-ignore', true);
|
||
|
|
path.get('body').get('body')[0].addComment('leading', ' @ts-ignore', true);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
var mergeRecord = function mergeRecord(sourceRecord, newRecord) {
|
||
|
|
Object.keys(newRecord).forEach(function (key) {
|
||
|
|
var action = newRecord[key];
|
||
|
|
if (typeof action === 'function') {
|
||
|
|
if (!sourceRecord[key]) {
|
||
|
|
sourceRecord[key] = function () {
|
||
|
|
return {};
|
||
|
|
};
|
||
|
|
}
|
||
|
|
var prev = sourceRecord[key];
|
||
|
|
sourceRecord[key] = function () {
|
||
|
|
prev.apply(undefined, arguments);
|
||
|
|
action.apply(undefined, arguments);
|
||
|
|
};
|
||
|
|
} else if ((typeof action === 'undefined' ? 'undefined' : _typeof(action)) === 'object') {
|
||
|
|
if (!sourceRecord[key]) {
|
||
|
|
sourceRecord[key] = {};
|
||
|
|
}
|
||
|
|
mergeRecord(sourceRecord[key], action);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
var composePlugins = function composePlugins(plugins) {
|
||
|
|
return function () {
|
||
|
|
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
|
||
|
|
args[_key] = arguments[_key];
|
||
|
|
}
|
||
|
|
|
||
|
|
var result = {};
|
||
|
|
plugins.forEach(function (creator) {
|
||
|
|
var plugin = creator.apply(undefined, args);
|
||
|
|
mergeRecord(result, plugin);
|
||
|
|
});
|
||
|
|
return result;
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
module.exports = composePlugins([plugin, function () {
|
||
|
|
var p = fresh.apply(undefined, arguments);
|
||
|
|
// removing everything we dont want right now
|
||
|
|
|
||
|
|
// registration
|
||
|
|
// delete p.visitor.Program;
|
||
|
|
// delete p.visitor.Program.exit;
|
||
|
|
|
||
|
|
// registrations
|
||
|
|
// delete p.visitor.FunctionDeclaration.enter;
|
||
|
|
// delete p.visitor.FunctionDeclaration.leave;
|
||
|
|
// delete p.visitor.VariableDeclaration;
|
||
|
|
|
||
|
|
return p;
|
||
|
|
}]);
|
||
|
|
|
||
|
|
module.exports.shouldIgnoreFile = shouldIgnoreFile;
|