client.mjs 22 KB

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