UNPKG

17.6 kBJavaScriptView Raw
1import './env';
2
3const template = /*html*/ `
4<style>
5:host {
6 position: fixed;
7 z-index: 99999;
8 top: 0;
9 left: 0;
10 width: 100%;
11 height: 100%;
12 overflow-y: scroll;
13 margin: 0;
14 background: rgba(0, 0, 0, 0.66);
15 --monospace: 'SFMono-Regular', Consolas,
16 'Liberation Mono', Menlo, Courier, monospace;
17 --red: #ff5555;
18 --yellow: #e2aa53;
19 --purple: #cfa4ff;
20 --cyan: #2dd9da;
21 --dim: #c9c9c9;
22}
23
24.window {
25 font-family: var(--monospace);
26 line-height: 1.5;
27 width: 800px;
28 color: #d8d8d8;
29 margin: 30px auto;
30 padding: 25px 40px;
31 position: relative;
32 background: #181818;
33 border-radius: 6px 6px 8px 8px;
34 box-shadow: 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22);
35 overflow: hidden;
36 border-top: 8px solid var(--red);
37}
38
39pre {
40 font-family: var(--monospace);
41 font-size: 16px;
42 margin-top: 0;
43 margin-bottom: 1em;
44 overflow-x: scroll;
45 scrollbar-width: none;
46}
47
48pre::-webkit-scrollbar {
49 display: none;
50}
51
52.message {
53 line-height: 1.3;
54 font-weight: 600;
55 white-space: pre-wrap;
56}
57
58.message-body {
59 color: var(--red);
60}
61
62.plugin {
63 color: var(--purple);
64}
65
66.file {
67 color: var(--cyan);
68 margin-bottom: 0;
69 white-space: pre-wrap;
70 word-break: break-all;
71}
72
73.frame {
74 color: var(--yellow);
75}
76
77.stack {
78 font-size: 13px;
79 color: var(--dim);
80}
81
82.tip {
83 font-size: 13px;
84 color: #999;
85 border-top: 1px dotted #999;
86 padding-top: 13px;
87}
88
89code {
90 font-size: 13px;
91 font-family: var(--monospace);
92 color: var(--yellow);
93}
94
95.file-link {
96 text-decoration: underline;
97 cursor: pointer;
98}
99</style>
100<div class="window">
101 <pre class="message"><span class="plugin"></span><span class="message-body"></span></pre>
102 <pre class="file"></pre>
103 <pre class="frame"></pre>
104 <pre class="stack"></pre>
105 <div class="tip">
106 Click outside or fix the code to dismiss.<br>
107 You can also disable this overlay with
108 <code>hmr: { overlay: false }</code> in <code>vite.config.js.</code>
109 </div>
110</div>
111`;
112const fileRE = /(?:[a-zA-Z]:\\|\/).*?:\d+:\d+/g;
113const codeframeRE = /^(?:>?\s+\d+\s+\|.*|\s+\|\s*\^.*)\r?\n/gm;
114class ErrorOverlay extends HTMLElement {
115 constructor(err) {
116 var _a;
117 super();
118 this.root = this.attachShadow({ mode: 'open' });
119 this.root.innerHTML = template;
120 codeframeRE.lastIndex = 0;
121 const hasFrame = err.frame && codeframeRE.test(err.frame);
122 const message = hasFrame
123 ? err.message.replace(codeframeRE, '')
124 : err.message;
125 if (err.plugin) {
126 this.text('.plugin', `[plugin:${err.plugin}] `);
127 }
128 this.text('.message-body', message.trim());
129 const [file] = (((_a = err.loc) === null || _a === void 0 ? void 0 : _a.file) || err.id || 'unknown file').split(`?`);
130 if (err.loc) {
131 this.text('.file', `${file}:${err.loc.line}:${err.loc.column}`, true);
132 }
133 else if (err.id) {
134 this.text('.file', file);
135 }
136 if (hasFrame) {
137 this.text('.frame', err.frame.trim());
138 }
139 this.text('.stack', err.stack, true);
140 this.root.querySelector('.window').addEventListener('click', (e) => {
141 e.stopPropagation();
142 });
143 this.addEventListener('click', () => {
144 this.close();
145 });
146 }
147 text(selector, text, linkFiles = false) {
148 const el = this.root.querySelector(selector);
149 if (!linkFiles) {
150 el.textContent = text;
151 }
152 else {
153 let curIndex = 0;
154 let match;
155 while ((match = fileRE.exec(text))) {
156 const { 0: file, index } = match;
157 if (index != null) {
158 const frag = text.slice(curIndex, index);
159 el.appendChild(document.createTextNode(frag));
160 const link = document.createElement('a');
161 link.textContent = file;
162 link.className = 'file-link';
163 link.onclick = () => {
164 fetch('/__open-in-editor?file=' + encodeURIComponent(file));
165 };
166 el.appendChild(link);
167 curIndex += frag.length + file.length;
168 }
169 }
170 }
171 }
172 close() {
173 var _a;
174 (_a = this.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(this);
175 }
176}
177const overlayId = 'vite-error-overlay';
178customElements.define(overlayId, ErrorOverlay);
179
180console.log('[vite] connecting...');
181// use server configuration, then fallback to inference
182const socketProtocol = __HMR_PROTOCOL__ || (location.protocol === 'https:' ? 'wss' : 'ws');
183const socketHost = `${__HMR_HOSTNAME__ || location.hostname}:${__HMR_PORT__}`;
184const socket = new WebSocket(`${socketProtocol}://${socketHost}`, 'vite-hmr');
185const base = __BASE__ || '/';
186function warnFailedFetch(err, path) {
187 if (!err.message.match('fetch')) {
188 console.error(err);
189 }
190 console.error(`[hmr] Failed to reload ${path}. ` +
191 `This could be due to syntax errors or importing non-existent ` +
192 `modules. (see errors above)`);
193}
194// Listen for messages
195socket.addEventListener('message', async ({ data }) => {
196 handleMessage(JSON.parse(data));
197});
198let isFirstUpdate = true;
199async function handleMessage(payload) {
200 switch (payload.type) {
201 case 'connected':
202 console.log(`[vite] connected.`);
203 // proxy(nginx, docker) hmr ws maybe caused timeout,
204 // so send ping package let ws keep alive.
205 setInterval(() => socket.send('ping'), __HMR_TIMEOUT__);
206 break;
207 case 'update':
208 notifyListeners('vite:beforeUpdate', payload);
209 // if this is the first update and there's already an error overlay, it
210 // means the page opened with existing server compile error and the whole
211 // module script failed to load (since one of the nested imports is 500).
212 // in this case a normal update won't work and a full reload is needed.
213 if (isFirstUpdate && hasErrorOverlay()) {
214 window.location.reload();
215 return;
216 }
217 else {
218 clearErrorOverlay();
219 isFirstUpdate = false;
220 }
221 payload.updates.forEach((update) => {
222 if (update.type === 'js-update') {
223 queueUpdate(fetchUpdate(update));
224 }
225 else {
226 // css-update
227 // this is only sent when a css file referenced with <link> is updated
228 let { path, timestamp } = update;
229 path = path.replace(/\?.*/, '');
230 // can't use querySelector with `[href*=]` here since the link may be
231 // using relative paths so we need to use link.href to grab the full
232 // URL for the include check.
233 const el = [].slice.call(document.querySelectorAll(`link`)).find((e) => e.href.includes(path));
234 if (el) {
235 const newPath = `${path}${path.includes('?') ? '&' : '?'}t=${timestamp}`;
236 el.href = new URL(newPath, el.href).href;
237 }
238 console.log(`[vite] css hot updated: ${path}`);
239 }
240 });
241 break;
242 case 'custom': {
243 notifyListeners(payload.event, payload.data);
244 break;
245 }
246 case 'full-reload':
247 notifyListeners('vite:beforeFullReload', payload);
248 if (payload.path && payload.path.endsWith('.html')) {
249 // if html file is edited, only reload the page if the browser is
250 // currently on that page.
251 const pagePath = location.pathname;
252 const payloadPath = base + payload.path.slice(1);
253 if (pagePath === payloadPath ||
254 (pagePath.endsWith('/') && pagePath + 'index.html' === payloadPath)) {
255 location.reload();
256 }
257 return;
258 }
259 else {
260 location.reload();
261 }
262 break;
263 case 'prune':
264 notifyListeners('vite:beforePrune', payload);
265 // After an HMR update, some modules are no longer imported on the page
266 // but they may have left behind side effects that need to be cleaned up
267 // (.e.g style injections)
268 // TODO Trigger their dispose callbacks.
269 payload.paths.forEach((path) => {
270 const fn = pruneMap.get(path);
271 if (fn) {
272 fn(dataMap.get(path));
273 }
274 });
275 break;
276 case 'error': {
277 notifyListeners('vite:error', payload);
278 const err = payload.err;
279 if (enableOverlay) {
280 createErrorOverlay(err);
281 }
282 else {
283 console.error(`[vite] Internal Server Error\n${err.message}\n${err.stack}`);
284 }
285 break;
286 }
287 default: {
288 const check = payload;
289 return check;
290 }
291 }
292}
293function notifyListeners(event, data) {
294 const cbs = customListenersMap.get(event);
295 if (cbs) {
296 cbs.forEach((cb) => cb(data));
297 }
298}
299const enableOverlay = __HMR_ENABLE_OVERLAY__;
300function createErrorOverlay(err) {
301 if (!enableOverlay)
302 return;
303 clearErrorOverlay();
304 document.body.appendChild(new ErrorOverlay(err));
305}
306function clearErrorOverlay() {
307 document
308 .querySelectorAll(overlayId)
309 .forEach((n) => n.close());
310}
311function hasErrorOverlay() {
312 return document.querySelectorAll(overlayId).length;
313}
314let pending = false;
315let queued = [];
316/**
317 * buffer multiple hot updates triggered by the same src change
318 * so that they are invoked in the same order they were sent.
319 * (otherwise the order may be inconsistent because of the http request round trip)
320 */
321async function queueUpdate(p) {
322 queued.push(p);
323 if (!pending) {
324 pending = true;
325 await Promise.resolve();
326 pending = false;
327 const loading = [...queued];
328 queued = [];
329 (await Promise.all(loading)).forEach((fn) => fn && fn());
330 }
331}
332async function waitForSuccessfulPing(ms = 1000) {
333 // eslint-disable-next-line no-constant-condition
334 while (true) {
335 try {
336 await fetch(`${base}__vite_ping`);
337 break;
338 }
339 catch (e) {
340 await new Promise((resolve) => setTimeout(resolve, ms));
341 }
342 }
343}
344// ping server
345socket.addEventListener('close', async ({ wasClean }) => {
346 if (wasClean)
347 return;
348 console.log(`[vite] server connection lost. polling for restart...`);
349 await waitForSuccessfulPing();
350 location.reload();
351});
352const sheetsMap = new Map();
353function updateStyle(id, content) {
354 let style = sheetsMap.get(id);
355 {
356 if (style && !(style instanceof HTMLStyleElement)) {
357 removeStyle(id);
358 style = undefined;
359 }
360 if (!style) {
361 style = document.createElement('style');
362 style.setAttribute('type', 'text/css');
363 style.innerHTML = content;
364 document.head.appendChild(style);
365 }
366 else {
367 style.innerHTML = content;
368 }
369 }
370 sheetsMap.set(id, style);
371}
372function removeStyle(id) {
373 const style = sheetsMap.get(id);
374 if (style) {
375 if (style instanceof CSSStyleSheet) {
376 // @ts-ignore
377 document.adoptedStyleSheets.indexOf(style);
378 // @ts-ignore
379 document.adoptedStyleSheets = document.adoptedStyleSheets.filter((s) => s !== style);
380 }
381 else {
382 document.head.removeChild(style);
383 }
384 sheetsMap.delete(id);
385 }
386}
387async function fetchUpdate({ path, acceptedPath, timestamp }) {
388 const mod = hotModulesMap.get(path);
389 if (!mod) {
390 // In a code-splitting project,
391 // it is common that the hot-updating module is not loaded yet.
392 // https://github.com/vitejs/vite/issues/721
393 return;
394 }
395 const moduleMap = new Map();
396 const isSelfUpdate = path === acceptedPath;
397 // make sure we only import each dep once
398 const modulesToUpdate = new Set();
399 if (isSelfUpdate) {
400 // self update - only update self
401 modulesToUpdate.add(path);
402 }
403 else {
404 // dep update
405 for (const { deps } of mod.callbacks) {
406 deps.forEach((dep) => {
407 if (acceptedPath === dep) {
408 modulesToUpdate.add(dep);
409 }
410 });
411 }
412 }
413 // determine the qualified callbacks before we re-import the modules
414 const qualifiedCallbacks = mod.callbacks.filter(({ deps }) => {
415 return deps.some((dep) => modulesToUpdate.has(dep));
416 });
417 await Promise.all(Array.from(modulesToUpdate).map(async (dep) => {
418 const disposer = disposeMap.get(dep);
419 if (disposer)
420 await disposer(dataMap.get(dep));
421 const [path, query] = dep.split(`?`);
422 try {
423 const newMod = await import(
424 /* @vite-ignore */
425 base +
426 path.slice(1) +
427 `?import&t=${timestamp}${query ? `&${query}` : ''}`);
428 moduleMap.set(dep, newMod);
429 }
430 catch (e) {
431 warnFailedFetch(e, dep);
432 }
433 }));
434 return () => {
435 for (const { deps, fn } of qualifiedCallbacks) {
436 fn(deps.map((dep) => moduleMap.get(dep)));
437 }
438 const loggedPath = isSelfUpdate ? path : `${acceptedPath} via ${path}`;
439 console.log(`[vite] hot updated: ${loggedPath}`);
440 };
441}
442const hotModulesMap = new Map();
443const disposeMap = new Map();
444const pruneMap = new Map();
445const dataMap = new Map();
446const customListenersMap = new Map();
447const ctxToListenersMap = new Map();
448// Just infer the return type for now
449// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
450const createHotContext = (ownerPath) => {
451 if (!dataMap.has(ownerPath)) {
452 dataMap.set(ownerPath, {});
453 }
454 // when a file is hot updated, a new context is created
455 // clear its stale callbacks
456 const mod = hotModulesMap.get(ownerPath);
457 if (mod) {
458 mod.callbacks = [];
459 }
460 // clear stale custom event listeners
461 const staleListeners = ctxToListenersMap.get(ownerPath);
462 if (staleListeners) {
463 for (const [event, staleFns] of staleListeners) {
464 const listeners = customListenersMap.get(event);
465 if (listeners) {
466 customListenersMap.set(event, listeners.filter((l) => !staleFns.includes(l)));
467 }
468 }
469 }
470 const newListeners = new Map();
471 ctxToListenersMap.set(ownerPath, newListeners);
472 function acceptDeps(deps, callback = () => { }) {
473 const mod = hotModulesMap.get(ownerPath) || {
474 id: ownerPath,
475 callbacks: []
476 };
477 mod.callbacks.push({
478 deps,
479 fn: callback
480 });
481 hotModulesMap.set(ownerPath, mod);
482 }
483 const hot = {
484 get data() {
485 return dataMap.get(ownerPath);
486 },
487 accept(deps, callback) {
488 if (typeof deps === 'function' || !deps) {
489 // self-accept: hot.accept(() => {})
490 acceptDeps([ownerPath], ([mod]) => deps && deps(mod));
491 }
492 else if (typeof deps === 'string') {
493 // explicit deps
494 acceptDeps([deps], ([mod]) => callback && callback(mod));
495 }
496 else if (Array.isArray(deps)) {
497 acceptDeps(deps, callback);
498 }
499 else {
500 throw new Error(`invalid hot.accept() usage.`);
501 }
502 },
503 acceptDeps() {
504 throw new Error(`hot.acceptDeps() is deprecated. ` +
505 `Use hot.accept() with the same signature instead.`);
506 },
507 dispose(cb) {
508 disposeMap.set(ownerPath, cb);
509 },
510 prune(cb) {
511 pruneMap.set(ownerPath, cb);
512 },
513 // TODO
514 // eslint-disable-next-line @typescript-eslint/no-empty-function
515 decline() { },
516 invalidate() {
517 // TODO should tell the server to re-perform hmr propagation
518 // from this module as root
519 location.reload();
520 },
521 // custom events
522 on: (event, cb) => {
523 const addToMap = (map) => {
524 const existing = map.get(event) || [];
525 existing.push(cb);
526 map.set(event, existing);
527 };
528 addToMap(customListenersMap);
529 addToMap(newListeners);
530 }
531 };
532 return hot;
533};
534/**
535 * urls here are dynamic import() urls that couldn't be statically analyzed
536 */
537function injectQuery(url, queryToInject) {
538 // skip urls that won't be handled by vite
539 if (!url.startsWith('.') && !url.startsWith('/')) {
540 return url;
541 }
542 // can't use pathname from URL since it may be relative like ../
543 const pathname = url.replace(/#.*$/, '').replace(/\?.*$/, '');
544 const { search, hash } = new URL(url, 'http://vitejs.dev');
545 return `${pathname}?${queryToInject}${search ? `&` + search.slice(1) : ''}${hash || ''}`;
546}
547
548export { createHotContext, injectQuery, removeStyle, updateStyle };
549//# sourceMappingURL=client.mjs.map