hotModuleReplacement.js 5.0 KB

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