react-hot-loader
Version:
Tweak React components in real time.
186 lines (151 loc) • 7.35 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
// the same as before
var PREFIX = '__reactstandin__';
var REGENERATE_METHOD = PREFIX + 'regenerateByEval';
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-hot-loader)([\/]|$)/);
};
/* eslint-enable */
module.exports = 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 headerTemplate = template('(function () {\n var enterModule = (typeof reactHotLoaderGlobal !== \'undefined\' ? reactHotLoaderGlobal : require(\'react-hot-loader\')).enterModule;\n enterModule && enterModule(module);\n }())', templateOptions);
var footerTemplate = template('(function () {\n var leaveModule = (typeof reactHotLoaderGlobal !== \'undefined\' ? reactHotLoaderGlobal : require(\'react-hot-loader\')).leaveModule;\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 : require(\'react-hot-loader\')).default;\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;
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;
}
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.insertBefore(t.variableDeclaration('const', [t.variableDeclarator(id, expression)]));
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;
var file = state.file;
state[REGISTRATIONS] = []; // eslint-disable-line no-param-reassign
// 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);
}
});
}
}
}
};
};
module.exports.shouldIgnoreFile = shouldIgnoreFile;