source: frontend/node_modules/@pmmmwh/react-refresh-webpack-plugin/overlay/index.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 13 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 9.1 KB
Line 
1const RuntimeErrorFooter = require('./components/RuntimeErrorFooter.js');
2const RuntimeErrorHeader = require('./components/RuntimeErrorHeader.js');
3const CompileErrorContainer = require('./containers/CompileErrorContainer.js');
4const RuntimeErrorContainer = require('./containers/RuntimeErrorContainer.js');
5const theme = require('./theme.js');
6const utils = require('./utils.js');
7
8/**
9 * @callback RenderFn
10 * @returns {void}
11 */
12
13/* ===== Cached elements for DOM manipulations ===== */
14/**
15 * The iframe that contains the overlay.
16 * @type {HTMLIFrameElement}
17 */
18let iframeRoot = null;
19/**
20 * The document object from the iframe root, used to create and render elements.
21 * @type {Document}
22 */
23let rootDocument = null;
24/**
25 * The root div elements will attach to.
26 * @type {HTMLDivElement}
27 */
28let root = null;
29/**
30 * A Cached function to allow deferred render.
31 * @type {RenderFn | null}
32 */
33let scheduledRenderFn = null;
34
35/* ===== Overlay State ===== */
36/**
37 * The latest error message from Webpack compilation.
38 * @type {string}
39 */
40let currentCompileErrorMessage = '';
41/**
42 * Index of the error currently shown by the overlay.
43 * @type {number}
44 */
45let currentRuntimeErrorIndex = 0;
46/**
47 * The latest runtime error objects.
48 * @type {Error[]}
49 */
50let currentRuntimeErrors = [];
51/**
52 * The render mode the overlay is currently in.
53 * @type {'compileError' | 'runtimeError' | null}
54 */
55let currentMode = null;
56
57/**
58 * @typedef {Object} IframeProps
59 * @property {function(): void} onIframeLoad
60 */
61
62/**
63 * Creates the main `iframe` the overlay will attach to.
64 * Accepts a callback to be ran after iframe is initialized.
65 * @param {Document} document
66 * @param {HTMLElement} root
67 * @param {IframeProps} props
68 * @returns {HTMLIFrameElement}
69 */
70function IframeRoot(document, root, props) {
71 const iframe = document.createElement('iframe');
72 iframe.id = 'react-refresh-overlay';
73 iframe.src = 'about:blank';
74
75 iframe.style.border = 'none';
76 iframe.style.height = '100%';
77 iframe.style.left = '0';
78 iframe.style.minHeight = '100vh';
79 iframe.style.minHeight = '-webkit-fill-available';
80 iframe.style.position = 'fixed';
81 iframe.style.top = '0';
82 iframe.style.width = '100vw';
83 iframe.style.zIndex = '2147483647';
84 iframe.addEventListener('load', function onLoad() {
85 // Reset margin of iframe body
86 iframe.contentDocument.body.style.margin = '0';
87 props.onIframeLoad();
88 });
89
90 // We skip mounting and returns as we need to ensure
91 // the load event is fired after we setup the global variable
92 return iframe;
93}
94
95/**
96 * Creates the main `div` element for the overlay to render.
97 * @param {Document} document
98 * @param {HTMLElement} root
99 * @returns {HTMLDivElement}
100 */
101function OverlayRoot(document, root) {
102 const div = document.createElement('div');
103 div.id = 'react-refresh-overlay-error';
104
105 // Style the contents container
106 div.style.backgroundColor = '#' + theme.grey;
107 div.style.boxSizing = 'border-box';
108 div.style.color = '#' + theme.white;
109 div.style.fontFamily = [
110 '-apple-system',
111 'BlinkMacSystemFont',
112 '"Segoe UI"',
113 '"Helvetica Neue"',
114 'Helvetica',
115 'Arial',
116 'sans-serif',
117 '"Apple Color Emoji"',
118 '"Segoe UI Emoji"',
119 'Segoe UI Symbol',
120 ].join(', ');
121 div.style.fontSize = '0.875rem';
122 div.style.height = '100%';
123 div.style.lineHeight = '1.3';
124 div.style.overflow = 'auto';
125 div.style.padding = '1rem 1.5rem 0';
126 div.style.paddingTop = 'max(1rem, env(safe-area-inset-top))';
127 div.style.paddingRight = 'max(1.5rem, env(safe-area-inset-right))';
128 div.style.paddingBottom = 'env(safe-area-inset-bottom)';
129 div.style.paddingLeft = 'max(1.5rem, env(safe-area-inset-left))';
130 div.style.width = '100vw';
131
132 root.appendChild(div);
133 return div;
134}
135
136/**
137 * Ensures the iframe root and the overlay root are both initialized before render.
138 * If check fails, render will be deferred until both roots are initialized.
139 * @param {RenderFn} renderFn A function that triggers a DOM render.
140 * @returns {void}
141 */
142function ensureRootExists(renderFn) {
143 if (root) {
144 // Overlay root is ready, we can render right away.
145 renderFn();
146 return;
147 }
148
149 // Creating an iframe may be asynchronous so we'll defer render.
150 // In case of multiple calls, function from the last call will be used.
151 scheduledRenderFn = renderFn;
152
153 if (iframeRoot) {
154 // Iframe is already ready, it will fire the load event.
155 return;
156 }
157
158 // Create the iframe root, and, the overlay root inside it when it is ready.
159 iframeRoot = IframeRoot(document, document.body, {
160 onIframeLoad: function onIframeLoad() {
161 rootDocument = iframeRoot.contentDocument;
162 root = OverlayRoot(rootDocument, rootDocument.body);
163 scheduledRenderFn();
164 },
165 });
166
167 // We have to mount here to ensure `iframeRoot` is set when `onIframeLoad` fires.
168 // This is because onIframeLoad() will be called synchronously
169 // or asynchronously depending on the browser.
170 document.body.appendChild(iframeRoot);
171}
172
173/**
174 * Creates the main `div` element for the overlay to render.
175 * @returns {void}
176 */
177function render() {
178 ensureRootExists(function () {
179 const currentFocus = rootDocument.activeElement;
180 let currentFocusId;
181 if (currentFocus.localName === 'button' && currentFocus.id) {
182 currentFocusId = currentFocus.id;
183 }
184
185 utils.removeAllChildren(root);
186
187 if (currentCompileErrorMessage) {
188 currentMode = 'compileError';
189
190 CompileErrorContainer(rootDocument, root, {
191 errorMessage: currentCompileErrorMessage,
192 });
193 } else if (currentRuntimeErrors.length) {
194 currentMode = 'runtimeError';
195
196 RuntimeErrorHeader(rootDocument, root, {
197 currentErrorIndex: currentRuntimeErrorIndex,
198 totalErrors: currentRuntimeErrors.length,
199 });
200 RuntimeErrorContainer(rootDocument, root, {
201 currentError: currentRuntimeErrors[currentRuntimeErrorIndex],
202 });
203 RuntimeErrorFooter(rootDocument, root, {
204 initialFocus: currentFocusId,
205 multiple: currentRuntimeErrors.length > 1,
206 onClickCloseButton: function onClose() {
207 clearRuntimeErrors();
208 },
209 onClickNextButton: function onNext() {
210 if (currentRuntimeErrorIndex === currentRuntimeErrors.length - 1) {
211 return;
212 }
213 currentRuntimeErrorIndex += 1;
214 ensureRootExists(render);
215 },
216 onClickPrevButton: function onPrev() {
217 if (currentRuntimeErrorIndex === 0) {
218 return;
219 }
220 currentRuntimeErrorIndex -= 1;
221 ensureRootExists(render);
222 },
223 });
224 }
225 });
226}
227
228/**
229 * Destroys the state of the overlay.
230 * @returns {void}
231 */
232function cleanup() {
233 // Clean up and reset all internal state.
234 document.body.removeChild(iframeRoot);
235 scheduledRenderFn = null;
236 root = null;
237 iframeRoot = null;
238}
239
240/**
241 * Clears Webpack compilation errors and dismisses the compile error overlay.
242 * @returns {void}
243 */
244function clearCompileError() {
245 if (!root || currentMode !== 'compileError') {
246 return;
247 }
248
249 currentCompileErrorMessage = '';
250 currentMode = null;
251 cleanup();
252}
253
254/**
255 * Clears runtime error records and dismisses the runtime error overlay.
256 * @param {boolean} [dismissOverlay] Whether to dismiss the overlay or not.
257 * @returns {void}
258 */
259function clearRuntimeErrors(dismissOverlay) {
260 if (!root || currentMode !== 'runtimeError') {
261 return;
262 }
263
264 currentRuntimeErrorIndex = 0;
265 currentRuntimeErrors = [];
266
267 if (typeof dismissOverlay === 'undefined' || dismissOverlay) {
268 currentMode = null;
269 cleanup();
270 }
271}
272
273/**
274 * Shows the compile error overlay with the specific Webpack error message.
275 * @param {string} message
276 * @returns {void}
277 */
278function showCompileError(message) {
279 if (!message) {
280 return;
281 }
282
283 currentCompileErrorMessage = message;
284
285 render();
286}
287
288/**
289 * Shows the runtime error overlay with the specific error records.
290 * @param {Error[]} errors
291 * @returns {void}
292 */
293function showRuntimeErrors(errors) {
294 if (!errors || !errors.length) {
295 return;
296 }
297
298 currentRuntimeErrors = errors;
299
300 render();
301}
302
303/**
304 * The debounced version of `showRuntimeErrors` to prevent frequent renders
305 * due to rapid firing listeners.
306 * @param {Error[]} errors
307 * @returns {void}
308 */
309const debouncedShowRuntimeErrors = utils.debounce(showRuntimeErrors, 30);
310
311/**
312 * Detects if an error is a Webpack compilation error.
313 * @param {Error} error The error of interest.
314 * @returns {boolean} If the error is a Webpack compilation error.
315 */
316function isWebpackCompileError(error) {
317 return /Module [A-z ]+\(from/.test(error.message) || /Cannot find module/.test(error.message);
318}
319
320/**
321 * Handles runtime error contexts captured with EventListeners.
322 * Integrates with a runtime error overlay.
323 * @param {Error} error A valid error object.
324 * @returns {void}
325 */
326function handleRuntimeError(error) {
327 if (error && !isWebpackCompileError(error) && currentRuntimeErrors.indexOf(error) === -1) {
328 currentRuntimeErrors = currentRuntimeErrors.concat(error);
329 }
330 debouncedShowRuntimeErrors(currentRuntimeErrors);
331}
332
333module.exports = Object.freeze({
334 clearCompileError: clearCompileError,
335 clearRuntimeErrors: clearRuntimeErrors,
336 handleRuntimeError: handleRuntimeError,
337 showCompileError: showCompileError,
338 showRuntimeErrors: showRuntimeErrors,
339});
Note: See TracBrowser for help on using the repository browser.