@xiaou66/vite-plugin-vue-mcp-next 1.0.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -380,6 +380,500 @@ function getRuntimePageIdentity(input) {
380
380
  };
381
381
  }
382
382
 
383
+ // src/runtime/performanceHook.ts
384
+ var import_nanoid4 = require("nanoid");
385
+
386
+ // src/shared/limits.ts
387
+ var DEFAULT_DOM_MAX_DEPTH = 8;
388
+ var DEFAULT_DOM_MAX_NODES = 2e3;
389
+ var DEFAULT_DOM_MAX_TEXT_LENGTH = 300;
390
+ var DEFAULT_CONSOLE_MAX_RECORDS = 1e3;
391
+ var DEFAULT_NETWORK_MAX_RECORDS = 500;
392
+ var DEFAULT_NETWORK_MAX_BODY_SIZE = 1e5;
393
+ var DEFAULT_MASK_HEADERS = [
394
+ "authorization",
395
+ "cookie",
396
+ "set-cookie"
397
+ ];
398
+
399
+ // src/constants.ts
400
+ var DEFAULT_MCP_PATH = "/__mcp";
401
+ var DEFAULT_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
402
+ var DEFAULT_SCREENSHOT_SAVE_DIR = ".vite-mcp/screenshot";
403
+ var DEFAULT_PERFORMANCE_SAVE_DIR = ".vite-mcp/performance";
404
+ var DEFAULT_PERFORMANCE_MAX_DURATION_MS = 3e4;
405
+ var DEFAULT_PERFORMANCE_SAMPLE_INTERVAL_MS = 250;
406
+ var DEFAULT_PERFORMANCE_LONG_TASK_THRESHOLD_MS = 50;
407
+ var VIRTUAL_RUNTIME_ID = "virtual:vite-plugin-vue-mcp-next/runtime";
408
+ var RESOLVED_VIRTUAL_RUNTIME_ID = `\0${VIRTUAL_RUNTIME_ID}`;
409
+ var VIRTUAL_SCREENSHOT_CONFIG_ID = "virtual:vite-plugin-vue-mcp-next/screenshot-config";
410
+ var RESOLVED_VIRTUAL_SCREENSHOT_CONFIG_ID = `\0${VIRTUAL_SCREENSHOT_CONFIG_ID}`;
411
+ var VIRTUAL_SNAPDOM_LOADER_ID = "virtual:vite-plugin-vue-mcp-next/snapdom-loader";
412
+ var RESOLVED_VIRTUAL_SNAPDOM_LOADER_ID = `\0${VIRTUAL_SNAPDOM_LOADER_ID}`;
413
+ var DEFAULT_MCP_CLIENT_SERVER_NAME = "vite-mcp-next";
414
+ var DEFAULT_OPTIONS = {
415
+ mcpPath: DEFAULT_MCP_PATH,
416
+ host: "localhost",
417
+ printUrl: true,
418
+ updateCursorMcpJson: {
419
+ enabled: true,
420
+ serverName: DEFAULT_MCP_CLIENT_SERVER_NAME
421
+ },
422
+ mcpClients: {
423
+ cursor: true,
424
+ codex: true,
425
+ claudeCode: true,
426
+ trae: true,
427
+ serverName: DEFAULT_MCP_CLIENT_SERVER_NAME
428
+ },
429
+ skill: {
430
+ autoConfig: true
431
+ },
432
+ runtime: {
433
+ mode: "auto",
434
+ evaluate: {
435
+ enabled: false,
436
+ timeoutMs: 3e3
437
+ }
438
+ },
439
+ cdp: {},
440
+ network: {
441
+ mode: "auto",
442
+ maxRecords: DEFAULT_NETWORK_MAX_RECORDS,
443
+ captureRequestBody: true,
444
+ captureResponseBody: true,
445
+ maxBodySize: DEFAULT_NETWORK_MAX_BODY_SIZE,
446
+ maskHeaders: [...DEFAULT_MASK_HEADERS]
447
+ },
448
+ dom: {
449
+ maxDepth: DEFAULT_DOM_MAX_DEPTH,
450
+ maxNodes: DEFAULT_DOM_MAX_NODES,
451
+ maxTextLength: DEFAULT_DOM_MAX_TEXT_LENGTH
452
+ },
453
+ console: {
454
+ maxRecords: DEFAULT_CONSOLE_MAX_RECORDS
455
+ },
456
+ screenshot: {
457
+ type: "path",
458
+ saveDir: DEFAULT_SCREENSHOT_SAVE_DIR,
459
+ prefer: "auto",
460
+ maxBytes: DEFAULT_SCREENSHOT_MAX_BYTES,
461
+ snapdom: {
462
+ options: {},
463
+ plugins: []
464
+ }
465
+ },
466
+ performance: {
467
+ mode: "auto",
468
+ maxDurationMs: DEFAULT_PERFORMANCE_MAX_DURATION_MS,
469
+ sampleIntervalMs: DEFAULT_PERFORMANCE_SAMPLE_INTERVAL_MS,
470
+ longTaskThresholdMs: DEFAULT_PERFORMANCE_LONG_TASK_THRESHOLD_MS,
471
+ saveDir: DEFAULT_PERFORMANCE_SAVE_DIR,
472
+ memory: {
473
+ enabled: true
474
+ },
475
+ stacks: {
476
+ enabled: true
477
+ }
478
+ }
479
+ };
480
+
481
+ // src/performance/summary.ts
482
+ function buildPerformanceSummary(input) {
483
+ const memory = buildMemorySummary(input.memorySamples);
484
+ const blockedTimeMs = input.longTasks.reduce((total, task) => {
485
+ return total + Math.max(0, task.durationMs - DEFAULT_PERFORMANCE_LONG_TASK_THRESHOLD_MS);
486
+ }, 0);
487
+ const longTaskCount = input.longTasks.length;
488
+ const durations = input.longTasks.map((task) => task.durationMs);
489
+ const maxTaskDurationMs = durations.length ? Math.max(...durations) : 0;
490
+ const averageTaskDurationMs = durations.length ? Math.round(
491
+ durations.reduce((total, duration) => total + duration, 0) / durations.length
492
+ ) : void 0;
493
+ const suspectedJank = blockedTimeMs > 0 || memory.trend === "growing" || longTaskCount > 0;
494
+ const severity = resolveSeverity({
495
+ blockedTimeMs,
496
+ longTaskCount,
497
+ memoryTrend: memory.trend
498
+ });
499
+ return {
500
+ blockedTimeMs,
501
+ longTaskCount,
502
+ maxTaskDurationMs,
503
+ averageTaskDurationMs,
504
+ suspectedJank,
505
+ severity
506
+ };
507
+ }
508
+ function buildMemorySummary(samples) {
509
+ const first = samples[0]?.usedJSHeapSize;
510
+ const last = samples.at(-1)?.usedJSHeapSize;
511
+ const peak = samples.reduce((currentPeak, sample) => {
512
+ if (typeof sample.usedJSHeapSize !== "number") {
513
+ return currentPeak;
514
+ }
515
+ return Math.max(currentPeak, sample.usedJSHeapSize);
516
+ }, 0);
517
+ const trend = resolveMemoryTrend(first, last);
518
+ return {
519
+ samples,
520
+ initialUsedJSHeapSize: first,
521
+ finalUsedJSHeapSize: last,
522
+ peakUsedJSHeapSize: peak || void 0,
523
+ deltaUsedJSHeapSize: typeof first === "number" && typeof last === "number" ? last - first : void 0,
524
+ trend
525
+ };
526
+ }
527
+ function buildStackSummary(frames, options = {}) {
528
+ return {
529
+ topFrames: [...frames].sort(sortByHotness).slice(0, 10),
530
+ rawProfilePath: options.rawProfilePath,
531
+ limitation: options.limitation
532
+ };
533
+ }
534
+ function resolveSeverity(input) {
535
+ if (input.blockedTimeMs >= 1e3 || input.longTaskCount >= 10) {
536
+ return "critical";
537
+ }
538
+ if (input.blockedTimeMs > 0 || input.memoryTrend === "growing") {
539
+ return "warning";
540
+ }
541
+ return "ok";
542
+ }
543
+ function resolveMemoryTrend(first, last) {
544
+ if (typeof first !== "number" || typeof last !== "number") {
545
+ return "unknown";
546
+ }
547
+ if (last > first) {
548
+ return "growing";
549
+ }
550
+ return "stable";
551
+ }
552
+ function sortByHotness(left, right) {
553
+ return compareNumber(right.totalTimeMs, left.totalTimeMs) || compareNumber(right.selfTimeMs, left.selfTimeMs) || compareNumber(right.hitCount, left.hitCount);
554
+ }
555
+ function compareNumber(left, right) {
556
+ return (left ?? -1) - (right ?? -1);
557
+ }
558
+
559
+ // src/runtime/performanceHook.ts
560
+ var activePerformanceCollector;
561
+ function installPerformanceHook(options) {
562
+ const collector = createPerformanceCollector({
563
+ pageId: options.pageId,
564
+ now: () => Date.now(),
565
+ readMemory: readBrowserMemory,
566
+ observeLongTask: (push) => observeLongTasks(push, options.longTaskThresholdMs),
567
+ observeAnimationFrame: observeAnimationFrameTasks,
568
+ observeErrorStack: observeWindowErrorStack,
569
+ observeUnhandledRejectionStack,
570
+ setTimeout: window.setTimeout.bind(window),
571
+ clearTimeout: window.clearTimeout.bind(window)
572
+ });
573
+ const decoratedCollector = options.send ? createDispatchingCollector(collector, options.send) : collector;
574
+ activePerformanceCollector = decoratedCollector;
575
+ return decoratedCollector;
576
+ }
577
+ function getPerformanceCollector() {
578
+ return activePerformanceCollector;
579
+ }
580
+ function createPerformanceCollector(deps) {
581
+ const state = {
582
+ longTasks: [],
583
+ stackFrames: [],
584
+ memorySamples: [],
585
+ latestReport: void 0,
586
+ activeRecordingId: void 0,
587
+ activeRecordingStartedAt: 0,
588
+ activeIncludeMemory: true,
589
+ activeIncludeStacks: true
590
+ };
591
+ const cleanups = [
592
+ deps.observeLongTask((task) => {
593
+ state.longTasks.push(task);
594
+ }),
595
+ deps.observeAnimationFrame((task) => {
596
+ state.longTasks.push(task);
597
+ })
598
+ ];
599
+ if (deps.observeErrorStack) {
600
+ cleanups.push(
601
+ deps.observeErrorStack((frame) => {
602
+ state.stackFrames.push(frame);
603
+ })
604
+ );
605
+ }
606
+ if (deps.observeUnhandledRejectionStack) {
607
+ cleanups.push(
608
+ deps.observeUnhandledRejectionStack((frame) => {
609
+ state.stackFrames.push(frame);
610
+ })
611
+ );
612
+ }
613
+ return {
614
+ async recordOnce(options) {
615
+ const recordingId = startSession(state, deps, {
616
+ includeMemory: options.includeMemory,
617
+ includeStacks: options.includeStacks
618
+ });
619
+ await waitForDuration(deps, options.durationMs);
620
+ return stopSession({
621
+ state,
622
+ deps,
623
+ recordingId,
624
+ source: "hook"
625
+ });
626
+ },
627
+ start(options) {
628
+ if (state.activeRecordingId) {
629
+ throw new Error("A performance recording is already active");
630
+ }
631
+ return startSession(state, deps, options);
632
+ },
633
+ stop(recordingId) {
634
+ return stopSession({
635
+ state,
636
+ deps,
637
+ recordingId,
638
+ source: "hook"
639
+ });
640
+ },
641
+ latest() {
642
+ return state.latestReport;
643
+ },
644
+ dispose() {
645
+ activePerformanceCollector = void 0;
646
+ for (const cleanup of cleanups) {
647
+ cleanup();
648
+ }
649
+ }
650
+ };
651
+ }
652
+ function createRecordingId() {
653
+ return `performance-${(0, import_nanoid4.nanoid)()}`;
654
+ }
655
+ function startSession(state, deps, options) {
656
+ const recordingId = createRecordingId();
657
+ state.longTasks.length = 0;
658
+ state.stackFrames.length = 0;
659
+ state.memorySamples.length = 0;
660
+ if (options.includeMemory) {
661
+ const sample = deps.readMemory();
662
+ if (sample) {
663
+ state.memorySamples.push(sample);
664
+ }
665
+ }
666
+ state.activeRecordingId = recordingId;
667
+ state.activeRecordingStartedAt = deps.now();
668
+ state.activeIncludeMemory = options.includeMemory;
669
+ state.activeIncludeStacks = options.includeStacks;
670
+ return recordingId;
671
+ }
672
+ function createDispatchingCollector(collector, send) {
673
+ return {
674
+ async recordOnce(options) {
675
+ const report = await collector.recordOnce(options);
676
+ send(report);
677
+ return report;
678
+ },
679
+ start(options) {
680
+ return collector.start(options);
681
+ },
682
+ stop(recordingId) {
683
+ const report = collector.stop(recordingId);
684
+ send(report);
685
+ return report;
686
+ },
687
+ latest() {
688
+ return collector.latest();
689
+ },
690
+ dispose() {
691
+ collector.dispose();
692
+ }
693
+ };
694
+ }
695
+ function waitForDuration(deps, durationMs) {
696
+ return new Promise((resolve) => {
697
+ const timer = deps.setTimeout(() => {
698
+ deps.clearTimeout(timer);
699
+ resolve();
700
+ }, durationMs);
701
+ });
702
+ }
703
+ function buildReport(options) {
704
+ const memory = options.includeMemory ? buildMemorySummary(options.memorySamples) : void 0;
705
+ const summary = buildPerformanceSummary({
706
+ longTasks: options.longTasks,
707
+ memorySamples: options.includeMemory ? options.memorySamples : []
708
+ });
709
+ const stacks = options.includeStacks ? buildStackSummary(options.stackFrames, {
710
+ rawProfilePath: options.rawProfilePath,
711
+ limitation: options.stackFrames.length > 0 ? void 0 : "Runtime path only exposes error stacks when the page reports them"
712
+ }) : void 0;
713
+ const report = {
714
+ recordingId: options.recordingId,
715
+ pageId: options.pageId,
716
+ source: options.source,
717
+ startedAt: options.startedAt,
718
+ endedAt: options.endedAt,
719
+ durationMs: options.endedAt - options.startedAt,
720
+ summary,
721
+ longTasks: [...options.longTasks],
722
+ memory,
723
+ stacks,
724
+ artifacts: options.artifacts,
725
+ limitations: options.limitations
726
+ };
727
+ return report;
728
+ }
729
+ function stopSession(options) {
730
+ const { state, deps, recordingId, source } = options;
731
+ if (!state.activeRecordingId || state.activeRecordingId !== recordingId) {
732
+ throw new Error(`Performance recording not found: ${recordingId}`);
733
+ }
734
+ if (state.activeIncludeMemory) {
735
+ const sample = deps.readMemory();
736
+ if (sample) {
737
+ state.memorySamples.push(sample);
738
+ }
739
+ }
740
+ const report = buildReport({
741
+ recordingId: state.activeRecordingId,
742
+ pageId: deps.pageId,
743
+ startedAt: state.activeRecordingStartedAt,
744
+ endedAt: deps.now(),
745
+ source,
746
+ includeMemory: state.activeIncludeMemory,
747
+ includeStacks: state.activeIncludeStacks,
748
+ longTasks: state.longTasks,
749
+ memorySamples: state.memorySamples,
750
+ stackFrames: state.stackFrames,
751
+ limitations: [
752
+ "Runtime path only sees browser-observable signals",
753
+ "Runtime path cannot produce a full CPU profile or heap snapshot"
754
+ ]
755
+ });
756
+ state.latestReport = report;
757
+ state.activeRecordingId = void 0;
758
+ state.activeRecordingStartedAt = 0;
759
+ state.activeIncludeMemory = true;
760
+ state.activeIncludeStacks = true;
761
+ return report;
762
+ }
763
+ function readBrowserMemory() {
764
+ const memory = performance.memory;
765
+ if (!memory) {
766
+ return void 0;
767
+ }
768
+ return {
769
+ timestamp: Date.now(),
770
+ usedJSHeapSize: memory.usedJSHeapSize,
771
+ totalJSHeapSize: memory.totalJSHeapSize,
772
+ jsHeapSizeLimit: memory.jsHeapSizeLimit
773
+ };
774
+ }
775
+ function observeLongTasks(push, longTaskThresholdMs = 50) {
776
+ if (typeof PerformanceObserver === "undefined") {
777
+ return () => {
778
+ };
779
+ }
780
+ const observer = new PerformanceObserver((list) => {
781
+ for (const entry of list.getEntries()) {
782
+ if (entry.duration < longTaskThresholdMs) {
783
+ continue;
784
+ }
785
+ push({
786
+ startTime: entry.startTime,
787
+ durationMs: entry.duration,
788
+ name: entry.name,
789
+ source: "longtask"
790
+ });
791
+ }
792
+ });
793
+ observer.observe({ type: "longtask", buffered: true });
794
+ return () => {
795
+ observer.disconnect();
796
+ };
797
+ }
798
+ function observeAnimationFrameTasks(push) {
799
+ if (typeof PerformanceObserver === "undefined") {
800
+ return () => {
801
+ };
802
+ }
803
+ const supported = PerformanceObserver.supportedEntryTypes.includes(
804
+ "long-animation-frame"
805
+ );
806
+ if (!supported) {
807
+ return () => {
808
+ };
809
+ }
810
+ const observer = new PerformanceObserver((list) => {
811
+ for (const entry of list.getEntries()) {
812
+ push({
813
+ startTime: entry.startTime,
814
+ durationMs: entry.duration,
815
+ name: entry.name,
816
+ source: "long-animation-frame"
817
+ });
818
+ }
819
+ });
820
+ observer.observe({ type: "long-animation-frame", buffered: true });
821
+ return () => {
822
+ observer.disconnect();
823
+ };
824
+ }
825
+ function observeWindowErrorStack(push) {
826
+ const onError = (event) => {
827
+ const error = event.error;
828
+ const frames = parseStackFrames(error?.stack);
829
+ if (frames.length === 0 && event.message) {
830
+ push({ functionName: event.message });
831
+ return;
832
+ }
833
+ frames.forEach((frame) => {
834
+ push(frame);
835
+ });
836
+ };
837
+ window.addEventListener("error", onError);
838
+ return () => {
839
+ window.removeEventListener("error", onError);
840
+ };
841
+ }
842
+ function observeUnhandledRejectionStack(push) {
843
+ const onRejection = (event) => {
844
+ const reason = event.reason;
845
+ const frames = parseStackFrames(reason?.stack);
846
+ if (frames.length === 0 && reason?.message) {
847
+ push({ functionName: reason.message });
848
+ return;
849
+ }
850
+ frames.forEach((frame) => {
851
+ push(frame);
852
+ });
853
+ };
854
+ window.addEventListener("unhandledrejection", onRejection);
855
+ return () => {
856
+ window.removeEventListener("unhandledrejection", onRejection);
857
+ };
858
+ }
859
+ function parseStackFrames(stack) {
860
+ if (!stack) {
861
+ return [];
862
+ }
863
+ return stack.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
864
+ const match = /at\s+(.*?)\s+\((.*?):(\d+):(\d+)\)$/.exec(line);
865
+ if (match) {
866
+ return {
867
+ functionName: match[1] || "<anonymous>",
868
+ url: match[2],
869
+ lineNumber: Number(match[3]),
870
+ columnNumber: Number(match[4])
871
+ };
872
+ }
873
+ return { functionName: line };
874
+ });
875
+ }
876
+
383
877
  // src/runtime/screenshot.ts
384
878
  var snapdomLoader = () => Promise.reject(createMissingSnapdomError());
385
879
  var screenshotModuleRegistry = {};
@@ -789,7 +1283,79 @@ function createClientVueRuntimeRpc(getRpc) {
789
1283
  });
790
1284
  getRpc().onPiniaInfoUpdated(query.event, (0, import_devtools_kit.stringify)(result));
791
1285
  },
792
- onPiniaInfoUpdated: () => void 0
1286
+ onPiniaInfoUpdated: () => void 0,
1287
+ async recordPerformance(query) {
1288
+ const collector = getPerformanceCollector();
1289
+ if (!collector) {
1290
+ getRpc().onPerformanceRecorded(
1291
+ query.event,
1292
+ createPerformanceUnavailableError()
1293
+ );
1294
+ return;
1295
+ }
1296
+ try {
1297
+ const report = await collector.recordOnce({
1298
+ durationMs: query.durationMs,
1299
+ includeMemory: query.includeMemory,
1300
+ includeStacks: query.includeStacks
1301
+ });
1302
+ getRpc().onPerformanceRecorded(query.event, report);
1303
+ } catch (error) {
1304
+ getRpc().onPerformanceRecorded(
1305
+ query.event,
1306
+ createPerformanceError(error)
1307
+ );
1308
+ }
1309
+ },
1310
+ onPerformanceRecorded: () => void 0,
1311
+ startPerformanceRecording(query) {
1312
+ const collector = getPerformanceCollector();
1313
+ if (!collector) {
1314
+ getRpc().onPerformanceRecordingStarted(
1315
+ query.event,
1316
+ createPerformanceUnavailableError()
1317
+ );
1318
+ return;
1319
+ }
1320
+ try {
1321
+ const recordingId = collector.start({
1322
+ includeMemory: query.includeMemory,
1323
+ includeStacks: query.includeStacks
1324
+ });
1325
+ getRpc().onPerformanceRecordingStarted(query.event, {
1326
+ ok: true,
1327
+ recordingId,
1328
+ startedAt: Date.now(),
1329
+ source: "hook"
1330
+ });
1331
+ } catch (error) {
1332
+ getRpc().onPerformanceRecordingStarted(
1333
+ query.event,
1334
+ createPerformanceError(error)
1335
+ );
1336
+ }
1337
+ },
1338
+ onPerformanceRecordingStarted: () => void 0,
1339
+ stopPerformanceRecording(query) {
1340
+ const collector = getPerformanceCollector();
1341
+ if (!collector) {
1342
+ getRpc().onPerformanceRecordingStopped(
1343
+ query.event,
1344
+ createPerformanceUnavailableError()
1345
+ );
1346
+ return;
1347
+ }
1348
+ try {
1349
+ const report = collector.stop(query.recordingId);
1350
+ getRpc().onPerformanceRecordingStopped(query.event, report);
1351
+ } catch (error) {
1352
+ getRpc().onPerformanceRecordingStopped(
1353
+ query.event,
1354
+ createPerformanceError(error)
1355
+ );
1356
+ }
1357
+ },
1358
+ onPerformanceRecordingStopped: () => void 0
793
1359
  };
794
1360
  }
795
1361
  function setStateValue(object, path, value) {
@@ -824,6 +1390,18 @@ function callVueDevtoolsHook(name, payload) {
824
1390
  const hooks = import_devtools_kit.devtools.ctx.hooks;
825
1391
  hooks.callHook(name, payload);
826
1392
  }
1393
+ function createPerformanceUnavailableError() {
1394
+ return {
1395
+ ok: false,
1396
+ error: "Performance collector is not initialized"
1397
+ };
1398
+ }
1399
+ function createPerformanceError(error) {
1400
+ return {
1401
+ ok: false,
1402
+ error: error instanceof Error ? error.message : String(error)
1403
+ };
1404
+ }
827
1405
  async function findComponentNode(componentName) {
828
1406
  const inspectorTree = await import_devtools_kit.devtools.api.getInspectorTree({
829
1407
  inspectorId: COMPONENTS_INSPECTOR_ID,
@@ -888,6 +1466,12 @@ async function startRuntimeClient() {
888
1466
  readyState: document.readyState
889
1467
  });
890
1468
  hot.send("vite-plugin-vue-mcp-next:page-connected", identity);
1469
+ installPerformanceHook({
1470
+ pageId: identity.pageId,
1471
+ send(report) {
1472
+ hot.send("vite-plugin-vue-mcp-next:performance-record", report);
1473
+ }
1474
+ });
891
1475
  installConsoleHook({
892
1476
  pageId: identity.pageId,
893
1477
  send(record) {