UNPKG

8.02 kBJavaScriptView Raw
1'use strict';
2
3var _babelTemplate = require('babel-template');
4
5var _babelTemplate2 = _interopRequireDefault(_babelTemplate);
6
7function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
8
9var buildRegistration = (0, _babelTemplate2.default)('__REACT_HOT_LOADER__.register(ID, NAME, FILENAME);');
10var buildSemi = (0, _babelTemplate2.default)(';');
11
12// We're making the IIFE we insert at the end of the file an unused variable
13// because it otherwise breaks the output of the babel-node REPL (#359).
14var buildTagger = (0, _babelTemplate2.default)('\nvar UNUSED = (function () {\n if (typeof __REACT_HOT_LOADER__ === \'undefined\') {\n return;\n }\n\n REGISTRATIONS\n})();\n');
15
16var buildNewClassProperty = function buildNewClassProperty(t, classPropertyName, newMethodName, isAsync) {
17 var returnExpression = t.callExpression(t.memberExpression(t.thisExpression(), newMethodName), [t.spreadElement(t.identifier('params'))]);
18
19 if (isAsync) {
20 returnExpression = t.awaitExpression(returnExpression);
21 }
22
23 var newArrowFunction = t.arrowFunctionExpression([t.restElement(t.identifier('params'))], returnExpression, isAsync);
24 return t.classProperty(classPropertyName, newArrowFunction);
25};
26
27var classPropertyOptOutVistor = {
28 MetaProperty: function MetaProperty(path, state) {
29 var node = path.node;
30
31
32 if (node.meta.name === 'new' && node.property.name === 'target') {
33 state.optOut = true; // eslint-disable-line no-param-reassign
34 }
35 },
36 ReferencedIdentifier: function ReferencedIdentifier(path, state) {
37 var node = path.node;
38
39
40 if (node.name === 'arguments') {
41 state.optOut = true; // eslint-disable-line no-param-reassign
42 }
43 }
44};
45
46module.exports = function plugin(args) {
47 // This is a Babel plugin, but the user put it in the Webpack config.
48 if (this && this.callback) {
49 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. ');
50 }
51 var t = args.types;
52
53 // No-op in production.
54
55 if (process.env.NODE_ENV === 'production') {
56 return { visitor: {} };
57 }
58
59 // Gather top-level variables, functions, and classes.
60 // Try our best to avoid variables from require().
61 // Ideally we only want to find components defined by the user.
62 function shouldRegisterBinding(binding) {
63 var _binding$path = binding.path,
64 type = _binding$path.type,
65 node = _binding$path.node;
66
67 switch (type) {
68 case 'FunctionDeclaration':
69 case 'ClassDeclaration':
70 case 'VariableDeclaration':
71 return true;
72 case 'VariableDeclarator':
73 {
74 var init = node.init;
75
76 if (t.isCallExpression(init) && init.callee.name === 'require') {
77 return false;
78 }
79 return true;
80 }
81 default:
82 return false;
83 }
84 }
85
86 var REGISTRATIONS = Symbol();
87 return {
88 visitor: {
89 ExportDefaultDeclaration: function ExportDefaultDeclaration(path, _ref) {
90 var file = _ref.file;
91
92 // Default exports with names are going
93 // to be in scope anyway so no need to bother.
94 if (path.node.declaration.id) {
95 return;
96 }
97
98 // Move export default right hand side to a variable
99 // so we can later refer to it and tag it with __source.
100 var id = path.scope.generateUidIdentifier('default');
101 var expression = t.isExpression(path.node.declaration) ? path.node.declaration : t.toExpression(path.node.declaration);
102 path.insertBefore(t.variableDeclaration('const', [t.variableDeclarator(id, expression)]));
103 path.node.declaration = id; // eslint-disable-line no-param-reassign
104
105 // It won't appear in scope.bindings
106 // so we'll manually remember it exists.
107 path.parent[REGISTRATIONS].push(buildRegistration({
108 ID: id,
109 NAME: t.stringLiteral('default'),
110 FILENAME: t.stringLiteral(file.opts.filename)
111 }));
112 },
113
114
115 Program: {
116 enter: function enter(_ref2, _ref3) {
117 var node = _ref2.node,
118 scope = _ref2.scope;
119 var file = _ref3.file;
120
121 node[REGISTRATIONS] = []; // eslint-disable-line no-param-reassign
122
123 // Everything in the top level scope, when reasonable,
124 // is going to get tagged with __source.
125 /* eslint-disable guard-for-in,no-restricted-syntax */
126 for (var id in scope.bindings) {
127 var binding = scope.bindings[id];
128 if (shouldRegisterBinding(binding)) {
129 node[REGISTRATIONS].push(buildRegistration({
130 ID: binding.identifier,
131 NAME: t.stringLiteral(id),
132 FILENAME: t.stringLiteral(file.opts.filename)
133 }));
134 }
135 }
136 /* eslint-enable */
137 },
138 exit: function exit(_ref4) {
139 var node = _ref4.node,
140 scope = _ref4.scope;
141
142 var registrations = node[REGISTRATIONS];
143 node[REGISTRATIONS] = null; // eslint-disable-line no-param-reassign
144
145 // Inject the generated tagging code at the very end
146 // so that it is as minimally intrusive as possible.
147 node.body.push(buildSemi());
148 node.body.push(buildTagger({
149 UNUSED: scope.generateUidIdentifier(),
150 REGISTRATIONS: registrations
151 }));
152 node.body.push(buildSemi());
153 }
154 },
155
156 Class: function Class(classPath) {
157 var classBody = classPath.get('body');
158
159 classBody.get('body').forEach(function (path) {
160 if (path.isClassProperty()) {
161 var node = path.node;
162
163 // don't apply transform to static class properties
164
165 if (node.static) {
166 return;
167 }
168
169 var state = {
170 optOut: false
171 };
172
173 path.traverse(classPropertyOptOutVistor, state);
174
175 if (state.optOut) {
176 return;
177 }
178
179 // class property node value is nullable
180 if (node.value && node.value.type === 'ArrowFunctionExpression') {
181 var isAsync = node.value.async;
182
183 // TODO:
184 // Remove this check when babel issue is resolved: https://github.com/babel/babel/issues/5078
185 // RHL Issue: https://github.com/gaearon/react-hot-loader/issues/391
186 // This code makes async arrow functions not reloadable,
187 // but doesn't break code any more when using 'this' inside AAF
188 if (isAsync) {
189 return;
190 }
191
192 var params = node.value.params;
193 var newIdentifier = t.identifier('__' + node.key.name + '__REACT_HOT_LOADER__');
194
195 // arrow function body can either be a block statement or a returned expression
196 var newMethodBody = node.value.body.type === 'BlockStatement' ? node.value.body : t.blockStatement([t.returnStatement(node.value.body)]);
197
198 // create a new method on the class that the original class property function
199 // calls, since the method is able to be replaced by RHL
200 var newMethod = t.classMethod('method', newIdentifier, params, newMethodBody);
201 newMethod.async = isAsync;
202 path.insertAfter(newMethod);
203
204 // replace the original class property function with a function that calls
205 // the new class method created above
206 path.replaceWith(buildNewClassProperty(t, node.key, newIdentifier, isAsync));
207 }
208 }
209 });
210 }
211 }
212 };
213};
\No newline at end of file