RefreshUtils.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. /* global __webpack_require__ */
  2. var Refresh = require('react-refresh/runtime');
  3. /**
  4. * Extracts exports from a webpack module object.
  5. * @param {string} moduleId A Webpack module ID.
  6. * @returns {*} An exports object from the module.
  7. */
  8. function getModuleExports(moduleId) {
  9. if (typeof moduleId === 'undefined') {
  10. // `moduleId` is unavailable, which indicates that this module is not in the cache,
  11. // which means we won't be able to capture any exports,
  12. // and thus they cannot be refreshed safely.
  13. // These are likely runtime or dynamically generated modules.
  14. return {};
  15. }
  16. var maybeModule = __webpack_require__.c[moduleId];
  17. if (typeof maybeModule === 'undefined') {
  18. // `moduleId` is available but the module in cache is unavailable,
  19. // which indicates the module is somehow corrupted (e.g. broken Webpacak `module` globals).
  20. // We will warn the user (as this is likely a mistake) and assume they cannot be refreshed.
  21. console.warn('[React Refresh] Failed to get exports for module: ' + moduleId + '.');
  22. return {};
  23. }
  24. var exportsOrPromise = maybeModule.exports;
  25. if (typeof Promise !== 'undefined' && exportsOrPromise instanceof Promise) {
  26. return exportsOrPromise.then(function (exports) {
  27. return exports;
  28. });
  29. }
  30. return exportsOrPromise;
  31. }
  32. /**
  33. * Calculates the signature of a React refresh boundary.
  34. * If this signature changes, it's unsafe to accept the boundary.
  35. *
  36. * This implementation is based on the one in [Metro](https://github.com/facebook/metro/blob/907d6af22ac6ebe58572be418e9253a90665ecbd/packages/metro/src/lib/polyfills/require.js#L795-L816).
  37. * @param {*} moduleExports A Webpack module exports object.
  38. * @returns {string[]} A React refresh boundary signature array.
  39. */
  40. function getReactRefreshBoundarySignature(moduleExports) {
  41. var signature = [];
  42. signature.push(Refresh.getFamilyByType(moduleExports));
  43. if (moduleExports == null || typeof moduleExports !== 'object') {
  44. // Exit if we can't iterate over exports.
  45. return signature;
  46. }
  47. for (var key in moduleExports) {
  48. if (key === '__esModule') {
  49. continue;
  50. }
  51. signature.push(key);
  52. signature.push(Refresh.getFamilyByType(moduleExports[key]));
  53. }
  54. return signature;
  55. }
  56. /**
  57. * Creates a data object to be retained across refreshes.
  58. * This object should not transtively reference previous exports,
  59. * which can form infinite chain of objects across refreshes, which can pressure RAM.
  60. *
  61. * @param {*} moduleExports A Webpack module exports object.
  62. * @returns {*} A React refresh boundary signature array.
  63. */
  64. function getWebpackHotData(moduleExports) {
  65. return {
  66. signature: getReactRefreshBoundarySignature(moduleExports),
  67. isReactRefreshBoundary: isReactRefreshBoundary(moduleExports),
  68. };
  69. }
  70. /**
  71. * Creates a helper that performs a delayed React refresh.
  72. * @returns {function(function(): void): void} A debounced React refresh function.
  73. */
  74. function createDebounceUpdate() {
  75. /**
  76. * A cached setTimeout handler.
  77. * @type {number | undefined}
  78. */
  79. var refreshTimeout;
  80. /**
  81. * Performs react refresh on a delay and clears the error overlay.
  82. * @param {function(): void} callback
  83. * @returns {void}
  84. */
  85. function enqueueUpdate(callback) {
  86. if (typeof refreshTimeout === 'undefined') {
  87. refreshTimeout = setTimeout(function () {
  88. refreshTimeout = undefined;
  89. Refresh.performReactRefresh();
  90. callback();
  91. }, 30);
  92. }
  93. }
  94. return enqueueUpdate;
  95. }
  96. /**
  97. * Checks if all exports are likely a React component.
  98. *
  99. * This implementation is based on the one in [Metro](https://github.com/facebook/metro/blob/febdba2383113c88296c61e28e4ef6a7f4939fda/packages/metro/src/lib/polyfills/require.js#L748-L774).
  100. * @param {*} moduleExports A Webpack module exports object.
  101. * @returns {boolean} Whether the exports are React component like.
  102. */
  103. function isReactRefreshBoundary(moduleExports) {
  104. if (Refresh.isLikelyComponentType(moduleExports)) {
  105. return true;
  106. }
  107. if (moduleExports === undefined || moduleExports === null || typeof moduleExports !== 'object') {
  108. // Exit if we can't iterate over exports.
  109. return false;
  110. }
  111. var hasExports = false;
  112. var areAllExportsComponents = true;
  113. for (var key in moduleExports) {
  114. hasExports = true;
  115. // This is the ES Module indicator flag
  116. if (key === '__esModule') {
  117. continue;
  118. }
  119. // We can (and have to) safely execute getters here,
  120. // as Webpack manually assigns harmony exports to getters,
  121. // without any side-effects attached.
  122. // Ref: https://github.com/webpack/webpack/blob/b93048643fe74de2a6931755911da1212df55897/lib/MainTemplate.js#L281
  123. var exportValue = moduleExports[key];
  124. if (!Refresh.isLikelyComponentType(exportValue)) {
  125. areAllExportsComponents = false;
  126. }
  127. }
  128. return hasExports && areAllExportsComponents;
  129. }
  130. /**
  131. * Checks if exports are likely a React component and registers them.
  132. *
  133. * This implementation is based on the one in [Metro](https://github.com/facebook/metro/blob/febdba2383113c88296c61e28e4ef6a7f4939fda/packages/metro/src/lib/polyfills/require.js#L818-L835).
  134. * @param {*} moduleExports A Webpack module exports object.
  135. * @param {string} moduleId A Webpack module ID.
  136. * @returns {void}
  137. */
  138. function registerExportsForReactRefresh(moduleExports, moduleId) {
  139. if (Refresh.isLikelyComponentType(moduleExports)) {
  140. // Register module.exports if it is likely a component
  141. Refresh.register(moduleExports, moduleId + ' %exports%');
  142. }
  143. if (moduleExports === undefined || moduleExports === null || typeof moduleExports !== 'object') {
  144. // Exit if we can't iterate over the exports.
  145. return;
  146. }
  147. for (var key in moduleExports) {
  148. // Skip registering the ES Module indicator
  149. if (key === '__esModule') {
  150. continue;
  151. }
  152. var exportValue = moduleExports[key];
  153. if (Refresh.isLikelyComponentType(exportValue)) {
  154. var typeID = moduleId + ' %exports% ' + key;
  155. Refresh.register(exportValue, typeID);
  156. }
  157. }
  158. }
  159. /**
  160. * Compares previous and next module objects to check for mutated boundaries.
  161. *
  162. * This implementation is based on the one in [Metro](https://github.com/facebook/metro/blob/907d6af22ac6ebe58572be418e9253a90665ecbd/packages/metro/src/lib/polyfills/require.js#L776-L792).
  163. * @param {*} prevSignature The signature of the current Webpack module exports object.
  164. * @param {*} nextSignature The signature of the next Webpack module exports object.
  165. * @returns {boolean} Whether the React refresh boundary should be invalidated.
  166. */
  167. function shouldInvalidateReactRefreshBoundary(prevSignature, nextSignature) {
  168. if (prevSignature.length !== nextSignature.length) {
  169. return true;
  170. }
  171. for (var i = 0; i < nextSignature.length; i += 1) {
  172. if (prevSignature[i] !== nextSignature[i]) {
  173. return true;
  174. }
  175. }
  176. return false;
  177. }
  178. var enqueueUpdate = createDebounceUpdate();
  179. function executeRuntime(moduleExports, moduleId, webpackHot, refreshOverlay, isTest) {
  180. registerExportsForReactRefresh(moduleExports, moduleId);
  181. if (webpackHot) {
  182. var isHotUpdate = !!webpackHot.data;
  183. var prevData;
  184. if (isHotUpdate) {
  185. prevData = webpackHot.data.prevData;
  186. }
  187. if (isReactRefreshBoundary(moduleExports)) {
  188. webpackHot.dispose(
  189. /**
  190. * A callback to performs a full refresh if React has unrecoverable errors,
  191. * and also caches the to-be-disposed module.
  192. * @param {*} data A hot module data object from Webpack HMR.
  193. * @returns {void}
  194. */
  195. function hotDisposeCallback(data) {
  196. // We have to mutate the data object to get data registered and cached
  197. data.prevData = getWebpackHotData(moduleExports);
  198. }
  199. );
  200. webpackHot.accept(
  201. /**
  202. * An error handler to allow self-recovering behaviours.
  203. * @param {Error} error An error occurred during evaluation of a module.
  204. * @returns {void}
  205. */
  206. function hotErrorHandler(error) {
  207. if (typeof refreshOverlay !== 'undefined' && refreshOverlay) {
  208. refreshOverlay.handleRuntimeError(error);
  209. }
  210. if (typeof isTest !== 'undefined' && isTest) {
  211. if (window.onHotAcceptError) {
  212. window.onHotAcceptError(error.message);
  213. }
  214. }
  215. __webpack_require__.c[moduleId].hot.accept(hotErrorHandler);
  216. }
  217. );
  218. if (isHotUpdate) {
  219. if (
  220. prevData &&
  221. prevData.isReactRefreshBoundary &&
  222. shouldInvalidateReactRefreshBoundary(
  223. prevData.signature,
  224. getReactRefreshBoundarySignature(moduleExports)
  225. )
  226. ) {
  227. webpackHot.invalidate();
  228. } else {
  229. enqueueUpdate(
  230. /**
  231. * A function to dismiss the error overlay after performing React refresh.
  232. * @returns {void}
  233. */
  234. function updateCallback() {
  235. if (typeof refreshOverlay !== 'undefined' && refreshOverlay) {
  236. refreshOverlay.clearRuntimeErrors();
  237. }
  238. }
  239. );
  240. }
  241. }
  242. } else {
  243. if (isHotUpdate && typeof prevData !== 'undefined') {
  244. webpackHot.invalidate();
  245. }
  246. }
  247. }
  248. }
  249. module.exports = Object.freeze({
  250. enqueueUpdate: enqueueUpdate,
  251. executeRuntime: executeRuntime,
  252. getModuleExports: getModuleExports,
  253. isReactRefreshBoundary: isReactRefreshBoundary,
  254. registerExportsForReactRefresh: registerExportsForReactRefresh,
  255. });