市场夺宝奇兵
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

736 lines
23 KiB

  1. import process from 'node:process';
  2. import c from 'ansis';
  3. import { debounce } from 'perfect-debounce';
  4. import sirv from 'sirv';
  5. import { createRPCServer } from 'vite-dev-rpc';
  6. import { DIR_CLIENT } from '../dirs.mjs';
  7. import fs from 'node:fs/promises';
  8. import { isAbsolute, resolve, join } from 'node:path';
  9. import { hash } from 'ohash';
  10. import { Buffer } from 'node:buffer';
  11. import { createFilter } from 'unplugin-utils';
  12. import Debug from 'debug';
  13. import { parse } from 'error-stack-parser-es';
  14. import { createServer } from 'node:http';
  15. function createEnvOrderHooks(environmentNames, { onFirst, onEach, onLast }) {
  16. const remainingEnvs = new Set(environmentNames);
  17. let ranFirst = false;
  18. let ranLast = false;
  19. return async (envName, ...args) => {
  20. if (!ranFirst) {
  21. ranFirst = true;
  22. await onFirst?.(...args);
  23. }
  24. remainingEnvs.delete(envName);
  25. await onEach?.(...args);
  26. if (!ranLast && remainingEnvs.size === 0) {
  27. ranLast = true;
  28. await onLast?.(...args);
  29. }
  30. };
  31. }
  32. function createBuildGenerator(ctx) {
  33. const {
  34. outputDir = ".vite-inspect"
  35. } = ctx.options;
  36. const targetDir = isAbsolute(outputDir) ? outputDir : resolve(process.cwd(), outputDir);
  37. const reportsDir = join(targetDir, "reports");
  38. return {
  39. getOutputDir() {
  40. return targetDir;
  41. },
  42. async setupOutputDir() {
  43. await fs.rm(targetDir, { recursive: true, force: true });
  44. await fs.mkdir(reportsDir, { recursive: true });
  45. await fs.cp(DIR_CLIENT, targetDir, { recursive: true });
  46. await Promise.all([
  47. fs.writeFile(
  48. join(targetDir, "index.html"),
  49. (await fs.readFile(join(targetDir, "index.html"), "utf-8")).replace(
  50. 'data-vite-inspect-mode="DEV"',
  51. 'data-vite-inspect-mode="BUILD"'
  52. )
  53. )
  54. ]);
  55. },
  56. async generateForEnv(pluginCtx) {
  57. const env = pluginCtx.environment;
  58. await Promise.all(
  59. [...ctx._idToInstances.values()].filter((v) => v.environments.has(env.name)).map((v) => {
  60. const e = v.environments.get(env.name);
  61. const key = `${v.id}-${env.name}`;
  62. return [key, e];
  63. }).map(async ([key, env2]) => {
  64. await fs.mkdir(join(reportsDir, key, "transforms"), { recursive: true });
  65. return await Promise.all([
  66. writeJSON(
  67. join(reportsDir, key, "modules.json"),
  68. env2.getModulesList(pluginCtx)
  69. ),
  70. writeJSON(
  71. join(reportsDir, key, "metric-plugins.json"),
  72. env2.getPluginMetrics()
  73. ),
  74. ...Object.entries(env2.data.transform).map(([id, info]) => writeJSON(
  75. join(reportsDir, key, "transforms", `${hash(id)}.json`),
  76. {
  77. resolvedId: id,
  78. transforms: info
  79. }
  80. ))
  81. ]);
  82. })
  83. );
  84. },
  85. async generateMetadata() {
  86. await writeJSON(
  87. join(reportsDir, "metadata.json"),
  88. ctx.getMetadata()
  89. );
  90. }
  91. };
  92. }
  93. function writeJSON(filename, data) {
  94. return fs.writeFile(filename, `${JSON.stringify(data, null, 2)}
  95. `);
  96. }
  97. const DUMMY_LOAD_PLUGIN_NAME = "__load__";
  98. async function openBrowser(address) {
  99. await import('open').then((r) => r.default(address, { newInstance: true })).catch(() => {
  100. });
  101. }
  102. function serializePlugin(plugin) {
  103. return JSON.parse(JSON.stringify(plugin, (key, value) => {
  104. if (typeof value === "function") {
  105. let name = value.name;
  106. if (name === "anonymous")
  107. name = "";
  108. if (name === key)
  109. name = "";
  110. if (name)
  111. return `[Function ${name}]`;
  112. return "[Function]";
  113. }
  114. if (key === "api" && value)
  115. return "[Object API]";
  116. return value;
  117. }));
  118. }
  119. function removeVersionQuery(url) {
  120. if (url.includes("v=")) {
  121. return url.replace(/&v=\w+/, "").replace(/\?v=\w+/, "?").replace(/\?$/, "");
  122. }
  123. return url;
  124. }
  125. let viteCount = 0;
  126. class InspectContext {
  127. constructor(options) {
  128. this.options = options;
  129. this.filter = createFilter(options.include, options.exclude);
  130. }
  131. _configToInstances = /* @__PURE__ */ new Map();
  132. _idToInstances = /* @__PURE__ */ new Map();
  133. filter;
  134. getMetadata() {
  135. return {
  136. instances: [...this._idToInstances.values()].map((vite) => ({
  137. root: vite.config.root,
  138. vite: vite.id,
  139. plugins: vite.config.plugins.map((i) => serializePlugin(i)),
  140. environments: [...vite.environments.keys()],
  141. environmentPlugins: Object.fromEntries(
  142. [...vite.environments.entries()].map(([name, env]) => {
  143. return [name, env.env.getTopLevelConfig().plugins.map((i) => vite.config.plugins.indexOf(i))];
  144. })
  145. )
  146. })),
  147. embedded: this.options.embedded
  148. };
  149. }
  150. getViteContext(configOrId) {
  151. if (typeof configOrId === "string") {
  152. if (!this._idToInstances.has(configOrId))
  153. throw new Error(`Can not found vite context for ${configOrId}`);
  154. return this._idToInstances.get(configOrId);
  155. }
  156. if (this._configToInstances.has(configOrId))
  157. return this._configToInstances.get(configOrId);
  158. const id = `vite${++viteCount}`;
  159. const vite = new InspectContextVite(id, this, configOrId);
  160. this._idToInstances.set(id, vite);
  161. this._configToInstances.set(configOrId, vite);
  162. return vite;
  163. }
  164. getEnvContext(env) {
  165. if (!env)
  166. return void 0;
  167. const vite = this.getViteContext(env.getTopLevelConfig());
  168. return vite.getEnvContext(env);
  169. }
  170. queryEnv(query) {
  171. const vite = this.getViteContext(query.vite);
  172. const env = vite.getEnvContext(query.env);
  173. return env;
  174. }
  175. }
  176. class InspectContextVite {
  177. constructor(id, context, config) {
  178. this.id = id;
  179. this.context = context;
  180. this.config = config;
  181. }
  182. environments = /* @__PURE__ */ new Map();
  183. data = {
  184. serverMetrics: {
  185. middleware: {}
  186. }
  187. };
  188. getEnvContext(env) {
  189. if (typeof env === "string") {
  190. if (!this.environments.has(env))
  191. throw new Error(`Can not found environment context for ${env}`);
  192. return this.environments.get(env);
  193. }
  194. if (env.getTopLevelConfig() !== this.config)
  195. throw new Error("Environment config does not match Vite config");
  196. if (!this.environments.has(env.name))
  197. this.environments.set(env.name, new InspectContextViteEnv(this.context, this, env));
  198. return this.environments.get(env.name);
  199. }
  200. }
  201. class InspectContextViteEnv {
  202. constructor(contextMain, contextVite, env) {
  203. this.contextMain = contextMain;
  204. this.contextVite = contextVite;
  205. this.env = env;
  206. }
  207. data = {
  208. transform: {},
  209. resolveId: {},
  210. transformCounter: {}
  211. };
  212. recordTransform(id, info, preTransformCode) {
  213. id = this.normalizeId(id);
  214. if (!this.data.transform[id] || !this.data.transform[id].some((tr) => tr.result)) {
  215. this.data.transform[id] = [{
  216. name: DUMMY_LOAD_PLUGIN_NAME,
  217. result: preTransformCode,
  218. start: info.start,
  219. end: info.start,
  220. sourcemaps: info.sourcemaps
  221. }];
  222. this.data.transformCounter[id] = (this.data.transformCounter[id] || 0) + 1;
  223. }
  224. this.data.transform[id].push(info);
  225. }
  226. recordLoad(id, info) {
  227. id = this.normalizeId(id);
  228. this.data.transform[id] = [info];
  229. this.data.transformCounter[id] = (this.data.transformCounter[id] || 0) + 1;
  230. }
  231. recordResolveId(id, info) {
  232. id = this.normalizeId(id);
  233. if (!this.data.resolveId[id])
  234. this.data.resolveId[id] = [];
  235. this.data.resolveId[id].push(info);
  236. }
  237. invalidate(id) {
  238. id = this.normalizeId(id);
  239. delete this.data.transform[id];
  240. }
  241. normalizeId(id) {
  242. if (this.contextMain.options.removeVersionQuery !== false)
  243. return removeVersionQuery(id);
  244. return id;
  245. }
  246. getModulesList(pluginCtx) {
  247. const moduleGraph = this.env.mode === "dev" ? this.env.moduleGraph : void 0;
  248. const getDeps = moduleGraph ? (id) => Array.from(moduleGraph.getModuleById(id)?.importedModules || []).map((i) => i.id || "").filter(Boolean) : pluginCtx ? (id) => pluginCtx.getModuleInfo(id)?.importedIds || [] : () => [];
  249. const getImporters = moduleGraph ? (id) => Array.from(moduleGraph?.getModuleById(id)?.importers || []).map((i) => i.id || "").filter(Boolean) : pluginCtx ? (id) => pluginCtx.getModuleInfo(id)?.importers || [] : () => [];
  250. function isVirtual(pluginName, transformName) {
  251. return pluginName !== DUMMY_LOAD_PLUGIN_NAME && transformName !== "vite:load-fallback" && transformName !== "vite:build-load-fallback";
  252. }
  253. const transformedIdMap = Object.values(this.data.resolveId).reduce((map, ids2) => {
  254. ids2.forEach((id) => {
  255. map[id.result] ??= [];
  256. map[id.result].push(id);
  257. });
  258. return map;
  259. }, {});
  260. const ids = new Set(Object.keys(this.data.transform).concat(Object.keys(transformedIdMap)));
  261. return Array.from(ids).sort().map((id) => {
  262. let totalTime = 0;
  263. const plugins = (this.data.transform[id] || []).filter((tr) => tr.result).map((transItem) => {
  264. const delta = transItem.end - transItem.start;
  265. totalTime += delta;
  266. return { name: transItem.name, transform: delta };
  267. }).concat(
  268. // @ts-expect-error transform is optional
  269. (transformedIdMap[id] || []).map((idItem) => {
  270. return { name: idItem.name, resolveId: idItem.end - idItem.start };
  271. })
  272. );
  273. function getSize(str) {
  274. if (!str)
  275. return 0;
  276. return Buffer.byteLength(str, "utf8");
  277. }
  278. return {
  279. id,
  280. deps: getDeps(id),
  281. importers: getImporters(id),
  282. plugins,
  283. virtual: isVirtual(plugins[0]?.name || "", this.data.transform[id]?.[0].name || ""),
  284. totalTime,
  285. invokeCount: this.data.transformCounter?.[id] || 0,
  286. sourceSize: getSize(this.data.transform[id]?.[0]?.result),
  287. distSize: getSize(this.data.transform[id]?.[this.data.transform[id].length - 1]?.result)
  288. };
  289. });
  290. }
  291. resolveId(id = "", ssr = false) {
  292. if (id.startsWith("./"))
  293. id = resolve(this.env.getTopLevelConfig().root, id).replace(/\\/g, "/");
  294. return this.resolveIdRecursive(id, ssr);
  295. }
  296. resolveIdRecursive(id, ssr = false) {
  297. const resolved = this.data.resolveId[id]?.[0]?.result;
  298. return resolved ? this.resolveIdRecursive(resolved, ssr) : id;
  299. }
  300. getPluginMetrics() {
  301. const map = {};
  302. const defaultMetricInfo = () => ({
  303. transform: { invokeCount: 0, totalTime: 0 },
  304. resolveId: { invokeCount: 0, totalTime: 0 }
  305. });
  306. this.env.getTopLevelConfig().plugins.forEach((i) => {
  307. map[i.name] = {
  308. ...defaultMetricInfo(),
  309. name: i.name,
  310. enforce: i.enforce
  311. };
  312. });
  313. Object.values(this.data.transform).forEach((transformInfos) => {
  314. transformInfos.forEach(({ name, start, end }) => {
  315. if (name === DUMMY_LOAD_PLUGIN_NAME)
  316. return;
  317. if (!map[name])
  318. map[name] = { ...defaultMetricInfo(), name };
  319. map[name].transform.totalTime += end - start;
  320. map[name].transform.invokeCount += 1;
  321. });
  322. });
  323. Object.values(this.data.resolveId).forEach((resolveIdInfos) => {
  324. resolveIdInfos.forEach(({ name, start, end }) => {
  325. if (!map[name])
  326. map[name] = { ...defaultMetricInfo(), name };
  327. map[name].resolveId.totalTime += end - start;
  328. map[name].resolveId.invokeCount += 1;
  329. });
  330. });
  331. const metrics = Object.values(map).filter(Boolean).sort((a, b) => a.name.localeCompare(b.name));
  332. return metrics;
  333. }
  334. async getModuleTransformInfo(id, clear = false) {
  335. if (clear) {
  336. this.clearId(id);
  337. try {
  338. if (this.env.mode === "dev")
  339. await this.env.transformRequest(id);
  340. } catch {
  341. }
  342. }
  343. const resolvedId = this.resolveId(id);
  344. return {
  345. resolvedId,
  346. transforms: this.data.transform[resolvedId] || []
  347. };
  348. }
  349. clearId(_id) {
  350. const id = this.resolveId(_id);
  351. if (id) {
  352. const moduleGraph = this.env.mode === "dev" ? this.env.moduleGraph : void 0;
  353. const mod = moduleGraph?.getModuleById(id);
  354. if (mod)
  355. moduleGraph?.invalidateModule(mod);
  356. this.invalidate(id);
  357. }
  358. }
  359. }
  360. const debug = Debug("vite-plugin-inspect");
  361. function hijackHook(plugin, name, wrapper) {
  362. if (!plugin[name])
  363. return;
  364. debug(`hijack plugin "${name}"`, plugin.name);
  365. let order = plugin.order || plugin.enforce || "normal";
  366. const hook = plugin[name];
  367. if ("handler" in hook) {
  368. const oldFn = hook.handler;
  369. order += `-${hook.order || hook.enforce || "normal"}`;
  370. hook.handler = function(...args) {
  371. return wrapper(oldFn, this, args, order);
  372. };
  373. } else if ("transform" in hook) {
  374. const oldFn = hook.transform;
  375. order += `-${hook.order || hook.enforce || "normal"}`;
  376. hook.transform = function(...args) {
  377. return wrapper(oldFn, this, args, order);
  378. };
  379. } else {
  380. const oldFn = hook;
  381. plugin[name] = function(...args) {
  382. return wrapper(oldFn, this, args, order);
  383. };
  384. }
  385. }
  386. const hijackedPlugins = /* @__PURE__ */ new WeakSet();
  387. function hijackPlugin(plugin, ctx) {
  388. if (hijackedPlugins.has(plugin))
  389. return;
  390. hijackedPlugins.add(plugin);
  391. hijackHook(plugin, "transform", async (fn, context, args, order) => {
  392. const code = args[0];
  393. const id = args[1];
  394. let _result;
  395. let error;
  396. const start = Date.now();
  397. try {
  398. _result = await fn.apply(context, args);
  399. } catch (_err) {
  400. error = _err;
  401. }
  402. const end = Date.now();
  403. const result = error ? "[Error]" : typeof _result === "string" ? _result : _result?.code;
  404. if (ctx.filter(id)) {
  405. const sourcemaps = typeof _result === "string" ? null : _result?.map;
  406. ctx.getEnvContext(context?.environment)?.recordTransform(id, {
  407. name: plugin.name,
  408. result,
  409. start,
  410. end,
  411. order,
  412. sourcemaps,
  413. error: error ? parseError(error) : void 0
  414. }, code);
  415. }
  416. if (error)
  417. throw error;
  418. return _result;
  419. });
  420. hijackHook(plugin, "load", async (fn, context, args) => {
  421. const id = args[0];
  422. let _result;
  423. let error;
  424. const start = Date.now();
  425. try {
  426. _result = await fn.apply(context, args);
  427. } catch (err) {
  428. error = err;
  429. }
  430. const end = Date.now();
  431. const result = error ? "[Error]" : typeof _result === "string" ? _result : _result?.code;
  432. const sourcemaps = typeof _result === "string" ? null : _result?.map;
  433. if (result) {
  434. ctx.getEnvContext(context?.environment)?.recordLoad(id, {
  435. name: plugin.name,
  436. result,
  437. start,
  438. end,
  439. sourcemaps,
  440. error: error ? parseError(error) : void 0
  441. });
  442. }
  443. if (error)
  444. throw error;
  445. return _result;
  446. });
  447. hijackHook(plugin, "resolveId", async (fn, context, args) => {
  448. const id = args[0];
  449. let _result;
  450. let error;
  451. const start = Date.now();
  452. try {
  453. _result = await fn.apply(context, args);
  454. } catch (err) {
  455. error = err;
  456. }
  457. const end = Date.now();
  458. if (!ctx.filter(id)) {
  459. if (error)
  460. throw error;
  461. return _result;
  462. }
  463. const result = error ? stringifyError(error) : typeof _result === "object" ? _result?.id : _result;
  464. if (result && result !== id) {
  465. ctx.getEnvContext(context?.environment)?.recordResolveId(id, {
  466. name: plugin.name,
  467. result,
  468. start,
  469. end,
  470. error
  471. });
  472. }
  473. if (error)
  474. throw error;
  475. return _result;
  476. });
  477. }
  478. function parseError(error) {
  479. const stack = parse(error, { allowEmpty: true });
  480. const message = error.message || String(error);
  481. return {
  482. message,
  483. stack,
  484. raw: error
  485. };
  486. }
  487. function stringifyError(err) {
  488. return String(err.stack ? err.stack : err);
  489. }
  490. function createPreviewServer(staticPath) {
  491. const server = createServer();
  492. const statics = sirv(staticPath);
  493. server.on("request", (req, res) => {
  494. statics(req, res, () => {
  495. res.statusCode = 404;
  496. res.end("File not found");
  497. });
  498. });
  499. server.listen(0, () => {
  500. const { port } = server.address();
  501. const url = `http://localhost:${port}`;
  502. console.log(` ${c.green("\u279C")} ${c.bold("Inspect Preview Started")}: ${url}`);
  503. openBrowser(url);
  504. });
  505. }
  506. function createServerRpc(ctx) {
  507. const rpc = {
  508. async getMetadata() {
  509. return ctx.getMetadata();
  510. },
  511. async getModulesList(query) {
  512. return ctx.queryEnv(query).getModulesList();
  513. },
  514. async getPluginMetrics(query) {
  515. return ctx.queryEnv(query).getPluginMetrics();
  516. },
  517. async getModuleTransformInfo(query, id, clear) {
  518. return ctx.queryEnv(query).getModuleTransformInfo(id, clear);
  519. },
  520. async resolveId(query, id) {
  521. return ctx.queryEnv(query).resolveId(id);
  522. },
  523. async getServerMetrics(query) {
  524. return ctx.getViteContext(query.vite).data.serverMetrics || {};
  525. },
  526. async onModuleUpdated() {
  527. }
  528. };
  529. return rpc;
  530. }
  531. const NAME = "vite-plugin-inspect";
  532. const isCI = !!process.env.CI;
  533. function PluginInspect(options = {}) {
  534. const {
  535. dev = true,
  536. build = false,
  537. silent = false,
  538. open: _open = false
  539. } = options;
  540. if (!dev && !build) {
  541. return {
  542. name: NAME
  543. };
  544. }
  545. const ctx = new InspectContext(options);
  546. let onBuildEnd;
  547. const timestampRE = /\bt=\d{13}&?\b/;
  548. const trailingSeparatorRE = /[?&]$/;
  549. function setupMiddlewarePerf(ctx2, middlewares) {
  550. let firstMiddlewareIndex = -1;
  551. middlewares.forEach((middleware, index) => {
  552. const { handle: originalHandle } = middleware;
  553. if (typeof originalHandle !== "function" || !originalHandle.name)
  554. return middleware;
  555. middleware.handle = function(...middlewareArgs) {
  556. let req;
  557. if (middlewareArgs.length === 4)
  558. [, req] = middlewareArgs;
  559. else
  560. [req] = middlewareArgs;
  561. const start = Date.now();
  562. const url = req.url?.replace(timestampRE, "").replace(trailingSeparatorRE, "");
  563. ctx2.data.serverMetrics.middleware[url] ??= [];
  564. if (firstMiddlewareIndex < 0)
  565. firstMiddlewareIndex = index;
  566. if (index === firstMiddlewareIndex)
  567. ctx2.data.serverMetrics.middleware[url] = [];
  568. const result = originalHandle.apply(this, middlewareArgs);
  569. Promise.resolve(result).then(() => {
  570. const total = Date.now() - start;
  571. const metrics = ctx2.data.serverMetrics.middleware[url];
  572. ctx2.data.serverMetrics.middleware[url].push({
  573. self: metrics.length ? Math.max(total - metrics[metrics.length - 1].total, 0) : total,
  574. total,
  575. name: originalHandle.name
  576. });
  577. });
  578. return result;
  579. };
  580. Object.defineProperty(middleware.handle, "name", {
  581. value: originalHandle.name,
  582. configurable: true,
  583. enumerable: true
  584. });
  585. return middleware;
  586. });
  587. }
  588. function configureServer(server) {
  589. const config = server.config;
  590. Object.values(server.environments).forEach((env) => {
  591. const envCtx = ctx.getEnvContext(env);
  592. const _invalidateModule = env.moduleGraph.invalidateModule;
  593. env.moduleGraph.invalidateModule = function(...args) {
  594. const mod = args[0];
  595. if (mod?.id)
  596. envCtx.invalidate(mod.id);
  597. return _invalidateModule.apply(this, args);
  598. };
  599. });
  600. const base = (options.base ?? server.config.base) || "/";
  601. server.middlewares.use(`${base}__inspect`, sirv(DIR_CLIENT, {
  602. single: true,
  603. dev: true
  604. }));
  605. const rpc = createServerRpc(ctx);
  606. const rpcServer = createRPCServer(
  607. "vite-plugin-inspect",
  608. server.ws,
  609. rpc
  610. );
  611. const debouncedModuleUpdated = debounce(() => {
  612. rpcServer.onModuleUpdated.asEvent();
  613. }, 100);
  614. server.middlewares.use((req, res, next) => {
  615. debouncedModuleUpdated();
  616. next();
  617. });
  618. const _print = server.printUrls;
  619. server.printUrls = () => {
  620. let host = `${config.server.https ? "https" : "http"}://localhost:${config.server.port || "80"}`;
  621. const url = server.resolvedUrls?.local[0];
  622. if (url) {
  623. try {
  624. const u = new URL(url);
  625. host = `${u.protocol}//${u.host}`;
  626. } catch (error) {
  627. config.logger.warn(`Parse resolved url failed: ${error}`);
  628. }
  629. }
  630. _print();
  631. if (!silent) {
  632. const colorUrl = (url2) => c.green(url2.replace(/:(\d+)\//, (_, port) => `:${c.bold(port)}/`));
  633. config.logger.info(` ${c.green("\u279C")} ${c.bold("Inspect")}: ${colorUrl(`${host}${base}__inspect/`)}`);
  634. }
  635. if (_open && !isCI) {
  636. setTimeout(() => {
  637. openBrowser(`${host}${base}__inspect/`);
  638. }, 500);
  639. }
  640. };
  641. return rpc;
  642. }
  643. const plugin = {
  644. name: NAME,
  645. enforce: "pre",
  646. apply(_, { command }) {
  647. if (command === "serve" && dev)
  648. return true;
  649. if (command === "build" && build)
  650. return true;
  651. return false;
  652. },
  653. configResolved(config) {
  654. config.plugins.forEach((plugin2) => hijackPlugin(plugin2, ctx));
  655. const _createResolver = config.createResolver;
  656. config.createResolver = function(...args) {
  657. const _resolver = _createResolver.apply(this, args);
  658. return async function(...args2) {
  659. const id = args2[0];
  660. const aliasOnly = args2[2];
  661. const ssr = args2[3];
  662. const start = Date.now();
  663. const result = await _resolver.apply(this, args2);
  664. const end = Date.now();
  665. if (result && result !== id) {
  666. const pluginName = aliasOnly ? "alias" : "vite:resolve (+alias)";
  667. const vite = ctx.getViteContext(config);
  668. const env = vite.getEnvContext(ssr ? "ssr" : "client");
  669. env.recordResolveId(id, { name: pluginName, result, start, end });
  670. }
  671. return result;
  672. };
  673. };
  674. if (build) {
  675. const buildGenerator = createBuildGenerator(ctx);
  676. onBuildEnd = createEnvOrderHooks(Object.keys(config.environments), {
  677. async onFirst() {
  678. await buildGenerator.setupOutputDir();
  679. },
  680. async onEach(pluginCtx) {
  681. await buildGenerator.generateForEnv(pluginCtx);
  682. },
  683. async onLast(pluginCtx) {
  684. await buildGenerator.generateMetadata();
  685. const dir = buildGenerator.getOutputDir();
  686. pluginCtx.environment.logger.info(`${c.green("Inspect report generated at")} ${c.dim(dir)}`);
  687. if (_open && !isCI)
  688. createPreviewServer(dir);
  689. }
  690. });
  691. }
  692. },
  693. configureServer(server) {
  694. const rpc = configureServer(server);
  695. plugin.api = {
  696. rpc
  697. };
  698. return () => {
  699. setupMiddlewarePerf(
  700. ctx.getViteContext(server.config),
  701. server.middlewares.stack
  702. );
  703. };
  704. },
  705. load: {
  706. order: "pre",
  707. handler(id) {
  708. ctx.getEnvContext(this.environment)?.invalidate(id);
  709. return null;
  710. }
  711. },
  712. hotUpdate({ modules }) {
  713. const ids = modules.map((module) => module.id);
  714. this.environment.hot.send({
  715. type: "custom",
  716. event: "vite-plugin-inspect:update",
  717. data: { ids }
  718. });
  719. },
  720. async buildEnd() {
  721. onBuildEnd?.(this.environment.name, this);
  722. },
  723. sharedDuringBuild: true
  724. };
  725. return plugin;
  726. }
  727. export { PluginInspect as P };