react-refresh-babel.development.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763
  1. /** @license React vundefined
  2. * react-refresh-babel.development.js
  3. *
  4. * Copyright (c) Facebook, Inc. and its affiliates.
  5. *
  6. * This source code is licensed under the MIT license found in the
  7. * LICENSE file in the root directory of this source tree.
  8. */
  9. 'use strict';
  10. if (process.env.NODE_ENV !== "production") {
  11. (function() {
  12. 'use strict';
  13. function ReactFreshBabelPlugin (babel) {
  14. var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  15. if (typeof babel.getEnv === 'function') {
  16. // Only available in Babel 7.
  17. var env = babel.getEnv();
  18. if (env !== 'development' && !opts.skipEnvCheck) {
  19. throw new Error('React Refresh Babel transform should only be enabled in development environment. ' + 'Instead, the environment is: "' + env + '". If you want to override this check, pass {skipEnvCheck: true} as plugin options.');
  20. }
  21. }
  22. var t = babel.types;
  23. var refreshReg = t.identifier(opts.refreshReg || '$RefreshReg$');
  24. var refreshSig = t.identifier(opts.refreshSig || '$RefreshSig$');
  25. var registrationsByProgramPath = new Map();
  26. function createRegistration(programPath, persistentID) {
  27. var handle = programPath.scope.generateUidIdentifier('c');
  28. if (!registrationsByProgramPath.has(programPath)) {
  29. registrationsByProgramPath.set(programPath, []);
  30. }
  31. var registrations = registrationsByProgramPath.get(programPath);
  32. registrations.push({
  33. handle: handle,
  34. persistentID: persistentID
  35. });
  36. return handle;
  37. }
  38. function isComponentishName(name) {
  39. return typeof name === 'string' && name[0] >= 'A' && name[0] <= 'Z';
  40. }
  41. function findInnerComponents(inferredName, path, callback) {
  42. var node = path.node;
  43. switch (node.type) {
  44. case 'Identifier':
  45. {
  46. if (!isComponentishName(node.name)) {
  47. return false;
  48. } // export default hoc(Foo)
  49. // const X = hoc(Foo)
  50. callback(inferredName, node, null);
  51. return true;
  52. }
  53. case 'FunctionDeclaration':
  54. {
  55. // function Foo() {}
  56. // export function Foo() {}
  57. // export default function Foo() {}
  58. callback(inferredName, node.id, null);
  59. return true;
  60. }
  61. case 'ArrowFunctionExpression':
  62. {
  63. if (node.body.type === 'ArrowFunctionExpression') {
  64. return false;
  65. } // let Foo = () => {}
  66. // export default hoc1(hoc2(() => {}))
  67. callback(inferredName, node, path);
  68. return true;
  69. }
  70. case 'FunctionExpression':
  71. {
  72. // let Foo = function() {}
  73. // const Foo = hoc1(forwardRef(function renderFoo() {}))
  74. // export default memo(function() {})
  75. callback(inferredName, node, path);
  76. return true;
  77. }
  78. case 'CallExpression':
  79. {
  80. var argsPath = path.get('arguments');
  81. if (argsPath === undefined || argsPath.length === 0) {
  82. return false;
  83. }
  84. var calleePath = path.get('callee');
  85. switch (calleePath.node.type) {
  86. case 'MemberExpression':
  87. case 'Identifier':
  88. {
  89. var calleeSource = calleePath.getSource();
  90. var firstArgPath = argsPath[0];
  91. var innerName = inferredName + '$' + calleeSource;
  92. var foundInside = findInnerComponents(innerName, firstArgPath, callback);
  93. if (!foundInside) {
  94. return false;
  95. } // const Foo = hoc1(hoc2(() => {}))
  96. // export default memo(React.forwardRef(function() {}))
  97. callback(inferredName, node, path);
  98. return true;
  99. }
  100. default:
  101. {
  102. return false;
  103. }
  104. }
  105. }
  106. case 'VariableDeclarator':
  107. {
  108. var init = node.init;
  109. if (init === null) {
  110. return false;
  111. }
  112. var name = node.id.name;
  113. if (!isComponentishName(name)) {
  114. return false;
  115. }
  116. switch (init.type) {
  117. case 'ArrowFunctionExpression':
  118. case 'FunctionExpression':
  119. // Likely component definitions.
  120. break;
  121. case 'CallExpression':
  122. {
  123. // Maybe a HOC.
  124. // Try to determine if this is some form of import.
  125. var callee = init.callee;
  126. var calleeType = callee.type;
  127. if (calleeType === 'Import') {
  128. return false;
  129. } else if (calleeType === 'Identifier') {
  130. if (callee.name.indexOf('require') === 0) {
  131. return false;
  132. } else if (callee.name.indexOf('import') === 0) {
  133. return false;
  134. } // Neither require nor import. Might be a HOC.
  135. // Pass through.
  136. } else if (calleeType === 'MemberExpression') ; else {
  137. // More complicated call.
  138. return false;
  139. }
  140. break;
  141. }
  142. case 'TaggedTemplateExpression':
  143. // Maybe something like styled.div`...`
  144. break;
  145. default:
  146. return false;
  147. }
  148. var initPath = path.get('init');
  149. var _foundInside = findInnerComponents(inferredName, initPath, callback);
  150. if (_foundInside) {
  151. return true;
  152. } // See if this identifier is used in JSX. Then it's a component.
  153. var binding = path.scope.getBinding(name);
  154. if (binding === undefined) {
  155. return;
  156. }
  157. var isLikelyUsedAsType = false;
  158. var referencePaths = binding.referencePaths;
  159. for (var i = 0; i < referencePaths.length; i++) {
  160. var ref = referencePaths[i];
  161. if (ref.node && ref.node.type !== 'JSXIdentifier' && ref.node.type !== 'Identifier') {
  162. continue;
  163. }
  164. var refParent = ref.parent;
  165. if (refParent.type === 'JSXOpeningElement') {
  166. isLikelyUsedAsType = true;
  167. } else if (refParent.type === 'CallExpression') {
  168. var _callee = refParent.callee;
  169. var fnName = void 0;
  170. switch (_callee.type) {
  171. case 'Identifier':
  172. fnName = _callee.name;
  173. break;
  174. case 'MemberExpression':
  175. fnName = _callee.property.name;
  176. break;
  177. }
  178. switch (fnName) {
  179. case 'createElement':
  180. case 'jsx':
  181. case 'jsxDEV':
  182. case 'jsxs':
  183. isLikelyUsedAsType = true;
  184. break;
  185. }
  186. }
  187. if (isLikelyUsedAsType) {
  188. // const X = ... + later <X />
  189. callback(inferredName, init, initPath);
  190. return true;
  191. }
  192. }
  193. }
  194. }
  195. return false;
  196. }
  197. function isBuiltinHook(hookName) {
  198. switch (hookName) {
  199. case 'useState':
  200. case 'React.useState':
  201. case 'useReducer':
  202. case 'React.useReducer':
  203. case 'useEffect':
  204. case 'React.useEffect':
  205. case 'useLayoutEffect':
  206. case 'React.useLayoutEffect':
  207. case 'useMemo':
  208. case 'React.useMemo':
  209. case 'useCallback':
  210. case 'React.useCallback':
  211. case 'useRef':
  212. case 'React.useRef':
  213. case 'useContext':
  214. case 'React.useContext':
  215. case 'useImperativeMethods':
  216. case 'React.useImperativeMethods':
  217. case 'useDebugValue':
  218. case 'React.useDebugValue':
  219. return true;
  220. default:
  221. return false;
  222. }
  223. }
  224. function getHookCallsSignature(functionNode) {
  225. var fnHookCalls = hookCalls.get(functionNode);
  226. if (fnHookCalls === undefined) {
  227. return null;
  228. }
  229. return {
  230. key: fnHookCalls.map(function (call) {
  231. return call.name + '{' + call.key + '}';
  232. }).join('\n'),
  233. customHooks: fnHookCalls.filter(function (call) {
  234. return !isBuiltinHook(call.name);
  235. }).map(function (call) {
  236. return t.cloneDeep(call.callee);
  237. })
  238. };
  239. }
  240. var hasForceResetCommentByFile = new WeakMap(); // We let user do /* @refresh reset */ to reset state in the whole file.
  241. function hasForceResetComment(path) {
  242. var file = path.hub.file;
  243. var hasForceReset = hasForceResetCommentByFile.get(file);
  244. if (hasForceReset !== undefined) {
  245. return hasForceReset;
  246. }
  247. hasForceReset = false;
  248. var comments = file.ast.comments;
  249. for (var i = 0; i < comments.length; i++) {
  250. var cmt = comments[i];
  251. if (cmt.value.indexOf('@refresh reset') !== -1) {
  252. hasForceReset = true;
  253. break;
  254. }
  255. }
  256. hasForceResetCommentByFile.set(file, hasForceReset);
  257. return hasForceReset;
  258. }
  259. function createArgumentsForSignature(node, signature, scope) {
  260. var key = signature.key,
  261. customHooks = signature.customHooks;
  262. var forceReset = hasForceResetComment(scope.path);
  263. var customHooksInScope = [];
  264. customHooks.forEach(function (callee) {
  265. // Check if a corresponding binding exists where we emit the signature.
  266. var bindingName;
  267. switch (callee.type) {
  268. case 'MemberExpression':
  269. if (callee.object.type === 'Identifier') {
  270. bindingName = callee.object.name;
  271. }
  272. break;
  273. case 'Identifier':
  274. bindingName = callee.name;
  275. break;
  276. }
  277. if (scope.hasBinding(bindingName)) {
  278. customHooksInScope.push(callee);
  279. } else {
  280. // We don't have anything to put in the array because Hook is out of scope.
  281. // Since it could potentially have been edited, remount the component.
  282. forceReset = true;
  283. }
  284. });
  285. var finalKey = key;
  286. if (typeof require === 'function' && !opts.emitFullSignatures) {
  287. // Prefer to hash when we can (e.g. outside of ASTExplorer).
  288. // This makes it deterministically compact, even if there's
  289. // e.g. a useState ininitalizer with some code inside.
  290. // We also need it for www that has transforms like cx()
  291. // that don't understand if something is part of a string.
  292. finalKey = require('crypto').createHash('sha1').update(key).digest('base64');
  293. }
  294. var args = [node, t.stringLiteral(finalKey)];
  295. if (forceReset || customHooksInScope.length > 0) {
  296. args.push(t.booleanLiteral(forceReset));
  297. }
  298. if (customHooksInScope.length > 0) {
  299. args.push( // TODO: We could use an arrow here to be more compact.
  300. // However, don't do it until AMA can run them natively.
  301. t.functionExpression(null, [], t.blockStatement([t.returnStatement(t.arrayExpression(customHooksInScope))])));
  302. }
  303. return args;
  304. }
  305. var seenForRegistration = new WeakSet();
  306. var seenForSignature = new WeakSet();
  307. var seenForOutro = new WeakSet();
  308. var hookCalls = new WeakMap();
  309. var HookCallsVisitor = {
  310. CallExpression: function (path) {
  311. var node = path.node;
  312. var callee = node.callee; // Note: this visitor MUST NOT mutate the tree in any way.
  313. // It runs early in a separate traversal and should be very fast.
  314. var name = null;
  315. switch (callee.type) {
  316. case 'Identifier':
  317. name = callee.name;
  318. break;
  319. case 'MemberExpression':
  320. name = callee.property.name;
  321. break;
  322. }
  323. if (name === null || !/^use[A-Z]/.test(name)) {
  324. return;
  325. }
  326. var fnScope = path.scope.getFunctionParent();
  327. if (fnScope === null) {
  328. return;
  329. } // This is a Hook call. Record it.
  330. var fnNode = fnScope.block;
  331. if (!hookCalls.has(fnNode)) {
  332. hookCalls.set(fnNode, []);
  333. }
  334. var hookCallsForFn = hookCalls.get(fnNode);
  335. var key = '';
  336. if (path.parent.type === 'VariableDeclarator') {
  337. // TODO: if there is no LHS, consider some other heuristic.
  338. key = path.parentPath.get('id').getSource();
  339. } // Some built-in Hooks reset on edits to arguments.
  340. var args = path.get('arguments');
  341. if (name === 'useState' && args.length > 0) {
  342. // useState second argument is initial state.
  343. key += '(' + args[0].getSource() + ')';
  344. } else if (name === 'useReducer' && args.length > 1) {
  345. // useReducer second argument is initial state.
  346. key += '(' + args[1].getSource() + ')';
  347. }
  348. hookCallsForFn.push({
  349. callee: path.node.callee,
  350. name: name,
  351. key: key
  352. });
  353. }
  354. };
  355. return {
  356. visitor: {
  357. ExportDefaultDeclaration: function (path) {
  358. var node = path.node;
  359. var decl = node.declaration;
  360. var declPath = path.get('declaration');
  361. if (decl.type !== 'CallExpression') {
  362. // For now, we only support possible HOC calls here.
  363. // Named function declarations are handled in FunctionDeclaration.
  364. // Anonymous direct exports like export default function() {}
  365. // are currently ignored.
  366. return;
  367. } // Make sure we're not mutating the same tree twice.
  368. // This can happen if another Babel plugin replaces parents.
  369. if (seenForRegistration.has(node)) {
  370. return;
  371. }
  372. seenForRegistration.add(node); // Don't mutate the tree above this point.
  373. // This code path handles nested cases like:
  374. // export default memo(() => {})
  375. // In those cases it is more plausible people will omit names
  376. // so they're worth handling despite possible false positives.
  377. // More importantly, it handles the named case:
  378. // export default memo(function Named() {})
  379. var inferredName = '%default%';
  380. var programPath = path.parentPath;
  381. findInnerComponents(inferredName, declPath, function (persistentID, targetExpr, targetPath) {
  382. if (targetPath === null) {
  383. // For case like:
  384. // export default hoc(Foo)
  385. // we don't want to wrap Foo inside the call.
  386. // Instead we assume it's registered at definition.
  387. return;
  388. }
  389. var handle = createRegistration(programPath, persistentID);
  390. targetPath.replaceWith(t.assignmentExpression('=', handle, targetExpr));
  391. });
  392. },
  393. FunctionDeclaration: {
  394. enter: function (path) {
  395. var node = path.node;
  396. var programPath;
  397. var insertAfterPath;
  398. switch (path.parent.type) {
  399. case 'Program':
  400. insertAfterPath = path;
  401. programPath = path.parentPath;
  402. break;
  403. case 'ExportNamedDeclaration':
  404. insertAfterPath = path.parentPath;
  405. programPath = insertAfterPath.parentPath;
  406. break;
  407. case 'ExportDefaultDeclaration':
  408. insertAfterPath = path.parentPath;
  409. programPath = insertAfterPath.parentPath;
  410. break;
  411. default:
  412. return;
  413. }
  414. var id = node.id;
  415. if (id === null) {
  416. // We don't currently handle anonymous default exports.
  417. return;
  418. }
  419. var inferredName = id.name;
  420. if (!isComponentishName(inferredName)) {
  421. return;
  422. } // Make sure we're not mutating the same tree twice.
  423. // This can happen if another Babel plugin replaces parents.
  424. if (seenForRegistration.has(node)) {
  425. return;
  426. }
  427. seenForRegistration.add(node); // Don't mutate the tree above this point.
  428. // export function Named() {}
  429. // function Named() {}
  430. findInnerComponents(inferredName, path, function (persistentID, targetExpr) {
  431. var handle = createRegistration(programPath, persistentID);
  432. insertAfterPath.insertAfter(t.expressionStatement(t.assignmentExpression('=', handle, targetExpr)));
  433. });
  434. },
  435. exit: function (path) {
  436. var node = path.node;
  437. var id = node.id;
  438. if (id === null) {
  439. return;
  440. }
  441. var signature = getHookCallsSignature(node);
  442. if (signature === null) {
  443. return;
  444. } // Make sure we're not mutating the same tree twice.
  445. // This can happen if another Babel plugin replaces parents.
  446. if (seenForSignature.has(node)) {
  447. return;
  448. }
  449. seenForSignature.add(node); // Don't mutate the tree above this point.
  450. var sigCallID = path.scope.generateUidIdentifier('_s');
  451. path.scope.parent.push({
  452. id: sigCallID,
  453. init: t.callExpression(refreshSig, [])
  454. }); // The signature call is split in two parts. One part is called inside the function.
  455. // This is used to signal when first render happens.
  456. path.get('body').unshiftContainer('body', t.expressionStatement(t.callExpression(sigCallID, []))); // The second call is around the function itself.
  457. // This is used to associate a type with a signature.
  458. // Unlike with $RefreshReg$, this needs to work for nested
  459. // declarations too. So we need to search for a path where
  460. // we can insert a statement rather than hardcoding it.
  461. var insertAfterPath = null;
  462. path.find(function (p) {
  463. if (p.parentPath.isBlock()) {
  464. insertAfterPath = p;
  465. return true;
  466. }
  467. });
  468. if (insertAfterPath === null) {
  469. return;
  470. }
  471. insertAfterPath.insertAfter(t.expressionStatement(t.callExpression(sigCallID, createArgumentsForSignature(id, signature, insertAfterPath.scope))));
  472. }
  473. },
  474. 'ArrowFunctionExpression|FunctionExpression': {
  475. exit: function (path) {
  476. var node = path.node;
  477. var signature = getHookCallsSignature(node);
  478. if (signature === null) {
  479. return;
  480. } // Make sure we're not mutating the same tree twice.
  481. // This can happen if another Babel plugin replaces parents.
  482. if (seenForSignature.has(node)) {
  483. return;
  484. }
  485. seenForSignature.add(node); // Don't mutate the tree above this point.
  486. var sigCallID = path.scope.generateUidIdentifier('_s');
  487. path.scope.parent.push({
  488. id: sigCallID,
  489. init: t.callExpression(refreshSig, [])
  490. }); // The signature call is split in two parts. One part is called inside the function.
  491. // This is used to signal when first render happens.
  492. if (path.node.body.type !== 'BlockStatement') {
  493. path.node.body = t.blockStatement([t.returnStatement(path.node.body)]);
  494. }
  495. path.get('body').unshiftContainer('body', t.expressionStatement(t.callExpression(sigCallID, []))); // The second call is around the function itself.
  496. // This is used to associate a type with a signature.
  497. if (path.parent.type === 'VariableDeclarator') {
  498. var insertAfterPath = null;
  499. path.find(function (p) {
  500. if (p.parentPath.isBlock()) {
  501. insertAfterPath = p;
  502. return true;
  503. }
  504. });
  505. if (insertAfterPath === null) {
  506. return;
  507. } // Special case when a function would get an inferred name:
  508. // let Foo = () => {}
  509. // let Foo = function() {}
  510. // We'll add signature it on next line so that
  511. // we don't mess up the inferred 'Foo' function name.
  512. insertAfterPath.insertAfter(t.expressionStatement(t.callExpression(sigCallID, createArgumentsForSignature(path.parent.id, signature, insertAfterPath.scope)))); // Result: let Foo = () => {}; __signature(Foo, ...);
  513. } else {
  514. // let Foo = hoc(() => {})
  515. path.replaceWith(t.callExpression(sigCallID, createArgumentsForSignature(node, signature, path.scope))); // Result: let Foo = hoc(__signature(() => {}, ...))
  516. }
  517. }
  518. },
  519. VariableDeclaration: function (path) {
  520. var node = path.node;
  521. var programPath;
  522. var insertAfterPath;
  523. switch (path.parent.type) {
  524. case 'Program':
  525. insertAfterPath = path;
  526. programPath = path.parentPath;
  527. break;
  528. case 'ExportNamedDeclaration':
  529. insertAfterPath = path.parentPath;
  530. programPath = insertAfterPath.parentPath;
  531. break;
  532. case 'ExportDefaultDeclaration':
  533. insertAfterPath = path.parentPath;
  534. programPath = insertAfterPath.parentPath;
  535. break;
  536. default:
  537. return;
  538. } // Make sure we're not mutating the same tree twice.
  539. // This can happen if another Babel plugin replaces parents.
  540. if (seenForRegistration.has(node)) {
  541. return;
  542. }
  543. seenForRegistration.add(node); // Don't mutate the tree above this point.
  544. var declPaths = path.get('declarations');
  545. if (declPaths.length !== 1) {
  546. return;
  547. }
  548. var declPath = declPaths[0];
  549. var inferredName = declPath.node.id.name;
  550. findInnerComponents(inferredName, declPath, function (persistentID, targetExpr, targetPath) {
  551. if (targetPath === null) {
  552. // For case like:
  553. // export const Something = hoc(Foo)
  554. // we don't want to wrap Foo inside the call.
  555. // Instead we assume it's registered at definition.
  556. return;
  557. }
  558. var handle = createRegistration(programPath, persistentID);
  559. if (targetPath.parent.type === 'VariableDeclarator') {
  560. // Special case when a variable would get an inferred name:
  561. // let Foo = () => {}
  562. // let Foo = function() {}
  563. // let Foo = styled.div``;
  564. // We'll register it on next line so that
  565. // we don't mess up the inferred 'Foo' function name.
  566. // (eg: with @babel/plugin-transform-react-display-name or
  567. // babel-plugin-styled-components)
  568. insertAfterPath.insertAfter(t.expressionStatement(t.assignmentExpression('=', handle, declPath.node.id))); // Result: let Foo = () => {}; _c1 = Foo;
  569. } else {
  570. // let Foo = hoc(() => {})
  571. targetPath.replaceWith(t.assignmentExpression('=', handle, targetExpr)); // Result: let Foo = hoc(_c1 = () => {})
  572. }
  573. });
  574. },
  575. Program: {
  576. enter: function (path) {
  577. // This is a separate early visitor because we need to collect Hook calls
  578. // and "const [foo, setFoo] = ..." signatures before the destructuring
  579. // transform mangles them. This extra traversal is not ideal for perf,
  580. // but it's the best we can do until we stop transpiling destructuring.
  581. path.traverse(HookCallsVisitor);
  582. },
  583. exit: function (path) {
  584. var registrations = registrationsByProgramPath.get(path);
  585. if (registrations === undefined) {
  586. return;
  587. } // Make sure we're not mutating the same tree twice.
  588. // This can happen if another Babel plugin replaces parents.
  589. var node = path.node;
  590. if (seenForOutro.has(node)) {
  591. return;
  592. }
  593. seenForOutro.add(node); // Don't mutate the tree above this point.
  594. registrationsByProgramPath.delete(path);
  595. var declarators = [];
  596. path.pushContainer('body', t.variableDeclaration('var', declarators));
  597. registrations.forEach(function (_ref) {
  598. var handle = _ref.handle,
  599. persistentID = _ref.persistentID;
  600. path.pushContainer('body', t.expressionStatement(t.callExpression(refreshReg, [handle, t.stringLiteral(persistentID)])));
  601. declarators.push(t.variableDeclarator(handle));
  602. });
  603. }
  604. }
  605. }
  606. };
  607. }
  608. module.exports = ReactFreshBabelPlugin;
  609. })();
  610. }