UNPKG

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