211 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			211 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| "use strict";
 | |
| 
 | |
| /* eslint-env browser */
 | |
| 
 | |
| /*
 | |
|   eslint-disable
 | |
|   no-console,
 | |
|   func-names
 | |
| */
 | |
| var normalizeUrl = require('normalize-url');
 | |
| 
 | |
| var srcByModuleId = Object.create(null);
 | |
| var noDocument = typeof document === 'undefined';
 | |
| var forEach = Array.prototype.forEach;
 | |
| 
 | |
| function debounce(fn, time) {
 | |
|   var timeout = 0;
 | |
|   return function () {
 | |
|     var self = this; // eslint-disable-next-line prefer-rest-params
 | |
| 
 | |
|     var args = arguments;
 | |
| 
 | |
|     var functionCall = function functionCall() {
 | |
|       return fn.apply(self, args);
 | |
|     };
 | |
| 
 | |
|     clearTimeout(timeout);
 | |
|     timeout = setTimeout(functionCall, time);
 | |
|   };
 | |
| }
 | |
| 
 | |
| function noop() {}
 | |
| 
 | |
| function getCurrentScriptUrl(moduleId) {
 | |
|   var src = srcByModuleId[moduleId];
 | |
| 
 | |
|   if (!src) {
 | |
|     if (document.currentScript) {
 | |
|       src = document.currentScript.src;
 | |
|     } else {
 | |
|       var scripts = document.getElementsByTagName('script');
 | |
|       var lastScriptTag = scripts[scripts.length - 1];
 | |
| 
 | |
|       if (lastScriptTag) {
 | |
|         src = lastScriptTag.src;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     srcByModuleId[moduleId] = src;
 | |
|   }
 | |
| 
 | |
|   return function (fileMap) {
 | |
|     if (!src) {
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     var splitResult = src.split(/([^\\/]+)\.js$/);
 | |
|     var filename = splitResult && splitResult[1];
 | |
| 
 | |
|     if (!filename) {
 | |
|       return [src.replace('.js', '.css')];
 | |
|     }
 | |
| 
 | |
|     if (!fileMap) {
 | |
|       return [src.replace('.js', '.css')];
 | |
|     }
 | |
| 
 | |
|     return fileMap.split(',').map(function (mapRule) {
 | |
|       var reg = new RegExp("".concat(filename, "\\.js$"), 'g');
 | |
|       return normalizeUrl(src.replace(reg, "".concat(mapRule.replace(/{fileName}/g, filename), ".css")), {
 | |
|         stripWWW: false
 | |
|       });
 | |
|     });
 | |
|   };
 | |
| }
 | |
| 
 | |
| function updateCss(el, url) {
 | |
|   if (!url) {
 | |
|     if (!el.href) {
 | |
|       return;
 | |
|     } // eslint-disable-next-line
 | |
| 
 | |
| 
 | |
|     url = el.href.split('?')[0];
 | |
|   }
 | |
| 
 | |
|   if (!isUrlRequest(url)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (el.isLoaded === false) {
 | |
|     // We seem to be about to replace a css link that hasn't loaded yet.
 | |
|     // We're probably changing the same file more than once.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!url || !(url.indexOf('.css') > -1)) {
 | |
|     return;
 | |
|   } // eslint-disable-next-line no-param-reassign
 | |
| 
 | |
| 
 | |
|   el.visited = true;
 | |
|   var newEl = el.cloneNode();
 | |
|   newEl.isLoaded = false;
 | |
|   newEl.addEventListener('load', function () {
 | |
|     newEl.isLoaded = true;
 | |
|     el.parentNode.removeChild(el);
 | |
|   });
 | |
|   newEl.addEventListener('error', function () {
 | |
|     newEl.isLoaded = true;
 | |
|     el.parentNode.removeChild(el);
 | |
|   });
 | |
|   newEl.href = "".concat(url, "?").concat(Date.now());
 | |
| 
 | |
|   if (el.nextSibling) {
 | |
|     el.parentNode.insertBefore(newEl, el.nextSibling);
 | |
|   } else {
 | |
|     el.parentNode.appendChild(newEl);
 | |
|   }
 | |
| }
 | |
| 
 | |
| function getReloadUrl(href, src) {
 | |
|   var ret; // eslint-disable-next-line no-param-reassign
 | |
| 
 | |
|   href = normalizeUrl(href, {
 | |
|     stripWWW: false
 | |
|   }); // eslint-disable-next-line array-callback-return
 | |
| 
 | |
|   src.some(function (url) {
 | |
|     if (href.indexOf(src) > -1) {
 | |
|       ret = url;
 | |
|     }
 | |
|   });
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| function reloadStyle(src) {
 | |
|   var elements = document.querySelectorAll('link');
 | |
|   var loaded = false;
 | |
|   forEach.call(elements, function (el) {
 | |
|     if (!el.href) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     var url = getReloadUrl(el.href, src);
 | |
| 
 | |
|     if (!isUrlRequest(url)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (el.visited === true) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (url) {
 | |
|       updateCss(el, url);
 | |
|       loaded = true;
 | |
|     }
 | |
|   });
 | |
|   return loaded;
 | |
| }
 | |
| 
 | |
| function reloadAll() {
 | |
|   var elements = document.querySelectorAll('link');
 | |
|   forEach.call(elements, function (el) {
 | |
|     if (el.visited === true) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     updateCss(el);
 | |
|   });
 | |
| }
 | |
| 
 | |
| function isUrlRequest(url) {
 | |
|   // An URL is not an request if
 | |
|   // It is not http or https
 | |
|   if (!/^https?:/i.test(url)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| module.exports = function (moduleId, options) {
 | |
|   if (noDocument) {
 | |
|     console.log('no window.document found, will not HMR CSS');
 | |
|     return noop;
 | |
|   }
 | |
| 
 | |
|   var getScriptSrc = getCurrentScriptUrl(moduleId);
 | |
| 
 | |
|   function update() {
 | |
|     var src = getScriptSrc(options.filename);
 | |
|     var reloaded = reloadStyle(src);
 | |
| 
 | |
|     if (options.locals) {
 | |
|       console.log('[HMR] Detected local css modules. Reload all css');
 | |
|       reloadAll();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (reloaded && !options.reloadAll) {
 | |
|       console.log('[HMR] css reload %s', src.join(' '));
 | |
|     } else {
 | |
|       console.log('[HMR] Reload all css');
 | |
|       reloadAll();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return debounce(update, 50);
 | |
| }; | 
