client.mjs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823
  1. import '@vite/env';
  2. class HMRContext {
  3. constructor(hmrClient, ownerPath) {
  4. this.hmrClient = hmrClient;
  5. this.ownerPath = ownerPath;
  6. if (!hmrClient.dataMap.has(ownerPath)) {
  7. hmrClient.dataMap.set(ownerPath, {});
  8. }
  9. // when a file is hot updated, a new context is created
  10. // clear its stale callbacks
  11. const mod = hmrClient.hotModulesMap.get(ownerPath);
  12. if (mod) {
  13. mod.callbacks = [];
  14. }
  15. // clear stale custom event listeners
  16. const staleListeners = hmrClient.ctxToListenersMap.get(ownerPath);
  17. if (staleListeners) {
  18. for (const [event, staleFns] of staleListeners) {
  19. const listeners = hmrClient.customListenersMap.get(event);
  20. if (listeners) {
  21. hmrClient.customListenersMap.set(event, listeners.filter((l) => !staleFns.includes(l)));
  22. }
  23. }
  24. }
  25. this.newListeners = new Map();
  26. hmrClient.ctxToListenersMap.set(ownerPath, this.newListeners);
  27. }
  28. get data() {
  29. return this.hmrClient.dataMap.get(this.ownerPath);
  30. }
  31. accept(deps, callback) {
  32. if (typeof deps === 'function' || !deps) {
  33. // self-accept: hot.accept(() => {})
  34. this.acceptDeps([this.ownerPath], ([mod]) => deps === null || deps === void 0 ? void 0 : deps(mod));
  35. }
  36. else if (typeof deps === 'string') {
  37. // explicit deps
  38. this.acceptDeps([deps], ([mod]) => callback === null || callback === void 0 ? void 0 : callback(mod));
  39. }
  40. else if (Array.isArray(deps)) {
  41. this.acceptDeps(deps, callback);
  42. }
  43. else {
  44. throw new Error(`invalid hot.accept() usage.`);
  45. }
  46. }
  47. // export names (first arg) are irrelevant on the client side, they're
  48. // extracted in the server for propagation
  49. acceptExports(_, callback) {
  50. this.acceptDeps([this.ownerPath], ([mod]) => callback === null || callback === void 0 ? void 0 : callback(mod));
  51. }
  52. dispose(cb) {
  53. this.hmrClient.disposeMap.set(this.ownerPath, cb);
  54. }
  55. prune(cb) {
  56. this.hmrClient.pruneMap.set(this.ownerPath, cb);
  57. }
  58. // Kept for backward compatibility (#11036)
  59. // eslint-disable-next-line @typescript-eslint/no-empty-function
  60. decline() { }
  61. invalidate(message) {
  62. this.hmrClient.notifyListeners('vite:invalidate', {
  63. path: this.ownerPath,
  64. message,
  65. });
  66. this.send('vite:invalidate', { path: this.ownerPath, message });
  67. this.hmrClient.logger.debug(`[vite] invalidate ${this.ownerPath}${message ? `: ${message}` : ''}`);
  68. }
  69. on(event, cb) {
  70. const addToMap = (map) => {
  71. const existing = map.get(event) || [];
  72. existing.push(cb);
  73. map.set(event, existing);
  74. };
  75. addToMap(this.hmrClient.customListenersMap);
  76. addToMap(this.newListeners);
  77. }
  78. off(event, cb) {
  79. const removeFromMap = (map) => {
  80. const existing = map.get(event);
  81. if (existing === undefined) {
  82. return;
  83. }
  84. const pruned = existing.filter((l) => l !== cb);
  85. if (pruned.length === 0) {
  86. map.delete(event);
  87. return;
  88. }
  89. map.set(event, pruned);
  90. };
  91. removeFromMap(this.hmrClient.customListenersMap);
  92. removeFromMap(this.newListeners);
  93. }
  94. send(event, data) {
  95. this.hmrClient.messenger.send(JSON.stringify({ type: 'custom', event, data }));
  96. }
  97. acceptDeps(deps, callback = () => { }) {
  98. const mod = this.hmrClient.hotModulesMap.get(this.ownerPath) || {
  99. id: this.ownerPath,
  100. callbacks: [],
  101. };
  102. mod.callbacks.push({
  103. deps,
  104. fn: callback,
  105. });
  106. this.hmrClient.hotModulesMap.set(this.ownerPath, mod);
  107. }
  108. }
  109. class HMRMessenger {
  110. constructor(connection) {
  111. this.connection = connection;
  112. this.queue = [];
  113. }
  114. send(message) {
  115. this.queue.push(message);
  116. this.flush();
  117. }
  118. flush() {
  119. if (this.connection.isReady()) {
  120. this.queue.forEach((msg) => this.connection.send(msg));
  121. this.queue = [];
  122. }
  123. }
  124. }
  125. class HMRClient {
  126. constructor(logger, connection,
  127. // This allows implementing reloading via different methods depending on the environment
  128. importUpdatedModule) {
  129. this.logger = logger;
  130. this.importUpdatedModule = importUpdatedModule;
  131. this.hotModulesMap = new Map();
  132. this.disposeMap = new Map();
  133. this.pruneMap = new Map();
  134. this.dataMap = new Map();
  135. this.customListenersMap = new Map();
  136. this.ctxToListenersMap = new Map();
  137. this.updateQueue = [];
  138. this.pendingUpdateQueue = false;
  139. this.messenger = new HMRMessenger(connection);
  140. }
  141. async notifyListeners(event, data) {
  142. const cbs = this.customListenersMap.get(event);
  143. if (cbs) {
  144. await Promise.allSettled(cbs.map((cb) => cb(data)));
  145. }
  146. }
  147. clear() {
  148. this.hotModulesMap.clear();
  149. this.disposeMap.clear();
  150. this.pruneMap.clear();
  151. this.dataMap.clear();
  152. this.customListenersMap.clear();
  153. this.ctxToListenersMap.clear();
  154. }
  155. // After an HMR update, some modules are no longer imported on the page
  156. // but they may have left behind side effects that need to be cleaned up
  157. // (.e.g style injections)
  158. async prunePaths(paths) {
  159. await Promise.all(paths.map((path) => {
  160. const disposer = this.disposeMap.get(path);
  161. if (disposer)
  162. return disposer(this.dataMap.get(path));
  163. }));
  164. paths.forEach((path) => {
  165. const fn = this.pruneMap.get(path);
  166. if (fn) {
  167. fn(this.dataMap.get(path));
  168. }
  169. });
  170. }
  171. warnFailedUpdate(err, path) {
  172. if (!err.message.includes('fetch')) {
  173. this.logger.error(err);
  174. }
  175. this.logger.error(`[hmr] Failed to reload ${path}. ` +
  176. `This could be due to syntax errors or importing non-existent ` +
  177. `modules. (see errors above)`);
  178. }
  179. /**
  180. * buffer multiple hot updates triggered by the same src change
  181. * so that they are invoked in the same order they were sent.
  182. * (otherwise the order may be inconsistent because of the http request round trip)
  183. */
  184. async queueUpdate(payload) {
  185. this.updateQueue.push(this.fetchUpdate(payload));
  186. if (!this.pendingUpdateQueue) {
  187. this.pendingUpdateQueue = true;
  188. await Promise.resolve();
  189. this.pendingUpdateQueue = false;
  190. const loading = [...this.updateQueue];
  191. this.updateQueue = [];
  192. (await Promise.all(loading)).forEach((fn) => fn && fn());
  193. }
  194. }
  195. async fetchUpdate(update) {
  196. const { path, acceptedPath } = update;
  197. const mod = this.hotModulesMap.get(path);
  198. if (!mod) {
  199. // In a code-splitting project,
  200. // it is common that the hot-updating module is not loaded yet.
  201. // https://github.com/vitejs/vite/issues/721
  202. return;
  203. }
  204. let fetchedModule;
  205. const isSelfUpdate = path === acceptedPath;
  206. // determine the qualified callbacks before we re-import the modules
  207. const qualifiedCallbacks = mod.callbacks.filter(({ deps }) => deps.includes(acceptedPath));
  208. if (isSelfUpdate || qualifiedCallbacks.length > 0) {
  209. const disposer = this.disposeMap.get(acceptedPath);
  210. if (disposer)
  211. await disposer(this.dataMap.get(acceptedPath));
  212. try {
  213. fetchedModule = await this.importUpdatedModule(update);
  214. }
  215. catch (e) {
  216. this.warnFailedUpdate(e, acceptedPath);
  217. }
  218. }
  219. return () => {
  220. for (const { deps, fn } of qualifiedCallbacks) {
  221. fn(deps.map((dep) => (dep === acceptedPath ? fetchedModule : undefined)));
  222. }
  223. const loggedPath = isSelfUpdate ? path : `${acceptedPath} via ${path}`;
  224. this.logger.debug(`[vite] hot updated: ${loggedPath}`);
  225. };
  226. }
  227. }
  228. const hmrConfigName = __HMR_CONFIG_NAME__;
  229. const base$1 = __BASE__ || '/';
  230. // Create an element with provided attributes and optional children
  231. function h(e, attrs = {}, ...children) {
  232. const elem = document.createElement(e);
  233. for (const [k, v] of Object.entries(attrs)) {
  234. elem.setAttribute(k, v);
  235. }
  236. elem.append(...children);
  237. return elem;
  238. }
  239. // set :host styles to make playwright detect the element as visible
  240. const templateStyle = /*css*/ `
  241. :host {
  242. position: fixed;
  243. top: 0;
  244. left: 0;
  245. width: 100%;
  246. height: 100%;
  247. z-index: 99999;
  248. --monospace: 'SFMono-Regular', Consolas,
  249. 'Liberation Mono', Menlo, Courier, monospace;
  250. --red: #ff5555;
  251. --yellow: #e2aa53;
  252. --purple: #cfa4ff;
  253. --cyan: #2dd9da;
  254. --dim: #c9c9c9;
  255. --window-background: #181818;
  256. --window-color: #d8d8d8;
  257. }
  258. .backdrop {
  259. position: fixed;
  260. z-index: 99999;
  261. top: 0;
  262. left: 0;
  263. width: 100%;
  264. height: 100%;
  265. overflow-y: scroll;
  266. margin: 0;
  267. background: rgba(0, 0, 0, 0.66);
  268. }
  269. .window {
  270. font-family: var(--monospace);
  271. line-height: 1.5;
  272. max-width: 80vw;
  273. color: var(--window-color);
  274. box-sizing: border-box;
  275. margin: 30px auto;
  276. padding: 2.5vh 4vw;
  277. position: relative;
  278. background: var(--window-background);
  279. border-radius: 6px 6px 8px 8px;
  280. box-shadow: 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22);
  281. overflow: hidden;
  282. border-top: 8px solid var(--red);
  283. direction: ltr;
  284. text-align: left;
  285. }
  286. pre {
  287. font-family: var(--monospace);
  288. font-size: 16px;
  289. margin-top: 0;
  290. margin-bottom: 1em;
  291. overflow-x: scroll;
  292. scrollbar-width: none;
  293. }
  294. pre::-webkit-scrollbar {
  295. display: none;
  296. }
  297. pre.frame::-webkit-scrollbar {
  298. display: block;
  299. height: 5px;
  300. }
  301. pre.frame::-webkit-scrollbar-thumb {
  302. background: #999;
  303. border-radius: 5px;
  304. }
  305. pre.frame {
  306. scrollbar-width: thin;
  307. }
  308. .message {
  309. line-height: 1.3;
  310. font-weight: 600;
  311. white-space: pre-wrap;
  312. }
  313. .message-body {
  314. color: var(--red);
  315. }
  316. .plugin {
  317. color: var(--purple);
  318. }
  319. .file {
  320. color: var(--cyan);
  321. margin-bottom: 0;
  322. white-space: pre-wrap;
  323. word-break: break-all;
  324. }
  325. .frame {
  326. color: var(--yellow);
  327. }
  328. .stack {
  329. font-size: 13px;
  330. color: var(--dim);
  331. }
  332. .tip {
  333. font-size: 13px;
  334. color: #999;
  335. border-top: 1px dotted #999;
  336. padding-top: 13px;
  337. line-height: 1.8;
  338. }
  339. code {
  340. font-size: 13px;
  341. font-family: var(--monospace);
  342. color: var(--yellow);
  343. }
  344. .file-link {
  345. text-decoration: underline;
  346. cursor: pointer;
  347. }
  348. kbd {
  349. line-height: 1.5;
  350. font-family: ui-monospace, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
  351. font-size: 0.75rem;
  352. font-weight: 700;
  353. background-color: rgb(38, 40, 44);
  354. color: rgb(166, 167, 171);
  355. padding: 0.15rem 0.3rem;
  356. border-radius: 0.25rem;
  357. border-width: 0.0625rem 0.0625rem 0.1875rem;
  358. border-style: solid;
  359. border-color: rgb(54, 57, 64);
  360. border-image: initial;
  361. }
  362. `;
  363. // Error Template
  364. const createTemplate = () => h('div', { class: 'backdrop', part: 'backdrop' }, h('div', { class: 'window', part: 'window' }, h('pre', { class: 'message', part: 'message' }, h('span', { class: 'plugin', part: 'plugin' }), h('span', { class: 'message-body', part: 'message-body' })), h('pre', { class: 'file', part: 'file' }), h('pre', { class: 'frame', part: 'frame' }), h('pre', { class: 'stack', part: 'stack' }), h('div', { class: 'tip', part: 'tip' }, 'Click outside, press ', h('kbd', {}, 'Esc'), ' key, or fix the code to dismiss.', h('br'), 'You can also disable this overlay by setting ', h('code', { part: 'config-option-name' }, 'server.hmr.overlay'), ' to ', h('code', { part: 'config-option-value' }, 'false'), ' in ', h('code', { part: 'config-file-name' }, hmrConfigName), '.')), h('style', {}, templateStyle));
  365. const fileRE = /(?:[a-zA-Z]:\\|\/).*?:\d+:\d+/g;
  366. const codeframeRE = /^(?:>?\s*\d+\s+\|.*|\s+\|\s*\^.*)\r?\n/gm;
  367. // Allow `ErrorOverlay` to extend `HTMLElement` even in environments where
  368. // `HTMLElement` was not originally defined.
  369. const { HTMLElement = class {
  370. } } = globalThis;
  371. class ErrorOverlay extends HTMLElement {
  372. constructor(err, links = true) {
  373. var _a;
  374. super();
  375. this.root = this.attachShadow({ mode: 'open' });
  376. this.root.appendChild(createTemplate());
  377. codeframeRE.lastIndex = 0;
  378. const hasFrame = err.frame && codeframeRE.test(err.frame);
  379. const message = hasFrame
  380. ? err.message.replace(codeframeRE, '')
  381. : err.message;
  382. if (err.plugin) {
  383. this.text('.plugin', `[plugin:${err.plugin}] `);
  384. }
  385. this.text('.message-body', message.trim());
  386. const [file] = (((_a = err.loc) === null || _a === void 0 ? void 0 : _a.file) || err.id || 'unknown file').split(`?`);
  387. if (err.loc) {
  388. this.text('.file', `${file}:${err.loc.line}:${err.loc.column}`, links);
  389. }
  390. else if (err.id) {
  391. this.text('.file', file);
  392. }
  393. if (hasFrame) {
  394. this.text('.frame', err.frame.trim());
  395. }
  396. this.text('.stack', err.stack, links);
  397. this.root.querySelector('.window').addEventListener('click', (e) => {
  398. e.stopPropagation();
  399. });
  400. this.addEventListener('click', () => {
  401. this.close();
  402. });
  403. this.closeOnEsc = (e) => {
  404. if (e.key === 'Escape' || e.code === 'Escape') {
  405. this.close();
  406. }
  407. };
  408. document.addEventListener('keydown', this.closeOnEsc);
  409. }
  410. text(selector, text, linkFiles = false) {
  411. const el = this.root.querySelector(selector);
  412. if (!linkFiles) {
  413. el.textContent = text;
  414. }
  415. else {
  416. let curIndex = 0;
  417. let match;
  418. fileRE.lastIndex = 0;
  419. while ((match = fileRE.exec(text))) {
  420. const { 0: file, index } = match;
  421. if (index != null) {
  422. const frag = text.slice(curIndex, index);
  423. el.appendChild(document.createTextNode(frag));
  424. const link = document.createElement('a');
  425. link.textContent = file;
  426. link.className = 'file-link';
  427. link.onclick = () => {
  428. fetch(new URL(`${base$1}__open-in-editor?file=${encodeURIComponent(file)}`, import.meta.url));
  429. };
  430. el.appendChild(link);
  431. curIndex += frag.length + file.length;
  432. }
  433. }
  434. }
  435. }
  436. close() {
  437. var _a;
  438. (_a = this.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(this);
  439. document.removeEventListener('keydown', this.closeOnEsc);
  440. }
  441. }
  442. const overlayId = 'vite-error-overlay';
  443. const { customElements } = globalThis; // Ensure `customElements` is defined before the next line.
  444. if (customElements && !customElements.get(overlayId)) {
  445. customElements.define(overlayId, ErrorOverlay);
  446. }
  447. var _a;
  448. console.debug('[vite] connecting...');
  449. const importMetaUrl = new URL(import.meta.url);
  450. // use server configuration, then fallback to inference
  451. const serverHost = __SERVER_HOST__;
  452. const socketProtocol = __HMR_PROTOCOL__ || (importMetaUrl.protocol === 'https:' ? 'wss' : 'ws');
  453. const hmrPort = __HMR_PORT__;
  454. const socketHost = `${__HMR_HOSTNAME__ || importMetaUrl.hostname}:${hmrPort || importMetaUrl.port}${__HMR_BASE__}`;
  455. const directSocketHost = __HMR_DIRECT_TARGET__;
  456. const base = __BASE__ || '/';
  457. let socket;
  458. try {
  459. let fallback;
  460. // only use fallback when port is inferred to prevent confusion
  461. if (!hmrPort) {
  462. fallback = () => {
  463. // fallback to connecting directly to the hmr server
  464. // for servers which does not support proxying websocket
  465. socket = setupWebSocket(socketProtocol, directSocketHost, () => {
  466. const currentScriptHostURL = new URL(import.meta.url);
  467. const currentScriptHost = currentScriptHostURL.host +
  468. currentScriptHostURL.pathname.replace(/@vite\/client$/, '');
  469. console.error('[vite] failed to connect to websocket.\n' +
  470. 'your current setup:\n' +
  471. ` (browser) ${currentScriptHost} <--[HTTP]--> ${serverHost} (server)\n` +
  472. ` (browser) ${socketHost} <--[WebSocket (failing)]--> ${directSocketHost} (server)\n` +
  473. 'Check out your Vite / network configuration and https://vitejs.dev/config/server-options.html#server-hmr .');
  474. });
  475. socket.addEventListener('open', () => {
  476. console.info('[vite] Direct websocket connection fallback. Check out https://vitejs.dev/config/server-options.html#server-hmr to remove the previous connection error.');
  477. }, { once: true });
  478. };
  479. }
  480. socket = setupWebSocket(socketProtocol, socketHost, fallback);
  481. }
  482. catch (error) {
  483. console.error(`[vite] failed to connect to websocket (${error}). `);
  484. }
  485. function setupWebSocket(protocol, hostAndPath, onCloseWithoutOpen) {
  486. const socket = new WebSocket(`${protocol}://${hostAndPath}`, 'vite-hmr');
  487. let isOpened = false;
  488. socket.addEventListener('open', () => {
  489. isOpened = true;
  490. notifyListeners('vite:ws:connect', { webSocket: socket });
  491. }, { once: true });
  492. // Listen for messages
  493. socket.addEventListener('message', async ({ data }) => {
  494. handleMessage(JSON.parse(data));
  495. });
  496. // ping server
  497. socket.addEventListener('close', async ({ wasClean }) => {
  498. if (wasClean)
  499. return;
  500. if (!isOpened && onCloseWithoutOpen) {
  501. onCloseWithoutOpen();
  502. return;
  503. }
  504. notifyListeners('vite:ws:disconnect', { webSocket: socket });
  505. if (hasDocument) {
  506. console.log(`[vite] server connection lost. polling for restart...`);
  507. await waitForSuccessfulPing(protocol, hostAndPath);
  508. location.reload();
  509. }
  510. });
  511. return socket;
  512. }
  513. function cleanUrl(pathname) {
  514. const url = new URL(pathname, 'http://vitejs.dev');
  515. url.searchParams.delete('direct');
  516. return url.pathname + url.search;
  517. }
  518. let isFirstUpdate = true;
  519. const outdatedLinkTags = new WeakSet();
  520. const debounceReload = (time) => {
  521. let timer;
  522. return () => {
  523. if (timer) {
  524. clearTimeout(timer);
  525. timer = null;
  526. }
  527. timer = setTimeout(() => {
  528. location.reload();
  529. }, time);
  530. };
  531. };
  532. const pageReload = debounceReload(50);
  533. const hmrClient = new HMRClient(console, {
  534. isReady: () => socket && socket.readyState === 1,
  535. send: (message) => socket.send(message),
  536. }, async function importUpdatedModule({ acceptedPath, timestamp, explicitImportRequired, isWithinCircularImport, }) {
  537. const [acceptedPathWithoutQuery, query] = acceptedPath.split(`?`);
  538. const importPromise = import(
  539. /* @vite-ignore */
  540. base +
  541. acceptedPathWithoutQuery.slice(1) +
  542. `?${explicitImportRequired ? 'import&' : ''}t=${timestamp}${query ? `&${query}` : ''}`);
  543. if (isWithinCircularImport) {
  544. importPromise.catch(() => {
  545. console.info(`[hmr] ${acceptedPath} failed to apply HMR as it's within a circular import. Reloading page to reset the execution order. ` +
  546. `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.`);
  547. pageReload();
  548. });
  549. }
  550. return await importPromise;
  551. });
  552. async function handleMessage(payload) {
  553. switch (payload.type) {
  554. case 'connected':
  555. console.debug(`[vite] connected.`);
  556. hmrClient.messenger.flush();
  557. // proxy(nginx, docker) hmr ws maybe caused timeout,
  558. // so send ping package let ws keep alive.
  559. setInterval(() => {
  560. if (socket.readyState === socket.OPEN) {
  561. socket.send('{"type":"ping"}');
  562. }
  563. }, __HMR_TIMEOUT__);
  564. break;
  565. case 'update':
  566. notifyListeners('vite:beforeUpdate', payload);
  567. if (hasDocument) {
  568. // if this is the first update and there's already an error overlay, it
  569. // means the page opened with existing server compile error and the whole
  570. // module script failed to load (since one of the nested imports is 500).
  571. // in this case a normal update won't work and a full reload is needed.
  572. if (isFirstUpdate && hasErrorOverlay()) {
  573. window.location.reload();
  574. return;
  575. }
  576. else {
  577. if (enableOverlay) {
  578. clearErrorOverlay();
  579. }
  580. isFirstUpdate = false;
  581. }
  582. }
  583. await Promise.all(payload.updates.map(async (update) => {
  584. if (update.type === 'js-update') {
  585. return hmrClient.queueUpdate(update);
  586. }
  587. // css-update
  588. // this is only sent when a css file referenced with <link> is updated
  589. const { path, timestamp } = update;
  590. const searchUrl = cleanUrl(path);
  591. // can't use querySelector with `[href*=]` here since the link may be
  592. // using relative paths so we need to use link.href to grab the full
  593. // URL for the include check.
  594. const el = Array.from(document.querySelectorAll('link')).find((e) => !outdatedLinkTags.has(e) && cleanUrl(e.href).includes(searchUrl));
  595. if (!el) {
  596. return;
  597. }
  598. const newPath = `${base}${searchUrl.slice(1)}${searchUrl.includes('?') ? '&' : '?'}t=${timestamp}`;
  599. // rather than swapping the href on the existing tag, we will
  600. // create a new link tag. Once the new stylesheet has loaded we
  601. // will remove the existing link tag. This removes a Flash Of
  602. // Unstyled Content that can occur when swapping out the tag href
  603. // directly, as the new stylesheet has not yet been loaded.
  604. return new Promise((resolve) => {
  605. const newLinkTag = el.cloneNode();
  606. newLinkTag.href = new URL(newPath, el.href).href;
  607. const removeOldEl = () => {
  608. el.remove();
  609. console.debug(`[vite] css hot updated: ${searchUrl}`);
  610. resolve();
  611. };
  612. newLinkTag.addEventListener('load', removeOldEl);
  613. newLinkTag.addEventListener('error', removeOldEl);
  614. outdatedLinkTags.add(el);
  615. el.after(newLinkTag);
  616. });
  617. }));
  618. notifyListeners('vite:afterUpdate', payload);
  619. break;
  620. case 'custom': {
  621. notifyListeners(payload.event, payload.data);
  622. break;
  623. }
  624. case 'full-reload':
  625. notifyListeners('vite:beforeFullReload', payload);
  626. if (hasDocument) {
  627. if (payload.path && payload.path.endsWith('.html')) {
  628. // if html file is edited, only reload the page if the browser is
  629. // currently on that page.
  630. const pagePath = decodeURI(location.pathname);
  631. const payloadPath = base + payload.path.slice(1);
  632. if (pagePath === payloadPath ||
  633. payload.path === '/index.html' ||
  634. (pagePath.endsWith('/') && pagePath + 'index.html' === payloadPath)) {
  635. pageReload();
  636. }
  637. return;
  638. }
  639. else {
  640. pageReload();
  641. }
  642. }
  643. break;
  644. case 'prune':
  645. notifyListeners('vite:beforePrune', payload);
  646. await hmrClient.prunePaths(payload.paths);
  647. break;
  648. case 'error': {
  649. notifyListeners('vite:error', payload);
  650. if (hasDocument) {
  651. const err = payload.err;
  652. if (enableOverlay) {
  653. createErrorOverlay(err);
  654. }
  655. else {
  656. console.error(`[vite] Internal Server Error\n${err.message}\n${err.stack}`);
  657. }
  658. }
  659. break;
  660. }
  661. default: {
  662. const check = payload;
  663. return check;
  664. }
  665. }
  666. }
  667. function notifyListeners(event, data) {
  668. hmrClient.notifyListeners(event, data);
  669. }
  670. const enableOverlay = __HMR_ENABLE_OVERLAY__;
  671. const hasDocument = 'document' in globalThis;
  672. function createErrorOverlay(err) {
  673. clearErrorOverlay();
  674. document.body.appendChild(new ErrorOverlay(err));
  675. }
  676. function clearErrorOverlay() {
  677. document.querySelectorAll(overlayId).forEach((n) => n.close());
  678. }
  679. function hasErrorOverlay() {
  680. return document.querySelectorAll(overlayId).length;
  681. }
  682. async function waitForSuccessfulPing(socketProtocol, hostAndPath, ms = 1000) {
  683. const pingHostProtocol = socketProtocol === 'wss' ? 'https' : 'http';
  684. const ping = async () => {
  685. // A fetch on a websocket URL will return a successful promise with status 400,
  686. // but will reject a networking error.
  687. // When running on middleware mode, it returns status 426, and an cors error happens if mode is not no-cors
  688. try {
  689. await fetch(`${pingHostProtocol}://${hostAndPath}`, {
  690. mode: 'no-cors',
  691. headers: {
  692. // Custom headers won't be included in a request with no-cors so (ab)use one of the
  693. // safelisted headers to identify the ping request
  694. Accept: 'text/x-vite-ping',
  695. },
  696. });
  697. return true;
  698. }
  699. catch { }
  700. return false;
  701. };
  702. if (await ping()) {
  703. return;
  704. }
  705. await wait(ms);
  706. // eslint-disable-next-line no-constant-condition
  707. while (true) {
  708. if (document.visibilityState === 'visible') {
  709. if (await ping()) {
  710. break;
  711. }
  712. await wait(ms);
  713. }
  714. else {
  715. await waitForWindowShow();
  716. }
  717. }
  718. }
  719. function wait(ms) {
  720. return new Promise((resolve) => setTimeout(resolve, ms));
  721. }
  722. function waitForWindowShow() {
  723. return new Promise((resolve) => {
  724. const onChange = async () => {
  725. if (document.visibilityState === 'visible') {
  726. resolve();
  727. document.removeEventListener('visibilitychange', onChange);
  728. }
  729. };
  730. document.addEventListener('visibilitychange', onChange);
  731. });
  732. }
  733. const sheetsMap = new Map();
  734. // collect existing style elements that may have been inserted during SSR
  735. // to avoid FOUC or duplicate styles
  736. if ('document' in globalThis) {
  737. document
  738. .querySelectorAll('style[data-vite-dev-id]')
  739. .forEach((el) => {
  740. sheetsMap.set(el.getAttribute('data-vite-dev-id'), el);
  741. });
  742. }
  743. const cspNonce = 'document' in globalThis
  744. ? (_a = document.querySelector('meta[property=csp-nonce]')) === null || _a === void 0 ? void 0 : _a.nonce
  745. : undefined;
  746. // all css imports should be inserted at the same position
  747. // because after build it will be a single css file
  748. let lastInsertedStyle;
  749. function updateStyle(id, content) {
  750. let style = sheetsMap.get(id);
  751. if (!style) {
  752. style = document.createElement('style');
  753. style.setAttribute('type', 'text/css');
  754. style.setAttribute('data-vite-dev-id', id);
  755. style.textContent = content;
  756. if (cspNonce) {
  757. style.setAttribute('nonce', cspNonce);
  758. }
  759. if (!lastInsertedStyle) {
  760. document.head.appendChild(style);
  761. // reset lastInsertedStyle after async
  762. // because dynamically imported css will be splitted into a different file
  763. setTimeout(() => {
  764. lastInsertedStyle = undefined;
  765. }, 0);
  766. }
  767. else {
  768. lastInsertedStyle.insertAdjacentElement('afterend', style);
  769. }
  770. lastInsertedStyle = style;
  771. }
  772. else {
  773. style.textContent = content;
  774. }
  775. sheetsMap.set(id, style);
  776. }
  777. function removeStyle(id) {
  778. const style = sheetsMap.get(id);
  779. if (style) {
  780. document.head.removeChild(style);
  781. sheetsMap.delete(id);
  782. }
  783. }
  784. function createHotContext(ownerPath) {
  785. return new HMRContext(hmrClient, ownerPath);
  786. }
  787. /**
  788. * urls here are dynamic import() urls that couldn't be statically analyzed
  789. */
  790. function injectQuery(url, queryToInject) {
  791. // skip urls that won't be handled by vite
  792. if (url[0] !== '.' && url[0] !== '/') {
  793. return url;
  794. }
  795. // can't use pathname from URL since it may be relative like ../
  796. const pathname = url.replace(/[?#].*$/, '');
  797. const { search, hash } = new URL(url, 'http://vitejs.dev');
  798. return `${pathname}?${queryToInject}${search ? `&` + search.slice(1) : ''}${hash || ''}`;
  799. }
  800. export { ErrorOverlay, createHotContext, injectQuery, removeStyle, updateStyle };
  801. //# sourceMappingURL=client.mjs.map