client.mjs 17 KB

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