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