hotModuleReplacement.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. "use strict";
  2. /* eslint-env browser */
  3. /*
  4. eslint-disable
  5. no-console,
  6. func-names
  7. */
  8. var normalizeUrl = require('./normalize-url');
  9. var srcByModuleId = Object.create(null);
  10. var noDocument = typeof document === 'undefined';
  11. var forEach = Array.prototype.forEach;
  12. function debounce(fn, time) {
  13. var timeout = 0;
  14. return function () {
  15. var self = this; // eslint-disable-next-line prefer-rest-params
  16. var args = arguments;
  17. var functionCall = function functionCall() {
  18. return fn.apply(self, args);
  19. };
  20. clearTimeout(timeout);
  21. timeout = setTimeout(functionCall, time);
  22. };
  23. }
  24. function noop() {}
  25. function getCurrentScriptUrl(moduleId) {
  26. var src = srcByModuleId[moduleId];
  27. if (!src) {
  28. if (document.currentScript) {
  29. src = document.currentScript.src;
  30. } else {
  31. var scripts = document.getElementsByTagName('script');
  32. var lastScriptTag = scripts[scripts.length - 1];
  33. if (lastScriptTag) {
  34. src = lastScriptTag.src;
  35. }
  36. }
  37. srcByModuleId[moduleId] = src;
  38. }
  39. return function (fileMap) {
  40. if (!src) {
  41. return null;
  42. }
  43. var splitResult = src.split(/([^\\/]+)\.js$/);
  44. var filename = splitResult && splitResult[1];
  45. if (!filename) {
  46. return [src.replace('.js', '.css')];
  47. }
  48. if (!fileMap) {
  49. return [src.replace('.js', '.css')];
  50. }
  51. return fileMap.split(',').map(function (mapRule) {
  52. var reg = new RegExp("".concat(filename, "\\.js$"), 'g');
  53. return normalizeUrl(src.replace(reg, "".concat(mapRule.replace(/{fileName}/g, filename), ".css")));
  54. });
  55. };
  56. }
  57. function updateCss(el, url) {
  58. if (!url) {
  59. if (!el.href) {
  60. return;
  61. } // eslint-disable-next-line
  62. url = el.href.split('?')[0];
  63. }
  64. if (!isUrlRequest(url)) {
  65. return;
  66. }
  67. if (el.isLoaded === false) {
  68. // We seem to be about to replace a css link that hasn't loaded yet.
  69. // We're probably changing the same file more than once.
  70. return;
  71. }
  72. if (!url || !(url.indexOf('.css') > -1)) {
  73. return;
  74. } // eslint-disable-next-line no-param-reassign
  75. el.visited = true;
  76. var newEl = el.cloneNode();
  77. newEl.isLoaded = false;
  78. newEl.addEventListener('load', function () {
  79. if (newEl.isLoaded) {
  80. return;
  81. }
  82. newEl.isLoaded = true;
  83. el.parentNode.removeChild(el);
  84. });
  85. newEl.addEventListener('error', function () {
  86. if (newEl.isLoaded) {
  87. return;
  88. }
  89. newEl.isLoaded = true;
  90. el.parentNode.removeChild(el);
  91. });
  92. newEl.href = "".concat(url, "?").concat(Date.now());
  93. if (el.nextSibling) {
  94. el.parentNode.insertBefore(newEl, el.nextSibling);
  95. } else {
  96. el.parentNode.appendChild(newEl);
  97. }
  98. }
  99. function getReloadUrl(href, src) {
  100. var ret; // eslint-disable-next-line no-param-reassign
  101. href = normalizeUrl(href, {
  102. stripWWW: false
  103. }); // eslint-disable-next-line array-callback-return
  104. src.some(function (url) {
  105. if (href.indexOf(src) > -1) {
  106. ret = url;
  107. }
  108. });
  109. return ret;
  110. }
  111. function reloadStyle(src) {
  112. if (!src) {
  113. return false;
  114. }
  115. var elements = document.querySelectorAll('link');
  116. var loaded = false;
  117. forEach.call(elements, function (el) {
  118. if (!el.href) {
  119. return;
  120. }
  121. var url = getReloadUrl(el.href, src);
  122. if (!isUrlRequest(url)) {
  123. return;
  124. }
  125. if (el.visited === true) {
  126. return;
  127. }
  128. if (url) {
  129. updateCss(el, url);
  130. loaded = true;
  131. }
  132. });
  133. return loaded;
  134. }
  135. function reloadAll() {
  136. var elements = document.querySelectorAll('link');
  137. forEach.call(elements, function (el) {
  138. if (el.visited === true) {
  139. return;
  140. }
  141. updateCss(el);
  142. });
  143. }
  144. function isUrlRequest(url) {
  145. // An URL is not an request if
  146. // It is not http or https
  147. if (!/^https?:/i.test(url)) {
  148. return false;
  149. }
  150. return true;
  151. }
  152. module.exports = function (moduleId, options) {
  153. if (noDocument) {
  154. console.log('no window.document found, will not HMR CSS');
  155. return noop;
  156. }
  157. var getScriptSrc = getCurrentScriptUrl(moduleId);
  158. function update() {
  159. var src = getScriptSrc(options.filename);
  160. var reloaded = reloadStyle(src);
  161. if (options.locals) {
  162. console.log('[HMR] Detected local css modules. Reload all css');
  163. reloadAll();
  164. return;
  165. }
  166. if (reloaded) {
  167. console.log('[HMR] css reload %s', src.join(' '));
  168. } else {
  169. console.log('[HMR] Reload all css');
  170. reloadAll();
  171. }
  172. }
  173. return debounce(update, 50);
  174. };