hotModuleReplacement.js 4.0 KB

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