import '@vite/env'; class HMRContext { constructor(hmrClient, ownerPath) { this.hmrClient = hmrClient; this.ownerPath = ownerPath; if (!hmrClient.dataMap.has(ownerPath)) { hmrClient.dataMap.set(ownerPath, {}); } // when a file is hot updated, a new context is created // clear its stale callbacks const mod = hmrClient.hotModulesMap.get(ownerPath); if (mod) { mod.callbacks = []; } // clear stale custom event listeners const staleListeners = hmrClient.ctxToListenersMap.get(ownerPath); if (staleListeners) { for (const [event, staleFns] of staleListeners) { const listeners = hmrClient.customListenersMap.get(event); if (listeners) { hmrClient.customListenersMap.set(event, listeners.filter((l) => !staleFns.includes(l))); } } } this.newListeners = new Map(); hmrClient.ctxToListenersMap.set(ownerPath, this.newListeners); } get data() { return this.hmrClient.dataMap.get(this.ownerPath); } accept(deps, callback) { if (typeof deps === 'function' || !deps) { // self-accept: hot.accept(() => {}) this.acceptDeps([this.ownerPath], ([mod]) => deps === null || deps === void 0 ? void 0 : deps(mod)); } else if (typeof deps === 'string') { // explicit deps this.acceptDeps([deps], ([mod]) => callback === null || callback === void 0 ? void 0 : callback(mod)); } else if (Array.isArray(deps)) { this.acceptDeps(deps, callback); } else { throw new Error(`invalid hot.accept() usage.`); } } // export names (first arg) are irrelevant on the client side, they're // extracted in the server for propagation acceptExports(_, callback) { this.acceptDeps([this.ownerPath], ([mod]) => callback === null || callback === void 0 ? void 0 : callback(mod)); } dispose(cb) { this.hmrClient.disposeMap.set(this.ownerPath, cb); } prune(cb) { this.hmrClient.pruneMap.set(this.ownerPath, cb); } // Kept for backward compatibility (#11036) // eslint-disable-next-line @typescript-eslint/no-empty-function decline() { } invalidate(message) { this.hmrClient.notifyListeners('vite:invalidate', { path: this.ownerPath, message, }); this.send('vite:invalidate', { path: this.ownerPath, message }); this.hmrClient.logger.debug(`[vite] invalidate ${this.ownerPath}${message ? `: ${message}` : ''}`); } on(event, cb) { const addToMap = (map) => { const existing = map.get(event) || []; existing.push(cb); map.set(event, existing); }; addToMap(this.hmrClient.customListenersMap); addToMap(this.newListeners); } off(event, cb) { const removeFromMap = (map) => { const existing = map.get(event); if (existing === undefined) { return; } const pruned = existing.filter((l) => l !== cb); if (pruned.length === 0) { map.delete(event); return; } map.set(event, pruned); }; removeFromMap(this.hmrClient.customListenersMap); removeFromMap(this.newListeners); } send(event, data) { this.hmrClient.messenger.send(JSON.stringify({ type: 'custom', event, data })); } acceptDeps(deps, callback = () => { }) { const mod = this.hmrClient.hotModulesMap.get(this.ownerPath) || { id: this.ownerPath, callbacks: [], }; mod.callbacks.push({ deps, fn: callback, }); this.hmrClient.hotModulesMap.set(this.ownerPath, mod); } } class HMRMessenger { constructor(connection) { this.connection = connection; this.queue = []; } send(message) { this.queue.push(message); this.flush(); } flush() { if (this.connection.isReady()) { this.queue.forEach((msg) => this.connection.send(msg)); this.queue = []; } } } class HMRClient { constructor(logger, connection, // This allows implementing reloading via different methods depending on the environment importUpdatedModule) { this.logger = logger; this.importUpdatedModule = importUpdatedModule; this.hotModulesMap = new Map(); this.disposeMap = new Map(); this.pruneMap = new Map(); this.dataMap = new Map(); this.customListenersMap = new Map(); this.ctxToListenersMap = new Map(); this.updateQueue = []; this.pendingUpdateQueue = false; this.messenger = new HMRMessenger(connection); } async notifyListeners(event, data) { const cbs = this.customListenersMap.get(event); if (cbs) { await Promise.allSettled(cbs.map((cb) => cb(data))); } } clear() { this.hotModulesMap.clear(); this.disposeMap.clear(); this.pruneMap.clear(); this.dataMap.clear(); this.customListenersMap.clear(); this.ctxToListenersMap.clear(); } // After an HMR update, some modules are no longer imported on the page // but they may have left behind side effects that need to be cleaned up // (.e.g style injections) // TODO Trigger their dispose callbacks. prunePaths(paths) { paths.forEach((path) => { const fn = this.pruneMap.get(path); if (fn) { fn(this.dataMap.get(path)); } }); } warnFailedUpdate(err, path) { if (!err.message.includes('fetch')) { this.logger.error(err); } this.logger.error(`[hmr] Failed to reload ${path}. ` + `This could be due to syntax errors or importing non-existent ` + `modules. (see errors above)`); } /** * buffer multiple hot updates triggered by the same src change * so that they are invoked in the same order they were sent. * (otherwise the order may be inconsistent because of the http request round trip) */ async queueUpdate(payload) { this.updateQueue.push(this.fetchUpdate(payload)); if (!this.pendingUpdateQueue) { this.pendingUpdateQueue = true; await Promise.resolve(); this.pendingUpdateQueue = false; const loading = [...this.updateQueue]; this.updateQueue = []; (await Promise.all(loading)).forEach((fn) => fn && fn()); } } async fetchUpdate(update) { const { path, acceptedPath } = update; const mod = this.hotModulesMap.get(path); if (!mod) { // In a code-splitting project, // it is common that the hot-updating module is not loaded yet. // https://github.com/vitejs/vite/issues/721 return; } let fetchedModule; const isSelfUpdate = path === acceptedPath; // determine the qualified callbacks before we re-import the modules const qualifiedCallbacks = mod.callbacks.filter(({ deps }) => deps.includes(acceptedPath)); if (isSelfUpdate || qualifiedCallbacks.length > 0) { const disposer = this.disposeMap.get(acceptedPath); if (disposer) await disposer(this.dataMap.get(acceptedPath)); try { fetchedModule = await this.importUpdatedModule(update); } catch (e) { this.warnFailedUpdate(e, acceptedPath); } } return () => { for (const { deps, fn } of qualifiedCallbacks) { fn(deps.map((dep) => (dep === acceptedPath ? fetchedModule : undefined))); } const loggedPath = isSelfUpdate ? path : `${acceptedPath} via ${path}`; this.logger.debug(`[vite] hot updated: ${loggedPath}`); }; } } const hmrConfigName = __HMR_CONFIG_NAME__; const base$1 = __BASE__ || '/'; // set :host styles to make playwright detect the element as visible const template = /*html*/ `

    

    

    
Click outside, press Esc key, or fix the code to dismiss.
You can also disable this overlay by setting server.hmr.overlay to false in ${hmrConfigName}.
`; const fileRE = /(?:[a-zA-Z]:\\|\/).*?:\d+:\d+/g; const codeframeRE = /^(?:>?\s*\d+\s+\|.*|\s+\|\s*\^.*)\r?\n/gm; // Allow `ErrorOverlay` to extend `HTMLElement` even in environments where // `HTMLElement` was not originally defined. const { HTMLElement = class { } } = globalThis; class ErrorOverlay extends HTMLElement { constructor(err, links = true) { var _a; super(); this.root = this.attachShadow({ mode: 'open' }); this.root.innerHTML = template; codeframeRE.lastIndex = 0; const hasFrame = err.frame && codeframeRE.test(err.frame); const message = hasFrame ? err.message.replace(codeframeRE, '') : err.message; if (err.plugin) { this.text('.plugin', `[plugin:${err.plugin}] `); } this.text('.message-body', message.trim()); const [file] = (((_a = err.loc) === null || _a === void 0 ? void 0 : _a.file) || err.id || 'unknown file').split(`?`); if (err.loc) { this.text('.file', `${file}:${err.loc.line}:${err.loc.column}`, links); } else if (err.id) { this.text('.file', file); } if (hasFrame) { this.text('.frame', err.frame.trim()); } this.text('.stack', err.stack, links); this.root.querySelector('.window').addEventListener('click', (e) => { e.stopPropagation(); }); this.addEventListener('click', () => { this.close(); }); this.closeOnEsc = (e) => { if (e.key === 'Escape' || e.code === 'Escape') { this.close(); } }; document.addEventListener('keydown', this.closeOnEsc); } text(selector, text, linkFiles = false) { const el = this.root.querySelector(selector); if (!linkFiles) { el.textContent = text; } else { let curIndex = 0; let match; fileRE.lastIndex = 0; while ((match = fileRE.exec(text))) { const { 0: file, index } = match; if (index != null) { const frag = text.slice(curIndex, index); el.appendChild(document.createTextNode(frag)); const link = document.createElement('a'); link.textContent = file; link.className = 'file-link'; link.onclick = () => { fetch(new URL(`${base$1}__open-in-editor?file=${encodeURIComponent(file)}`, import.meta.url)); }; el.appendChild(link); curIndex += frag.length + file.length; } } } } close() { var _a; (_a = this.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(this); document.removeEventListener('keydown', this.closeOnEsc); } } const overlayId = 'vite-error-overlay'; const { customElements } = globalThis; // Ensure `customElements` is defined before the next line. if (customElements && !customElements.get(overlayId)) { customElements.define(overlayId, ErrorOverlay); } console.debug('[vite] connecting...'); const importMetaUrl = new URL(import.meta.url); // use server configuration, then fallback to inference const serverHost = __SERVER_HOST__; const socketProtocol = __HMR_PROTOCOL__ || (importMetaUrl.protocol === 'https:' ? 'wss' : 'ws'); const hmrPort = __HMR_PORT__; const socketHost = `${__HMR_HOSTNAME__ || importMetaUrl.hostname}:${hmrPort || importMetaUrl.port}${__HMR_BASE__}`; const directSocketHost = __HMR_DIRECT_TARGET__; const base = __BASE__ || '/'; let socket; try { let fallback; // only use fallback when port is inferred to prevent confusion if (!hmrPort) { fallback = () => { // fallback to connecting directly to the hmr server // for servers which does not support proxying websocket socket = setupWebSocket(socketProtocol, directSocketHost, () => { const currentScriptHostURL = new URL(import.meta.url); const currentScriptHost = currentScriptHostURL.host + currentScriptHostURL.pathname.replace(/@vite\/client$/, ''); console.error('[vite] failed to connect to websocket.\n' + 'your current setup:\n' + ` (browser) ${currentScriptHost} <--[HTTP]--> ${serverHost} (server)\n` + ` (browser) ${socketHost} <--[WebSocket (failing)]--> ${directSocketHost} (server)\n` + 'Check out your Vite / network configuration and https://vitejs.dev/config/server-options.html#server-hmr .'); }); socket.addEventListener('open', () => { console.info('[vite] Direct websocket connection fallback. Check out https://vitejs.dev/config/server-options.html#server-hmr to remove the previous connection error.'); }, { once: true }); }; } socket = setupWebSocket(socketProtocol, socketHost, fallback); } catch (error) { console.error(`[vite] failed to connect to websocket (${error}). `); } function setupWebSocket(protocol, hostAndPath, onCloseWithoutOpen) { const socket = new WebSocket(`${protocol}://${hostAndPath}`, 'vite-hmr'); let isOpened = false; socket.addEventListener('open', () => { isOpened = true; notifyListeners('vite:ws:connect', { webSocket: socket }); }, { once: true }); // Listen for messages socket.addEventListener('message', async ({ data }) => { handleMessage(JSON.parse(data)); }); // ping server socket.addEventListener('close', async ({ wasClean }) => { if (wasClean) return; if (!isOpened && onCloseWithoutOpen) { onCloseWithoutOpen(); return; } notifyListeners('vite:ws:disconnect', { webSocket: socket }); console.log(`[vite] server connection lost. polling for restart...`); await waitForSuccessfulPing(protocol, hostAndPath); location.reload(); }); return socket; } function cleanUrl(pathname) { const url = new URL(pathname, location.toString()); url.searchParams.delete('direct'); return url.pathname + url.search; } let isFirstUpdate = true; const outdatedLinkTags = new WeakSet(); const debounceReload = (time) => { let timer; return () => { if (timer) { clearTimeout(timer); timer = null; } timer = setTimeout(() => { location.reload(); }, time); }; }; const pageReload = debounceReload(50); const hmrClient = new HMRClient(console, { isReady: () => socket && socket.readyState === 1, send: (message) => socket.send(message), }, async function importUpdatedModule({ acceptedPath, timestamp, explicitImportRequired, isWithinCircularImport, }) { const [acceptedPathWithoutQuery, query] = acceptedPath.split(`?`); const importPromise = import( /* @vite-ignore */ base + acceptedPathWithoutQuery.slice(1) + `?${explicitImportRequired ? 'import&' : ''}t=${timestamp}${query ? `&${query}` : ''}`); if (isWithinCircularImport) { importPromise.catch(() => { console.info(`[hmr] ${acceptedPath} failed to apply HMR as it's within a circular import. Reloading page to reset the execution order. ` + `To debug and break the circular import, you can run \`vite --debug hmr\` to log the circular dependency path if a file change triggered it.`); pageReload(); }); } return await importPromise; }); async function handleMessage(payload) { switch (payload.type) { case 'connected': console.debug(`[vite] connected.`); hmrClient.messenger.flush(); // proxy(nginx, docker) hmr ws maybe caused timeout, // so send ping package let ws keep alive. setInterval(() => { if (socket.readyState === socket.OPEN) { socket.send('{"type":"ping"}'); } }, __HMR_TIMEOUT__); break; case 'update': notifyListeners('vite:beforeUpdate', payload); // if this is the first update and there's already an error overlay, it // means the page opened with existing server compile error and the whole // module script failed to load (since one of the nested imports is 500). // in this case a normal update won't work and a full reload is needed. if (isFirstUpdate && hasErrorOverlay()) { window.location.reload(); return; } else { clearErrorOverlay(); isFirstUpdate = false; } await Promise.all(payload.updates.map(async (update) => { if (update.type === 'js-update') { return hmrClient.queueUpdate(update); } // css-update // this is only sent when a css file referenced with is updated const { path, timestamp } = update; const searchUrl = cleanUrl(path); // can't use querySelector with `[href*=]` here since the link may be // using relative paths so we need to use link.href to grab the full // URL for the include check. const el = Array.from(document.querySelectorAll('link')).find((e) => !outdatedLinkTags.has(e) && cleanUrl(e.href).includes(searchUrl)); if (!el) { return; } const newPath = `${base}${searchUrl.slice(1)}${searchUrl.includes('?') ? '&' : '?'}t=${timestamp}`; // rather than swapping the href on the existing tag, we will // create a new link tag. Once the new stylesheet has loaded we // will remove the existing link tag. This removes a Flash Of // Unstyled Content that can occur when swapping out the tag href // directly, as the new stylesheet has not yet been loaded. return new Promise((resolve) => { const newLinkTag = el.cloneNode(); newLinkTag.href = new URL(newPath, el.href).href; const removeOldEl = () => { el.remove(); console.debug(`[vite] css hot updated: ${searchUrl}`); resolve(); }; newLinkTag.addEventListener('load', removeOldEl); newLinkTag.addEventListener('error', removeOldEl); outdatedLinkTags.add(el); el.after(newLinkTag); }); })); notifyListeners('vite:afterUpdate', payload); break; case 'custom': { notifyListeners(payload.event, payload.data); break; } case 'full-reload': notifyListeners('vite:beforeFullReload', payload); if (payload.path && payload.path.endsWith('.html')) { // if html file is edited, only reload the page if the browser is // currently on that page. const pagePath = decodeURI(location.pathname); const payloadPath = base + payload.path.slice(1); if (pagePath === payloadPath || payload.path === '/index.html' || (pagePath.endsWith('/') && pagePath + 'index.html' === payloadPath)) { pageReload(); } return; } else { pageReload(); } break; case 'prune': notifyListeners('vite:beforePrune', payload); hmrClient.prunePaths(payload.paths); break; case 'error': { notifyListeners('vite:error', payload); const err = payload.err; if (enableOverlay) { createErrorOverlay(err); } else { console.error(`[vite] Internal Server Error\n${err.message}\n${err.stack}`); } break; } default: { const check = payload; return check; } } } function notifyListeners(event, data) { hmrClient.notifyListeners(event, data); } const enableOverlay = __HMR_ENABLE_OVERLAY__; function createErrorOverlay(err) { clearErrorOverlay(); document.body.appendChild(new ErrorOverlay(err)); } function clearErrorOverlay() { document.querySelectorAll(overlayId).forEach((n) => n.close()); } function hasErrorOverlay() { return document.querySelectorAll(overlayId).length; } async function waitForSuccessfulPing(socketProtocol, hostAndPath, ms = 1000) { const pingHostProtocol = socketProtocol === 'wss' ? 'https' : 'http'; const ping = async () => { // A fetch on a websocket URL will return a successful promise with status 400, // but will reject a networking error. // When running on middleware mode, it returns status 426, and an cors error happens if mode is not no-cors try { await fetch(`${pingHostProtocol}://${hostAndPath}`, { mode: 'no-cors', headers: { // Custom headers won't be included in a request with no-cors so (ab)use one of the // safelisted headers to identify the ping request Accept: 'text/x-vite-ping', }, }); return true; } catch { } return false; }; if (await ping()) { return; } await wait(ms); // eslint-disable-next-line no-constant-condition while (true) { if (document.visibilityState === 'visible') { if (await ping()) { break; } await wait(ms); } else { await waitForWindowShow(); } } } function wait(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function waitForWindowShow() { return new Promise((resolve) => { const onChange = async () => { if (document.visibilityState === 'visible') { resolve(); document.removeEventListener('visibilitychange', onChange); } }; document.addEventListener('visibilitychange', onChange); }); } const sheetsMap = new Map(); // collect existing style elements that may have been inserted during SSR // to avoid FOUC or duplicate styles if ('document' in globalThis) { document .querySelectorAll('style[data-vite-dev-id]') .forEach((el) => { sheetsMap.set(el.getAttribute('data-vite-dev-id'), el); }); } // all css imports should be inserted at the same position // because after build it will be a single css file let lastInsertedStyle; function updateStyle(id, content) { let style = sheetsMap.get(id); if (!style) { style = document.createElement('style'); style.setAttribute('type', 'text/css'); style.setAttribute('data-vite-dev-id', id); style.textContent = content; if (!lastInsertedStyle) { document.head.appendChild(style); // reset lastInsertedStyle after async // because dynamically imported css will be splitted into a different file setTimeout(() => { lastInsertedStyle = undefined; }, 0); } else { lastInsertedStyle.insertAdjacentElement('afterend', style); } lastInsertedStyle = style; } else { style.textContent = content; } sheetsMap.set(id, style); } function removeStyle(id) { const style = sheetsMap.get(id); if (style) { document.head.removeChild(style); sheetsMap.delete(id); } } function createHotContext(ownerPath) { return new HMRContext(hmrClient, ownerPath); } /** * urls here are dynamic import() urls that couldn't be statically analyzed */ function injectQuery(url, queryToInject) { // skip urls that won't be handled by vite if (url[0] !== '.' && url[0] !== '/') { return url; } // can't use pathname from URL since it may be relative like ../ const pathname = url.replace(/[?#].*$/, ''); const { search, hash } = new URL(url, 'http://vitejs.dev'); return `${pathname}?${queryToInject}${search ? `&` + search.slice(1) : ''}${hash || ''}`; } export { ErrorOverlay, createHotContext, injectQuery, removeStyle, updateStyle }; //# sourceMappingURL=client.mjs.map