hot-reloader.client.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. "client";
  2. "use strict";
  3. Object.defineProperty(exports, "__esModule", {
  4. value: true
  5. });
  6. exports.default = HotReload;
  7. var _interop_require_default = require("@swc/helpers/lib/_interop_require_default.js").default;
  8. var _react = require("react");
  9. var _appRouterContext = require("../../shared/lib/app-router-context");
  10. var _client = require("next/dist/compiled/@next/react-dev-overlay/dist/client");
  11. var _stripAnsi = _interop_require_default(require("next/dist/compiled/strip-ansi"));
  12. var _formatWebpackMessages = _interop_require_default(require("../dev/error-overlay/format-webpack-messages"));
  13. var _hooksClient = require("./hooks-client");
  14. function HotReload({ assetPrefix }) {
  15. const { tree } = (0, _react).useContext(_appRouterContext.GlobalLayoutRouterContext);
  16. const router = (0, _hooksClient).useRouter();
  17. const webSocketRef = (0, _react).useRef();
  18. const sendMessage = (0, _react).useCallback((data)=>{
  19. const socket = webSocketRef.current;
  20. if (!socket || socket.readyState !== socket.OPEN) return;
  21. return socket.send(data);
  22. }, []);
  23. (0, _react).useEffect(()=>{
  24. (0, _client).register();
  25. const onError = ()=>{
  26. hadRuntimeError = true;
  27. };
  28. window.addEventListener('error', onError);
  29. window.addEventListener('unhandledrejection', onError);
  30. return ()=>{
  31. (0, _client).unregister();
  32. window.removeEventListener('error', onError);
  33. window.removeEventListener('unhandledrejection', onError);
  34. };
  35. }, []);
  36. (0, _react).useEffect(()=>{
  37. if (webSocketRef.current) {
  38. return;
  39. }
  40. const { hostname , port } = window.location;
  41. const protocol = getSocketProtocol(assetPrefix || '');
  42. const normalizedAssetPrefix = assetPrefix.replace(/^\/+/, '');
  43. let url = `${protocol}://${hostname}:${port}${normalizedAssetPrefix ? `/${normalizedAssetPrefix}` : ''}`;
  44. if (normalizedAssetPrefix.startsWith('http')) {
  45. url = `${protocol}://${normalizedAssetPrefix.split('://')[1]}`;
  46. }
  47. webSocketRef.current = new window.WebSocket(`${url}/_next/webpack-hmr`);
  48. }, [
  49. assetPrefix
  50. ]);
  51. (0, _react).useEffect(()=>{
  52. // Taken from on-demand-entries-client.js
  53. // TODO-APP: check 404 case
  54. const interval = setInterval(()=>{
  55. sendMessage(JSON.stringify({
  56. event: 'ping',
  57. // TODO-APP: fix case for dynamic parameters, this will be resolved wrong currently.
  58. tree,
  59. appDirRoute: true
  60. }));
  61. }, 2500);
  62. return ()=>clearInterval(interval);
  63. }, [
  64. tree,
  65. sendMessage
  66. ]);
  67. (0, _react).useEffect(()=>{
  68. const handler = (event)=>{
  69. if (event.data.indexOf('action') === -1 && // TODO-APP: clean this up for consistency
  70. event.data.indexOf('pong') === -1) {
  71. return;
  72. }
  73. try {
  74. processMessage(event, sendMessage, router);
  75. } catch (ex) {
  76. console.warn('Invalid HMR message: ' + event.data + '\n', ex);
  77. }
  78. };
  79. if (webSocketRef.current) {
  80. webSocketRef.current.addEventListener('message', handler);
  81. }
  82. return ()=>webSocketRef.current && webSocketRef.current.removeEventListener('message', handler);
  83. }, [
  84. sendMessage,
  85. router
  86. ]);
  87. // useEffect(() => {
  88. // const interval = setInterval(function () {
  89. // if (
  90. // lastActivityRef.current &&
  91. // Date.now() - lastActivityRef.current > TIMEOUT
  92. // ) {
  93. // handleDisconnect()
  94. // }
  95. // }, 2500)
  96. // return () => clearInterval(interval)
  97. // })
  98. return null;
  99. }
  100. 'client';
  101. function getSocketProtocol(assetPrefix) {
  102. let protocol = window.location.protocol;
  103. try {
  104. // assetPrefix is a url
  105. protocol = new URL(assetPrefix).protocol;
  106. } catch (_) {}
  107. return protocol === 'http:' ? 'ws' : 'wss';
  108. }
  109. let mostRecentCompilationHash = null;
  110. let __nextDevClientId = Math.round(Math.random() * 100 + Date.now());
  111. let hadRuntimeError = false;
  112. // let startLatency = undefined
  113. function onFastRefresh(hasUpdates) {
  114. (0, _client).onBuildOk();
  115. if (hasUpdates) {
  116. (0, _client).onRefresh();
  117. }
  118. // if (startLatency) {
  119. // const endLatency = Date.now()
  120. // const latency = endLatency - startLatency
  121. // console.log(`[Fast Refresh] done in ${latency}ms`)
  122. // sendMessage(
  123. // JSON.stringify({
  124. // event: 'client-hmr-latency',
  125. // id: __nextDevClientId,
  126. // startTime: startLatency,
  127. // endTime: endLatency,
  128. // })
  129. // )
  130. // // if (self.__NEXT_HMR_LATENCY_CB) {
  131. // // self.__NEXT_HMR_LATENCY_CB(latency)
  132. // // }
  133. // }
  134. }
  135. // There is a newer version of the code available.
  136. function handleAvailableHash(hash) {
  137. // Update last known compilation hash.
  138. mostRecentCompilationHash = hash;
  139. }
  140. // Is there a newer version of this code available?
  141. function isUpdateAvailable() {
  142. /* globals __webpack_hash__ */ // __webpack_hash__ is the hash of the current compilation.
  143. // It's a global variable injected by Webpack.
  144. // @ts-expect-error __webpack_hash__ exists
  145. return mostRecentCompilationHash !== __webpack_hash__;
  146. }
  147. // Webpack disallows updates in other states.
  148. function canApplyUpdates() {
  149. // @ts-expect-error module.hot exists
  150. return module.hot.status() === 'idle';
  151. }
  152. // function afterApplyUpdates(fn: any) {
  153. // if (canApplyUpdates()) {
  154. // fn()
  155. // } else {
  156. // function handler(status: any) {
  157. // if (status === 'idle') {
  158. // // @ts-expect-error module.hot exists
  159. // module.hot.removeStatusHandler(handler)
  160. // fn()
  161. // }
  162. // }
  163. // // @ts-expect-error module.hot exists
  164. // module.hot.addStatusHandler(handler)
  165. // }
  166. // }
  167. function performFullReload(err, sendMessage) {
  168. const stackTrace = err && (err.stack && err.stack.split('\n').slice(0, 5).join('\n') || err.message || err + '');
  169. sendMessage(JSON.stringify({
  170. event: 'client-full-reload',
  171. stackTrace
  172. }));
  173. window.location.reload();
  174. }
  175. // Attempt to update code on the fly, fall back to a hard reload.
  176. function tryApplyUpdates(onHotUpdateSuccess, sendMessage) {
  177. // @ts-expect-error module.hot exists
  178. if (!module.hot) {
  179. // HotModuleReplacementPlugin is not in Webpack configuration.
  180. console.error('HotModuleReplacementPlugin is not in Webpack configuration.');
  181. // window.location.reload();
  182. return;
  183. }
  184. if (!isUpdateAvailable() || !canApplyUpdates()) {
  185. (0, _client).onBuildOk();
  186. return;
  187. }
  188. function handleApplyUpdates(err, updatedModules) {
  189. if (err || hadRuntimeError || !updatedModules) {
  190. if (err) {
  191. console.warn('[Fast Refresh] performing full reload\n\n' + "Fast Refresh will perform a full reload when you edit a file that's imported by modules outside of the React rendering tree.\n" + 'You might have a file which exports a React component but also exports a value that is imported by a non-React component file.\n' + 'Consider migrating the non-React component export to a separate file and importing it into both files.\n\n' + 'It is also possible the parent component of the component you edited is a class component, which disables Fast Refresh.\n' + 'Fast Refresh requires at least one parent function component in your React tree.');
  192. } else if (hadRuntimeError) {
  193. console.warn('[Fast Refresh] performing full reload because your application had an unrecoverable error');
  194. }
  195. performFullReload(err, sendMessage);
  196. return;
  197. }
  198. const hasUpdates = Boolean(updatedModules.length);
  199. if (typeof onHotUpdateSuccess === 'function') {
  200. // Maybe we want to do something.
  201. onHotUpdateSuccess(hasUpdates);
  202. }
  203. if (isUpdateAvailable()) {
  204. // While we were updating, there was a new update! Do it again.
  205. tryApplyUpdates(hasUpdates ? _client.onBuildOk : onHotUpdateSuccess, sendMessage);
  206. } else {
  207. (0, _client).onBuildOk();
  208. // if (process.env.__NEXT_TEST_MODE) {
  209. // afterApplyUpdates(() => {
  210. // if (self.__NEXT_HMR_CB) {
  211. // self.__NEXT_HMR_CB()
  212. // self.__NEXT_HMR_CB = null
  213. // }
  214. // })
  215. // }
  216. }
  217. }
  218. // https://webpack.js.org/api/hot-module-replacement/#check
  219. // @ts-expect-error module.hot exists
  220. module.hot.check(/* autoApply */ true).then((updatedModules)=>{
  221. handleApplyUpdates(null, updatedModules);
  222. }, (err)=>{
  223. handleApplyUpdates(err, null);
  224. });
  225. }
  226. function processMessage(e, sendMessage, router) {
  227. const obj = JSON.parse(e.data);
  228. switch(obj.action){
  229. case 'building':
  230. {
  231. // startLatency = Date.now()
  232. console.log('[Fast Refresh] rebuilding');
  233. break;
  234. }
  235. case 'built':
  236. case 'sync':
  237. {
  238. if (obj.hash) {
  239. handleAvailableHash(obj.hash);
  240. }
  241. const { errors , warnings } = obj;
  242. const hasErrors = Boolean(errors && errors.length);
  243. // Compilation with errors (e.g. syntax error or missing modules).
  244. if (hasErrors) {
  245. sendMessage(JSON.stringify({
  246. event: 'client-error',
  247. errorCount: errors.length,
  248. clientId: __nextDevClientId
  249. }));
  250. // "Massage" webpack messages.
  251. var formatted = (0, _formatWebpackMessages).default({
  252. errors: errors,
  253. warnings: []
  254. });
  255. // Only show the first error.
  256. (0, _client).onBuildError(formatted.errors[0]);
  257. // Also log them to the console.
  258. for(let i = 0; i < formatted.errors.length; i++){
  259. console.error((0, _stripAnsi).default(formatted.errors[i]));
  260. }
  261. // Do not attempt to reload now.
  262. // We will reload on next success instead.
  263. // if (process.env.__NEXT_TEST_MODE) {
  264. // if (self.__NEXT_HMR_CB) {
  265. // self.__NEXT_HMR_CB(formatted.errors[0])
  266. // self.__NEXT_HMR_CB = null
  267. // }
  268. // }
  269. return;
  270. }
  271. const hasWarnings = Boolean(warnings && warnings.length);
  272. if (hasWarnings) {
  273. sendMessage(JSON.stringify({
  274. event: 'client-warning',
  275. warningCount: warnings.length,
  276. clientId: __nextDevClientId
  277. }));
  278. // Compilation with warnings (e.g. ESLint).
  279. const isHotUpdate = obj.action !== 'sync';
  280. // Print warnings to the console.
  281. const formattedMessages = (0, _formatWebpackMessages).default({
  282. warnings: warnings,
  283. errors: []
  284. });
  285. for(let i = 0; i < formattedMessages.warnings.length; i++){
  286. if (i === 5) {
  287. console.warn('There were more warnings in other files.\n' + 'You can find a complete log in the terminal.');
  288. break;
  289. }
  290. console.warn((0, _stripAnsi).default(formattedMessages.warnings[i]));
  291. }
  292. // Attempt to apply hot updates or reload.
  293. if (isHotUpdate) {
  294. tryApplyUpdates(function onSuccessfulHotUpdate(hasUpdates) {
  295. // Only dismiss it when we're sure it's a hot update.
  296. // Otherwise it would flicker right before the reload.
  297. onFastRefresh(hasUpdates);
  298. }, sendMessage);
  299. }
  300. return;
  301. }
  302. sendMessage(JSON.stringify({
  303. event: 'client-success',
  304. clientId: __nextDevClientId
  305. }));
  306. const isHotUpdate = obj.action !== 'sync' || (!window.__NEXT_DATA__ || window.__NEXT_DATA__.page !== '/_error') && isUpdateAvailable();
  307. // Attempt to apply hot updates or reload.
  308. if (isHotUpdate) {
  309. tryApplyUpdates(function onSuccessfulHotUpdate(hasUpdates) {
  310. // Only dismiss it when we're sure it's a hot update.
  311. // Otherwise it would flicker right before the reload.
  312. onFastRefresh(hasUpdates);
  313. }, sendMessage);
  314. }
  315. return;
  316. }
  317. // TODO-APP: make server component change more granular
  318. case 'serverComponentChanges':
  319. {
  320. sendMessage(JSON.stringify({
  321. event: 'server-component-reload-page',
  322. clientId: __nextDevClientId
  323. }));
  324. if (hadRuntimeError) {
  325. return window.location.reload();
  326. }
  327. (0, _react).startTransition(()=>{
  328. router.reload();
  329. (0, _client).onRefresh();
  330. });
  331. return;
  332. }
  333. case 'reloadPage':
  334. {
  335. sendMessage(JSON.stringify({
  336. event: 'client-reload-page',
  337. clientId: __nextDevClientId
  338. }));
  339. return window.location.reload();
  340. }
  341. case 'removedPage':
  342. {
  343. // const [page] = obj.data
  344. // if (page === window.next.router.pathname) {
  345. // sendMessage(
  346. // JSON.stringify({
  347. // event: 'client-removed-page',
  348. // clientId: window.__nextDevClientId,
  349. // page,
  350. // })
  351. // )
  352. // return window.location.reload()
  353. // }
  354. return;
  355. }
  356. case 'addedPage':
  357. {
  358. // const [page] = obj.data
  359. // if (
  360. // page === window.next.router.pathname &&
  361. // typeof window.next.router.components[page] === 'undefined'
  362. // ) {
  363. // sendMessage(
  364. // JSON.stringify({
  365. // event: 'client-added-page',
  366. // clientId: window.__nextDevClientId,
  367. // page,
  368. // })
  369. // )
  370. // return window.location.reload()
  371. // }
  372. return;
  373. }
  374. case 'pong':
  375. {
  376. const { invalid } = obj;
  377. if (invalid) {
  378. // Payload can be invalid even if the page does exist.
  379. // So, we check if it can be created.
  380. fetch(location.href, {
  381. credentials: 'same-origin'
  382. }).then((pageRes)=>{
  383. if (pageRes.status === 200) {
  384. // Page exists now, reload
  385. location.reload();
  386. } else {
  387. // TODO-APP: fix this
  388. // Page doesn't exist
  389. // if (
  390. // self.__NEXT_DATA__.page === Router.pathname &&
  391. // Router.pathname !== '/_error'
  392. // ) {
  393. // // We are still on the page,
  394. // // reload to show 404 error page
  395. // location.reload()
  396. // }
  397. }
  398. });
  399. }
  400. return;
  401. }
  402. default:
  403. {
  404. throw new Error('Unexpected action ' + obj.action);
  405. }
  406. }
  407. }
  408. if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
  409. Object.defineProperty(exports.default, '__esModule', { value: true });
  410. Object.assign(exports.default, exports);
  411. module.exports = exports.default;
  412. }
  413. //# sourceMappingURL=hot-reloader.client.js.map