weapp-vite 6.16.1 → 6.16.2

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.
package/dist/cli.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { C as isPathInside, S as shouldPassPlatformArgToIdeOpen, _ as createCjsConfigLoadError, b as normalizeMiniPlatform, c as createSharedBuildConfig, f as resolveWeappConfigFile, g as parseCommentJson, h as loadViteConfigFile, l as SHARED_CHUNK_VIRTUAL_PREFIX, m as getProjectConfigFileName, n as syncProjectSupportFiles, p as checkRuntime, r as syncManagedTsconfigBootstrapFiles, s as formatBytes, t as createCompilerContext, u as resolveHmrProfileJsonPath, v as DEFAULT_MP_PLATFORM, x as resolveMiniPlatform, y as getDefaultIdeProjectRoot } from "./createContext-C-05IQc1.mjs";
1
+ import { C as isPathInside, S as shouldPassPlatformArgToIdeOpen, _ as createCjsConfigLoadError, b as normalizeMiniPlatform, c as createSharedBuildConfig, f as resolveWeappConfigFile, g as parseCommentJson, h as loadViteConfigFile, l as SHARED_CHUNK_VIRTUAL_PREFIX, m as getProjectConfigFileName, n as syncProjectSupportFiles, p as checkRuntime, r as syncManagedTsconfigBootstrapFiles, s as formatBytes, t as createCompilerContext, u as resolveHmrProfileJsonPath, v as DEFAULT_MP_PLATFORM, x as resolveMiniPlatform, y as getDefaultIdeProjectRoot } from "./createContext-D69xinBq.mjs";
2
2
  import { r as logger_default, t as colors } from "./logger-CgxdNjvb.mjs";
3
- import { h as VERSION } from "./file-BC_RNbAI.mjs";
3
+ import { h as VERSION } from "./file-Dnwa6tm_.mjs";
4
4
  import { a as resolveWeappMcpConfig, o as startWeappViteMcpServer } from "./mcp-BzcrPiku.mjs";
5
5
  import { createRequire } from "node:module";
6
6
  import path, { posix } from "pathe";
@@ -14,6 +14,7 @@ import os from "node:os";
14
14
  import { execFile } from "node:child_process";
15
15
  import { Buffer } from "node:buffer";
16
16
  import { cac } from "cac";
17
+ import { brotliCompressSync, gzipSync } from "node:zlib";
17
18
  import { resolveCommand } from "package-manager-detector/commands";
18
19
  import { RETRY_CANCEL_KEYS, RETRY_CONFIRM_KEYS, bootstrapWechatDevtoolsSettings, buildWechatIdeNpm, clearWechatIdeCache, clearWechatIdeCacheByAutomator, closeSharedMiniProgram, closeWechatIdeProject, compileWechatIdeByAutomator, connectOpenedAutomator, createSharedInputSession, dispatchWechatCliCommand, formatAutomatorLoginError, getConfig, getWechatIdeTestAccounts, getWechatIdeTicket, getWechatIdeToolInfo, isAutomatorLoginError, isWeappIdeTopLevelCommand, isWechatIdeLoginRequiredError, launchAutomator, openWechatIdeProjectByHttp, parse, promptRetryKeypress, promptWechatIdeLoginRetry, quitWechatIde, refreshWechatIdeTicket, resetWechatIdeFileUtilsByHttp, runRetryableCommand, runWechatIdeEngineBuild, runWithSuspendedSharedInput, setWechatIdeTicket, startForwardConsole, takeScreenshot } from "weapp-ide-cli";
19
20
  import { promisify } from "node:util";
@@ -122,6 +123,223 @@ async function analyzeHmrProfile(options) {
122
123
  };
123
124
  }
124
125
  //#endregion
126
+ //#region src/analyze/components/suggestions.ts
127
+ function createComponentSuggestion(usage) {
128
+ if (usage.componentPackage !== "__main__" || usage.crossPackageUsageCount === 0) return;
129
+ if (usage.crossPackageUsageCount === usage.placeholderCoveredCrossPackageUsageCount) return;
130
+ const pagePackages = Array.from(new Set(Array.from(usage.pages.values()).map((page) => page.packageId))).sort((left, right) => {
131
+ if (left === "__main__") return -1;
132
+ if (right === "__main__") return 1;
133
+ return left.localeCompare(right);
134
+ });
135
+ const subPackageIds = pagePackages.filter((packageId) => packageId !== "__main__");
136
+ const usedByMain = pagePackages.includes("__main__");
137
+ if (subPackageIds.length === 1 && !usedByMain) {
138
+ const targetPackage = subPackageIds[0];
139
+ return {
140
+ kind: "move-to-subpackage",
141
+ component: usage.component,
142
+ componentPackage: usage.componentPackage,
143
+ targetPackage,
144
+ pagePackages,
145
+ message: `主包组件 ${usage.component} 仅被分包 ${targetPackage} 使用,建议评估移动到该分包。`
146
+ };
147
+ }
148
+ if (subPackageIds.length > 1) return {
149
+ kind: "shared-subpackage-or-placeholder",
150
+ component: usage.component,
151
+ componentPackage: usage.componentPackage,
152
+ pagePackages,
153
+ message: `主包组件 ${usage.component} 被多个分包使用,建议评估分包归属、共享策略或 componentPlaceholder。`
154
+ };
155
+ if (usedByMain && subPackageIds.length > 0) return {
156
+ kind: "split-or-async",
157
+ component: usage.component,
158
+ componentPackage: usage.componentPackage,
159
+ pagePackages,
160
+ message: `主包组件 ${usage.component} 同时被主包和分包使用,建议评估组件拆分、归属或异步化策略。`
161
+ };
162
+ }
163
+ //#endregion
164
+ //#region src/analyze/components/index.ts
165
+ function normalizeRoute(value) {
166
+ return posix.normalize(value.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\.json$/, ""));
167
+ }
168
+ function isRecord(value) {
169
+ return typeof value === "object" && value !== null && !Array.isArray(value);
170
+ }
171
+ function toStringArray(value) {
172
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim() !== "") : [];
173
+ }
174
+ function readUsingComponents(config) {
175
+ if (!isRecord(config) || !isRecord(config.usingComponents)) return [];
176
+ return Object.entries(config.usingComponents).filter((entry) => typeof entry[1] === "string" && entry[1].trim() !== "");
177
+ }
178
+ function readComponentPlaceholder(config) {
179
+ if (!isRecord(config) || !isRecord(config.componentPlaceholder)) return /* @__PURE__ */ new Set();
180
+ return new Set(Object.keys(config.componentPlaceholder));
181
+ }
182
+ function resolveComponentRoute(owner, request) {
183
+ const normalizedRequest = request.replace(/\\/g, "/").trim();
184
+ if (!normalizedRequest || normalizedRequest.startsWith("plugin://")) return;
185
+ if (normalizedRequest.startsWith(".")) return normalizeRoute(posix.join(posix.dirname(owner), normalizedRequest));
186
+ if (normalizedRequest.startsWith("/")) return normalizeRoute(normalizedRequest);
187
+ return normalizeRoute(normalizedRequest);
188
+ }
189
+ function resolvePackageId(route, subPackages) {
190
+ return subPackages.map((item) => item.root).filter(Boolean).sort((left, right) => right.length - left.length).find((root) => route === root || route.startsWith(`${root}/`)) ?? "__main__";
191
+ }
192
+ function collectAppPages(configs) {
193
+ const appJson = configs.get("app");
194
+ if (!isRecord(appJson)) return /* @__PURE__ */ new Set();
195
+ const pages = /* @__PURE__ */ new Set();
196
+ for (const page of toStringArray(appJson.pages)) pages.add(normalizeRoute(page));
197
+ const subPackages = Array.isArray(appJson.subPackages) ? appJson.subPackages : Array.isArray(appJson.subpackages) ? appJson.subpackages : [];
198
+ for (const item of subPackages) {
199
+ if (!isRecord(item) || typeof item.root !== "string") continue;
200
+ for (const page of toStringArray(item.pages)) pages.add(normalizeRoute(posix.join(item.root, page)));
201
+ }
202
+ return pages;
203
+ }
204
+ function registerUsage(usageMap, edge, page, pagePackage, componentPackage) {
205
+ const usage = usageMap.get(edge.component) ?? {
206
+ component: edge.component,
207
+ componentPackage,
208
+ totalUsageCount: 0,
209
+ pages: /* @__PURE__ */ new Map(),
210
+ crossPackageUsageCount: 0,
211
+ placeholderCoveredCrossPackageUsageCount: 0
212
+ };
213
+ usage.totalUsageCount += 1;
214
+ const pageUsage = usage.pages.get(page) ?? {
215
+ page,
216
+ packageId: pagePackage,
217
+ usageCount: 0
218
+ };
219
+ pageUsage.usageCount += 1;
220
+ usage.pages.set(page, pageUsage);
221
+ if (pagePackage !== componentPackage) {
222
+ usage.crossPackageUsageCount += 1;
223
+ if (edge.placeholderCovered) usage.placeholderCoveredCrossPackageUsageCount += 1;
224
+ }
225
+ usageMap.set(edge.component, usage);
226
+ }
227
+ function collectAnalyzeComponentJsonConfigs(output) {
228
+ if (!output) return [];
229
+ const configs = [];
230
+ for (const item of output.output ?? []) {
231
+ if (item.type !== "asset" || !item.fileName.endsWith(".json")) continue;
232
+ const asset = item;
233
+ if (typeof asset.source !== "string") continue;
234
+ try {
235
+ configs.push({
236
+ file: normalizeRoute(asset.fileName),
237
+ config: JSON.parse(asset.source)
238
+ });
239
+ } catch {}
240
+ }
241
+ return configs;
242
+ }
243
+ function analyzeComponentUsage(options) {
244
+ const configs = /* @__PURE__ */ new Map();
245
+ for (const item of options.jsonConfigs) configs.set(normalizeRoute(item.file), item.config);
246
+ const pages = collectAppPages(configs);
247
+ const graph = /* @__PURE__ */ new Map();
248
+ const packageMap = /* @__PURE__ */ new Map();
249
+ for (const route of configs.keys()) packageMap.set(route, resolvePackageId(route, options.subPackages));
250
+ for (const [owner, config] of configs) {
251
+ const placeholders = readComponentPlaceholder(config);
252
+ const edges = readUsingComponents(config).map(([name, request]) => {
253
+ const component = resolveComponentRoute(owner, request);
254
+ return component && configs.has(component) ? {
255
+ owner,
256
+ component,
257
+ placeholderCovered: placeholders.has(name)
258
+ } : void 0;
259
+ }).filter((edge) => Boolean(edge));
260
+ if (edges.length > 0) graph.set(owner, edges);
261
+ }
262
+ const usageMap = /* @__PURE__ */ new Map();
263
+ const visit = (owner, page, stack) => {
264
+ for (const edge of graph.get(owner) ?? []) {
265
+ const componentPackage = packageMap.get(edge.component) ?? "__main__";
266
+ registerUsage(usageMap, edge, page, packageMap.get(page) ?? "__main__", componentPackage);
267
+ if (stack.has(edge.component)) continue;
268
+ const nextStack = new Set(stack);
269
+ nextStack.add(edge.component);
270
+ visit(edge.component, page, nextStack);
271
+ }
272
+ };
273
+ for (const page of pages) visit(page, page, new Set([page]));
274
+ return Array.from(usageMap.values()).map((usage) => {
275
+ const pages = Array.from(usage.pages.values()).sort((left, right) => left.page.localeCompare(right.page));
276
+ const suggestions = [createComponentSuggestion(usage)].filter((item) => Boolean(item));
277
+ return {
278
+ component: usage.component,
279
+ componentPackage: usage.componentPackage,
280
+ totalUsageCount: usage.totalUsageCount,
281
+ pageUsageCount: pages.length,
282
+ pages,
283
+ suggestions
284
+ };
285
+ }).sort((left, right) => {
286
+ const usageDelta = right.totalUsageCount - left.totalUsageCount;
287
+ return usageDelta !== 0 ? usageDelta : left.component.localeCompare(right.component);
288
+ });
289
+ }
290
+ //#endregion
291
+ //#region src/analyze/subpackages/metadata.ts
292
+ const defaultTotalBudgetBytes = 20 * 1024 * 1024;
293
+ const defaultPackageBudgetBytes = 2 * 1024 * 1024;
294
+ const defaultWarningRatio = .85;
295
+ const defaultHistoryDir = ".weapp-vite/analyze-history";
296
+ const defaultHistoryLimit = 20;
297
+ function resolveBudgetValue(value, fallback) {
298
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
299
+ }
300
+ function resolveHistoryLimit(value) {
301
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : defaultHistoryLimit;
302
+ }
303
+ function resolveAnalyzeBudgets(configService) {
304
+ const budgets = configService.weappViteConfig.analyze?.budgets;
305
+ const legacyPackageBudget = configService.weappViteConfig.packageSizeWarningBytes;
306
+ const packageFallback = resolveBudgetValue(legacyPackageBudget, defaultPackageBudgetBytes);
307
+ return {
308
+ totalBytes: resolveBudgetValue(budgets?.totalBytes, defaultTotalBudgetBytes),
309
+ mainBytes: resolveBudgetValue(budgets?.mainBytes, packageFallback),
310
+ subPackageBytes: resolveBudgetValue(budgets?.subPackageBytes, packageFallback),
311
+ independentBytes: resolveBudgetValue(budgets?.independentBytes, packageFallback),
312
+ warningRatio: resolveBudgetValue(budgets?.warningRatio, defaultWarningRatio),
313
+ source: budgets ? "config" : "default"
314
+ };
315
+ }
316
+ function resolveAnalyzeHistoryMetadata(configService) {
317
+ const history = configService.weappViteConfig.analyze?.history;
318
+ if (history === false) return {
319
+ enabled: false,
320
+ dir: path.resolve(configService.cwd, defaultHistoryDir),
321
+ limit: defaultHistoryLimit
322
+ };
323
+ const historyConfig = typeof history === "object" && history ? history : {};
324
+ const rawDir = typeof historyConfig.dir === "string" && historyConfig.dir.trim() ? historyConfig.dir.trim() : defaultHistoryDir;
325
+ return {
326
+ enabled: historyConfig.enabled !== false,
327
+ dir: path.isAbsolute(rawDir) ? rawDir : path.resolve(configService.cwd, rawDir),
328
+ limit: resolveHistoryLimit(historyConfig.limit)
329
+ };
330
+ }
331
+ function createAnalyzeMetadata(configService, now = /* @__PURE__ */ new Date()) {
332
+ const history = resolveAnalyzeHistoryMetadata(configService);
333
+ return {
334
+ generatedAt: now.toISOString(),
335
+ budgets: resolveAnalyzeBudgets(configService),
336
+ history: {
337
+ ...history,
338
+ dir: configService.relativeCwd(history.dir)
339
+ }
340
+ };
341
+ }
342
+ //#endregion
125
343
  //#region src/analyze/subpackages/classifier.ts
126
344
  const VIRTUAL_MODULE_INDICATOR = "\0";
127
345
  const VIRTUAL_PREFIX = `${SHARED_CHUNK_VIRTUAL_PREFIX}/`;
@@ -139,6 +357,10 @@ function resolvePluginAssetAbsolute(normalizedFileName, pluginRoot) {
139
357
  const absolute = posix.resolve(pluginRoot, relative);
140
358
  return isPathInside(pluginRoot, absolute) ? absolute : void 0;
141
359
  }
360
+ function resolveSubPackageRoot$1(fileName, context) {
361
+ const normalized = posix.normalize(fileName);
362
+ return Array.from(context.subPackageRoots).filter(Boolean).sort((left, right) => right.length - left.length).find((root) => normalized === root || normalized.startsWith(`${root}/`));
363
+ }
142
364
  function classifyPackage(fileName, origin, context) {
143
365
  if (fileName.startsWith(VIRTUAL_PREFIX)) {
144
366
  const combination = fileName.slice(VIRTUAL_PREFIX.length).split("/")[0] || "shared";
@@ -148,8 +370,8 @@ function classifyPackage(fileName, origin, context) {
148
370
  type: "virtual"
149
371
  };
150
372
  }
151
- const rootCandidate = fileName.split("/")[0] ?? "";
152
- if (rootCandidate && context.subPackageRoots.has(rootCandidate)) {
373
+ const rootCandidate = resolveSubPackageRoot$1(fileName, context);
374
+ if (rootCandidate) {
153
375
  const isIndependent = context.independentRoots.has(rootCandidate);
154
376
  return {
155
377
  id: rootCandidate,
@@ -231,9 +453,17 @@ function registerModuleInPackage(modules, moduleId, source, sourceType, packageI
231
453
  }
232
454
  //#endregion
233
455
  //#region src/analyze/subpackages/output.ts
234
- function getAssetSize(asset) {
235
- if (typeof asset.source === "string") return Buffer.byteLength(asset.source, "utf8");
236
- if (asset.source instanceof Uint8Array) return asset.source.byteLength;
456
+ function getAssetBuffer(asset) {
457
+ if (typeof asset.source === "string") return Buffer.from(asset.source, "utf8");
458
+ if (asset.source instanceof Uint8Array) return Buffer.from(asset.source);
459
+ }
460
+ function getCompressedSizes$1(content) {
461
+ if (content === void 0) return {};
462
+ const buffer = typeof content === "string" ? Buffer.from(content, "utf8") : Buffer.from(content);
463
+ return {
464
+ gzipSize: gzipSync(buffer).byteLength,
465
+ brotliSize: brotliCompressSync(buffer).byteLength
466
+ };
237
467
  }
238
468
  function processChunk(chunk, origin, ctx, classifierContext, packages, modules) {
239
469
  const classification = classifyPackage(chunk.fileName, origin, classifierContext);
@@ -243,6 +473,7 @@ function processChunk(chunk, origin, ctx, classifierContext, packages, modules)
243
473
  type: "chunk",
244
474
  from: origin,
245
475
  size: typeof chunk.code === "string" ? Buffer.byteLength(chunk.code, "utf8") : void 0,
476
+ ...getCompressedSizes$1(chunk.code),
246
477
  isEntry: chunk.isEntry,
247
478
  modules: []
248
479
  };
@@ -267,11 +498,13 @@ function processChunk(chunk, origin, ctx, classifierContext, packages, modules)
267
498
  function processAsset(asset, origin, ctx, classifierContext, packages, modules) {
268
499
  const classification = classifyPackage(asset.fileName, origin, classifierContext);
269
500
  const packageEntry = ensurePackage(packages, classification);
501
+ const assetBuffer = getAssetBuffer(asset);
270
502
  const entry = {
271
503
  file: asset.fileName,
272
504
  type: "asset",
273
505
  from: origin,
274
- size: getAssetSize(asset)
506
+ size: assetBuffer?.byteLength,
507
+ ...getCompressedSizes$1(assetBuffer)
275
508
  };
276
509
  const assetSource = resolveAssetSource(asset.fileName, ctx);
277
510
  if (assetSource) {
@@ -411,22 +644,470 @@ async function analyzeSubpackages(ctx) {
411
644
  const mainOutputs = Array.isArray(mainResult) ? mainResult : [mainResult];
412
645
  const packages = /* @__PURE__ */ new Map();
413
646
  const modules = /* @__PURE__ */ new Map();
414
- for (const output of mainOutputs) processOutput(output, "main", ctx, classifierContext, packages, modules);
415
- for (const root of independentRoots) processOutput(buildService.getIndependentOutput(root), "independent", ctx, classifierContext, packages, modules);
647
+ const componentJsonConfigs = [];
648
+ for (const output of mainOutputs) {
649
+ processOutput(output, "main", ctx, classifierContext, packages, modules);
650
+ componentJsonConfigs.push(...collectAnalyzeComponentJsonConfigs(output));
651
+ }
652
+ for (const root of independentRoots) {
653
+ const output = buildService.getIndependentOutput(root);
654
+ processOutput(output, "independent", ctx, classifierContext, packages, modules);
655
+ componentJsonConfigs.push(...collectAnalyzeComponentJsonConfigs(output));
656
+ }
416
657
  expandVirtualModulePlacements(modules, packages, classifierContext);
658
+ const subPackages = summarizeSubPackages(subPackageMetas);
417
659
  return {
660
+ metadata: createAnalyzeMetadata(ctx.configService),
418
661
  packages: summarizePackages(packages),
419
662
  modules: summarizeModules(modules),
420
- subPackages: summarizeSubPackages(subPackageMetas)
663
+ subPackages,
664
+ components: analyzeComponentUsage({
665
+ jsonConfigs: componentJsonConfigs,
666
+ subPackages
667
+ })
668
+ };
669
+ }
670
+ //#endregion
671
+ //#region src/analyze/subpackages/history.ts
672
+ const jsonExtension = ".json";
673
+ function createSnapshotFileName(date = /* @__PURE__ */ new Date()) {
674
+ return `${date.toISOString().replace(/[:.]/g, "-")}${jsonExtension}`;
675
+ }
676
+ function isAnalyzeResult(value) {
677
+ return Boolean(value && typeof value === "object" && Array.isArray(value.packages) && Array.isArray(value.modules) && Array.isArray(value.subPackages));
678
+ }
679
+ async function listSnapshotFiles(dir) {
680
+ try {
681
+ return (await fs$2.readdir(dir, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(jsonExtension)).map((entry) => path.join(dir, entry.name)).sort((a, b) => b.localeCompare(a));
682
+ } catch {
683
+ return [];
684
+ }
685
+ }
686
+ async function trimSnapshotFiles(dir, limit) {
687
+ const staleFiles = (await listSnapshotFiles(dir)).slice(limit);
688
+ await Promise.all(staleFiles.map(async (file) => {
689
+ try {
690
+ await fs$2.unlink(file);
691
+ } catch {}
692
+ }));
693
+ }
694
+ async function readLatestAnalyzeHistorySnapshot(configService) {
695
+ const history = resolveAnalyzeHistoryMetadata(configService);
696
+ if (!history.enabled) return null;
697
+ const files = await listSnapshotFiles(history.dir);
698
+ for (const file of files) try {
699
+ const parsed = JSON.parse(await fs$2.readFile(file, "utf8"));
700
+ if (isAnalyzeResult(parsed)) return parsed;
701
+ } catch {}
702
+ return null;
703
+ }
704
+ async function writeAnalyzeHistorySnapshot(result, configService, now = /* @__PURE__ */ new Date()) {
705
+ const history = resolveAnalyzeHistoryMetadata(configService);
706
+ if (!history.enabled) return;
707
+ await fs$2.mkdir(history.dir, { recursive: true });
708
+ const snapshotPath = path.join(history.dir, createSnapshotFileName(now));
709
+ if (result.metadata) result.metadata = {
710
+ ...result.metadata,
711
+ history: {
712
+ ...history,
713
+ dir: configService.relativeCwd(history.dir),
714
+ latestSnapshot: configService.relativeCwd(snapshotPath)
715
+ }
716
+ };
717
+ await fs$2.writeFile(snapshotPath, `${JSON.stringify(result, null, 2)}\n`, "utf8");
718
+ await trimSnapshotFiles(history.dir, history.limit);
719
+ return snapshotPath;
720
+ }
721
+ //#endregion
722
+ //#region src/analyze/subpackages/report.ts
723
+ function formatAnalyzeBytes(bytes) {
724
+ if (!bytes || Number.isNaN(bytes)) return "0 B";
725
+ const units = [
726
+ "B",
727
+ "KB",
728
+ "MB",
729
+ "GB"
730
+ ];
731
+ let value = bytes;
732
+ let unitIndex = 0;
733
+ while (value >= 1024 && unitIndex < units.length - 1) {
734
+ value /= 1024;
735
+ unitIndex++;
736
+ }
737
+ return `${value.toFixed(value >= 100 || unitIndex === 0 ? 0 : 2)} ${units[unitIndex]}`;
738
+ }
739
+ function formatDelta(bytes) {
740
+ if (typeof bytes !== "number" || Number.isNaN(bytes) || bytes === 0) return "无变化";
741
+ return `${bytes > 0 ? "+" : "-"}${formatAnalyzeBytes(Math.abs(bytes))}`;
742
+ }
743
+ function getFileSize(file) {
744
+ return file.size ?? 0;
745
+ }
746
+ function getCompressedSize(file) {
747
+ return file.brotliSize ?? file.gzipSize ?? 0;
748
+ }
749
+ function getBudgetLimit(type, budgets) {
750
+ if (!budgets) return;
751
+ if (type === "main") return budgets.mainBytes;
752
+ if (type === "subPackage") return budgets.subPackageBytes;
753
+ if (type === "independent") return budgets.independentBytes;
754
+ }
755
+ function createPackageSizeMap(result) {
756
+ return new Map((result?.packages ?? []).map((pkg) => {
757
+ const totalBytes = pkg.files.reduce((sum, file) => sum + getFileSize(file), 0);
758
+ return [pkg.id, totalBytes];
759
+ }));
760
+ }
761
+ function createFileKey(packageId, fileName) {
762
+ return `${packageId}\u0000${fileName}`;
763
+ }
764
+ function createFileSizeMap(result) {
765
+ const map = /* @__PURE__ */ new Map();
766
+ for (const pkg of result?.packages ?? []) for (const file of pkg.files) map.set(createFileKey(pkg.id, file.file), getFileSize(file));
767
+ return map;
768
+ }
769
+ function createPackageTypeMap(result) {
770
+ return new Map(result.packages.map((pkg) => [pkg.id, pkg.type]));
771
+ }
772
+ function createModuleByteMap(result) {
773
+ const map = /* @__PURE__ */ new Map();
774
+ for (const pkg of result.packages) for (const file of pkg.files) for (const mod of file.modules ?? []) {
775
+ const bytes = mod.bytes ?? mod.originalBytes ?? 0;
776
+ map.set(mod.id, Math.max(map.get(mod.id) ?? 0, bytes));
777
+ }
778
+ return map;
779
+ }
780
+ function classifyIncrementCategory(source, sourceType) {
781
+ if (source.includes("wevu") || source.includes("@weapp-vite/dashboard")) return "WeVu / runtime";
782
+ if (sourceType === "node_modules" || source.includes("node_modules")) return "第三方依赖";
783
+ if (sourceType === "workspace") return "工作区包";
784
+ if (sourceType === "plugin") return "插件生成";
785
+ if (source.endsWith(".wxss") || source.endsWith(".css") || source.endsWith(".scss")) return "样式资源";
786
+ if (source.endsWith(".wxml") || source.endsWith(".json")) return "页面结构";
787
+ return "业务源码";
788
+ }
789
+ function createIncrementAdvice(item) {
790
+ if (item.category === "第三方依赖") return "检查依赖是否只在必要分包引用,必要时收敛到公共入口或替换轻量实现。";
791
+ if (item.category === "WeVu / runtime") return "确认运行时能力是否被新页面引入,优先排查组件和 API 引用边界。";
792
+ if (item.category === "样式资源") return "检查样式重复、原子类生成范围和组件样式裁剪。";
793
+ if (item.type === "new-file" || item.type === "new-module") return "确认是否为本次需求必要新增,评估分包归属和懒加载边界。";
794
+ return "对比本次变更,优先查看新增引用、共享模块和大对象常量。";
795
+ }
796
+ function createModuleSizeMap(result) {
797
+ const map = /* @__PURE__ */ new Map();
798
+ for (const pkg of result?.packages ?? []) for (const file of pkg.files) for (const module of file.modules ?? []) {
799
+ const bytes = module.bytes ?? module.originalBytes ?? 0;
800
+ const existing = map.get(module.id);
801
+ if (existing && existing.bytes >= bytes) continue;
802
+ map.set(module.id, {
803
+ source: module.source,
804
+ sourceType: module.sourceType,
805
+ bytes,
806
+ packageLabel: pkg.label,
807
+ file: file.file
808
+ });
809
+ }
810
+ return map;
811
+ }
812
+ function createDuplicateAdvice(sourceType, packageIds, packageTypeMap, estimatedSavingBytes) {
813
+ if (packageIds.some((packageId) => packageTypeMap.get(packageId) === "independent")) return estimatedSavingBytes > 0 ? "含独立分包,先确认隔离要求,再评估是否抽公共入口。" : "含独立分包,重复可能来自隔离边界。";
814
+ if (sourceType === "node_modules") return "依赖被多个包带入,检查引用边界或考虑主包公共入口。";
815
+ if (sourceType === "src" || sourceType === "workspace") return "共享源码跨包重复,优先抽公共模块或调整分包归属。";
816
+ if (sourceType === "plugin") return "插件生成内容跨包重复,检查插件产物输出策略。";
817
+ return "检查该模块是否需要在多个包内重复存在。";
818
+ }
819
+ function formatBudgetStatus(item) {
820
+ if (item.status === "ok") return "正常";
821
+ return `${item.status === "exceeded" ? "超预算" : "接近预算"} ${(item.ratio * 100).toFixed(1)}%`;
822
+ }
823
+ function createActionItems(options) {
824
+ const actions = [];
825
+ const exceededItems = options.budgetItems.filter((item) => item.status === "exceeded");
826
+ const warningItems = options.budgetItems.filter((item) => item.status === "warning");
827
+ const topDuplicate = options.duplicateInsights.find((item) => item.estimatedSavingBytes > 0);
828
+ if (exceededItems.length > 0) actions.push(`处理 ${exceededItems[0].label} 预算超限:当前 ${formatAnalyzeBytes(exceededItems[0].currentBytes)},限制 ${formatAnalyzeBytes(exceededItems[0].limitBytes)}。`);
829
+ else if (warningItems.length > 0) actions.push(`关注 ${warningItems[0].label} 预算接近阈值:当前 ${(warningItems[0].ratio * 100).toFixed(1)}%。`);
830
+ if (topDuplicate) actions.push(`优先处理重复模块 ${topDuplicate.source},估算可节省 ${formatAnalyzeBytes(topDuplicate.estimatedSavingBytes)}。`);
831
+ if (actions.length === 0) actions.push("当前没有预算超限或高收益重复模块,保持观察即可。");
832
+ return actions;
833
+ }
834
+ function createAnalyzeBudgetCheck(result) {
835
+ const budgets = result.metadata?.budgets;
836
+ if (!budgets) return [];
837
+ const items = [];
838
+ const totalBytes = result.packages.flatMap((pkg) => pkg.files).reduce((sum, file) => sum + getFileSize(file), 0);
839
+ const warningRatio = budgets.warningRatio;
840
+ const createItem = (options) => {
841
+ const ratio = options.limitBytes > 0 ? options.currentBytes / options.limitBytes : 0;
842
+ const status = ratio >= 1 ? "exceeded" : ratio >= warningRatio ? "warning" : "ok";
843
+ return {
844
+ ...options,
845
+ ratio,
846
+ status
847
+ };
421
848
  };
849
+ items.push(createItem({
850
+ id: "__total__",
851
+ label: "总包",
852
+ scope: "total",
853
+ currentBytes: totalBytes,
854
+ limitBytes: budgets.totalBytes
855
+ }));
856
+ for (const pkg of result.packages) {
857
+ const limitBytes = getBudgetLimit(pkg.type, budgets);
858
+ if (!limitBytes) continue;
859
+ items.push(createItem({
860
+ id: pkg.id,
861
+ label: pkg.label,
862
+ scope: pkg.type,
863
+ currentBytes: pkg.files.reduce((sum, file) => sum + getFileSize(file), 0),
864
+ limitBytes
865
+ }));
866
+ }
867
+ return items.sort((a, b) => b.ratio - a.ratio || a.label.localeCompare(b.label));
868
+ }
869
+ function createDuplicateModuleInsights(result) {
870
+ const moduleByteMap = createModuleByteMap(result);
871
+ const packageTypeMap = createPackageTypeMap(result);
872
+ return result.modules.filter((module) => module.packages.length > 1).map((module) => {
873
+ const bytes = moduleByteMap.get(module.id) ?? 0;
874
+ const packageIds = module.packages.map((pkg) => pkg.packageId);
875
+ const estimatedSavingBytes = bytes * Math.max(module.packages.length - 1, 0);
876
+ return {
877
+ id: module.id,
878
+ source: module.source,
879
+ sourceType: module.sourceType,
880
+ packageCount: module.packages.length,
881
+ bytes,
882
+ estimatedSavingBytes,
883
+ packages: packageIds,
884
+ advice: createDuplicateAdvice(module.sourceType, packageIds, packageTypeMap, estimatedSavingBytes)
885
+ };
886
+ }).sort((a, b) => b.estimatedSavingBytes - a.estimatedSavingBytes || b.packageCount - a.packageCount || a.source.localeCompare(b.source));
887
+ }
888
+ function createAnalyzeIncrementAttribution(result, previousResult) {
889
+ if (!previousResult) return [];
890
+ const previousFiles = createFileSizeMap(previousResult);
891
+ const previousModules = createModuleSizeMap(previousResult);
892
+ const currentModules = createModuleSizeMap(result);
893
+ const items = [];
894
+ for (const pkg of result.packages) for (const file of pkg.files) {
895
+ const currentBytes = getFileSize(file);
896
+ const previousBytes = previousFiles.get(createFileKey(pkg.id, file.file)) ?? 0;
897
+ const deltaBytes = currentBytes - previousBytes;
898
+ if (deltaBytes <= 0) continue;
899
+ const type = previousBytes > 0 ? "increased-file" : "new-file";
900
+ const item = {
901
+ key: `file:${pkg.id}:${file.file}`,
902
+ type,
903
+ label: file.file,
904
+ category: classifyIncrementCategory(file.source ?? file.file),
905
+ packageLabel: pkg.label,
906
+ file: file.file,
907
+ currentBytes,
908
+ previousBytes,
909
+ deltaBytes,
910
+ advice: ""
911
+ };
912
+ items.push({
913
+ ...item,
914
+ advice: createIncrementAdvice(item)
915
+ });
916
+ }
917
+ for (const [id, module] of currentModules) {
918
+ const previousBytes = previousModules.get(id)?.bytes ?? 0;
919
+ const deltaBytes = module.bytes - previousBytes;
920
+ if (deltaBytes <= 0) continue;
921
+ const type = previousBytes > 0 ? "increased-module" : "new-module";
922
+ const item = {
923
+ key: `module:${id}`,
924
+ type,
925
+ label: module.source,
926
+ category: classifyIncrementCategory(module.source, module.sourceType),
927
+ packageLabel: module.packageLabel,
928
+ file: module.file,
929
+ currentBytes: module.bytes,
930
+ previousBytes,
931
+ deltaBytes,
932
+ advice: ""
933
+ };
934
+ items.push({
935
+ ...item,
936
+ advice: createIncrementAdvice(item)
937
+ });
938
+ }
939
+ return items.sort((a, b) => b.deltaBytes - a.deltaBytes || a.category.localeCompare(b.category) || a.label.localeCompare(b.label));
940
+ }
941
+ function createAnalyzeIncrementCategorySummary(items) {
942
+ const map = /* @__PURE__ */ new Map();
943
+ for (const item of items) {
944
+ const entry = map.get(item.category) ?? {
945
+ category: item.category,
946
+ count: 0,
947
+ deltaBytes: 0
948
+ };
949
+ entry.count += 1;
950
+ entry.deltaBytes += item.deltaBytes;
951
+ map.set(item.category, entry);
952
+ }
953
+ return [...map.values()].sort((a, b) => b.deltaBytes - a.deltaBytes || b.count - a.count || a.category.localeCompare(b.category));
954
+ }
955
+ function createAnalyzePrMarkdownReport(result, previousResult) {
956
+ const files = result.packages.flatMap((pkg) => pkg.files.map((file) => ({
957
+ pkg,
958
+ file
959
+ })));
960
+ const totalBytes = files.reduce((sum, item) => sum + getFileSize(item.file), 0);
961
+ const compressedBytes = files.reduce((sum, item) => sum + getCompressedSize(item.file), 0);
962
+ const previousTotalBytes = previousResult?.packages.flatMap((pkg) => pkg.files).reduce((sum, file) => sum + getFileSize(file), 0);
963
+ const incrementItems = createAnalyzeIncrementAttribution(result, previousResult);
964
+ const incrementSummary = createAnalyzeIncrementCategorySummary(incrementItems);
965
+ const duplicateInsights = createDuplicateModuleInsights(result);
966
+ const budgetItems = createAnalyzeBudgetCheck(result);
967
+ const budgetIssues = budgetItems.filter((item) => item.status !== "ok");
968
+ const actionItems = createActionItems({
969
+ budgetItems,
970
+ duplicateInsights
971
+ }).slice(0, 3);
972
+ const budgetRows = budgetIssues.slice(0, 5).map((item) => `| ${item.label} | ${formatAnalyzeBytes(item.currentBytes)} | ${formatAnalyzeBytes(item.limitBytes)} | ${formatBudgetStatus(item)} |`).join("\n");
973
+ const incrementRows = incrementItems.slice(0, 8).map((item) => `| ${item.label} | ${item.category} | ${item.packageLabel} | ${formatAnalyzeBytes(item.deltaBytes)} | ${item.advice} |`).join("\n");
974
+ const sourceRows = incrementSummary.slice(0, 6).map((item) => `| ${item.category} | ${item.count} | ${formatAnalyzeBytes(item.deltaBytes)} |`).join("\n");
975
+ const duplicateRows = duplicateInsights.slice(0, 5).map((module) => `| ${module.source} | ${module.packageCount} | ${formatAnalyzeBytes(module.estimatedSavingBytes)} | ${module.advice} |`).join("\n");
976
+ return [
977
+ "## weapp-vite analyze PR 摘要",
978
+ "",
979
+ `- 总产物体积:${formatAnalyzeBytes(totalBytes)}(较上次 ${formatDelta(typeof previousTotalBytes === "number" ? totalBytes - previousTotalBytes : void 0)})`,
980
+ `- 压缩后体积:${formatAnalyzeBytes(compressedBytes)}`,
981
+ `- 预算告警:${budgetIssues.length}`,
982
+ `- 增量归因:${incrementItems.length > 0 ? `${incrementItems.length} 项正向增长` : "无正向增长"}`,
983
+ `- 跨包复用:${duplicateInsights.length}`,
984
+ "",
985
+ "### 建议动作",
986
+ "",
987
+ ...actionItems.map((item) => `- ${item}`),
988
+ "",
989
+ "### 预算状态",
990
+ "",
991
+ "| 对象 | 当前体积 | 预算 | 状态 |",
992
+ "| --- | ---: | ---: | --- |",
993
+ budgetRows || "| - | 0 B | 0 B | 正常 |",
994
+ "",
995
+ "### 增量来源",
996
+ "",
997
+ "| 来源 | 项数 | 增量 |",
998
+ "| --- | ---: | ---: |",
999
+ sourceRows || "| - | 0 | 0 B |",
1000
+ "",
1001
+ "### Top 增量",
1002
+ "",
1003
+ "| 文件/模块 | 来源 | 包 | 增量 | 建议 |",
1004
+ "| --- | --- | --- | ---: | --- |",
1005
+ incrementRows || "| - | - | - | 0 B | - |",
1006
+ "",
1007
+ "### 重复模块",
1008
+ "",
1009
+ "| 模块 | 包数量 | 估算可节省 | 建议 |",
1010
+ "| --- | ---: | ---: | --- |",
1011
+ duplicateRows || "| - | 0 | 0 B | - |",
1012
+ ""
1013
+ ].join("\n");
1014
+ }
1015
+ function createAnalyzeMarkdownReport(result, previousResult) {
1016
+ const files = result.packages.flatMap((pkg) => pkg.files.map((file) => ({
1017
+ pkg,
1018
+ file
1019
+ })));
1020
+ const totalBytes = files.reduce((sum, item) => sum + getFileSize(item.file), 0);
1021
+ const compressedBytes = files.reduce((sum, item) => sum + getCompressedSize(item.file), 0);
1022
+ const duplicateInsights = createDuplicateModuleInsights(result);
1023
+ const incrementItems = createAnalyzeIncrementAttribution(result, previousResult);
1024
+ const incrementSummary = createAnalyzeIncrementCategorySummary(incrementItems);
1025
+ const budgetItems = createAnalyzeBudgetCheck(result);
1026
+ const previousTotalBytes = previousResult?.packages.flatMap((pkg) => pkg.files).reduce((sum, file) => sum + getFileSize(file), 0);
1027
+ const previousPackageSizes = createPackageSizeMap(previousResult);
1028
+ const budgets = result.metadata?.budgets;
1029
+ const budgetIssues = budgetItems.filter((item) => item.status !== "ok");
1030
+ const actionItems = createActionItems({
1031
+ budgetItems,
1032
+ duplicateInsights
1033
+ });
1034
+ const packageRows = result.packages.map((pkg) => {
1035
+ const size = pkg.files.reduce((sum, file) => sum + getFileSize(file), 0);
1036
+ const compressed = pkg.files.reduce((sum, file) => sum + getCompressedSize(file), 0);
1037
+ const previousSize = previousPackageSizes.get(pkg.id);
1038
+ const budgetStatus = budgetItems.find((item) => item.id === pkg.id);
1039
+ return `| ${pkg.label} | ${pkg.type} | ${formatAnalyzeBytes(size)} | ${formatAnalyzeBytes(compressed)} | ${formatDelta(typeof previousSize === "number" ? size - previousSize : void 0)} | ${budgetStatus ? formatBudgetStatus(budgetStatus) : "正常"} |`;
1040
+ }).join("\n");
1041
+ const topFileRows = files.sort((a, b) => getFileSize(b.file) - getFileSize(a.file) || a.file.file.localeCompare(b.file.file)).slice(0, 10).map((item) => `| ${item.file.file} | ${item.pkg.label} | ${item.file.type} | ${formatAnalyzeBytes(getFileSize(item.file))} | ${formatAnalyzeBytes(getCompressedSize(item.file))} |`).join("\n");
1042
+ const duplicateRows = duplicateInsights.slice(0, 10).map((module) => `| ${module.source} | ${module.sourceType} | ${module.packageCount} | ${formatAnalyzeBytes(module.estimatedSavingBytes)} | ${module.advice} |`).join("\n");
1043
+ const budgetRows = budgetIssues.map((item) => `| ${item.label} | ${item.scope} | ${formatAnalyzeBytes(item.currentBytes)} | ${formatAnalyzeBytes(item.limitBytes)} | ${formatBudgetStatus(item)} |`).join("\n");
1044
+ const incrementRows = incrementItems.slice(0, 10).map((item) => `| ${item.label} | ${item.category} | ${item.packageLabel} | ${formatAnalyzeBytes(item.deltaBytes)} | ${item.advice} |`).join("\n");
1045
+ const incrementSummaryRows = incrementSummary.slice(0, 8).map((item) => `| ${item.category} | ${item.count} | ${formatAnalyzeBytes(item.deltaBytes)} |`).join("\n");
1046
+ return [
1047
+ "# weapp-vite analyze 报告",
1048
+ "",
1049
+ `生成时间:${result.metadata?.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString()}`,
1050
+ "",
1051
+ "## 本次变化摘要",
1052
+ "",
1053
+ `- 总产物体积:${formatAnalyzeBytes(totalBytes)}`,
1054
+ `- 压缩后体积:${formatAnalyzeBytes(compressedBytes)}`,
1055
+ `- 较上次:${formatDelta(typeof previousTotalBytes === "number" ? totalBytes - previousTotalBytes : void 0)}`,
1056
+ `- 包体数量:${result.packages.length}`,
1057
+ `- 源码模块:${result.modules.length}`,
1058
+ `- 跨包复用:${duplicateInsights.length}`,
1059
+ `- 预算来源:${budgets?.source === "config" ? "配置" : "默认"}`,
1060
+ "",
1061
+ "## 预算告警",
1062
+ "",
1063
+ "| 对象 | 范围 | 当前体积 | 预算 | 状态 |",
1064
+ "| --- | --- | ---: | ---: | --- |",
1065
+ budgetRows || "| - | - | 0 B | 0 B | 正常 |",
1066
+ "",
1067
+ "## 建议动作",
1068
+ "",
1069
+ ...actionItems.map((item) => `- ${item}`),
1070
+ "",
1071
+ "## 增量归因",
1072
+ "",
1073
+ "| 来源 | 项数 | 增量 |",
1074
+ "| --- | ---: | ---: |",
1075
+ incrementSummaryRows || "| - | 0 | 0 B |",
1076
+ "",
1077
+ "| 文件/模块 | 来源 | 包 | 增量 | 建议 |",
1078
+ "| --- | --- | --- | ---: | --- |",
1079
+ incrementRows || "| - | - | - | 0 B | - |",
1080
+ "",
1081
+ "## 包体预算",
1082
+ "",
1083
+ "| 包 | 类型 | 体积 | 压缩后 | 较上次 | 预算 |",
1084
+ "| --- | --- | ---: | ---: | ---: | --- |",
1085
+ packageRows || "| - | - | 0 B | 0 B | 无变化 | 正常 |",
1086
+ "",
1087
+ "## Top 文件",
1088
+ "",
1089
+ "| 文件 | 包 | 类型 | 体积 | 压缩后 |",
1090
+ "| --- | --- | --- | ---: | ---: |",
1091
+ topFileRows || "| - | - | - | 0 B | 0 B |",
1092
+ "",
1093
+ "## 重复模块",
1094
+ "",
1095
+ "| 模块 | 来源 | 包数量 | 估算可节省 | 建议 |",
1096
+ "| --- | --- | ---: | ---: | --- |",
1097
+ duplicateRows || "| - | - | 0 | 0 B | - |",
1098
+ ""
1099
+ ].join("\n");
422
1100
  }
423
1101
  //#endregion
424
1102
  //#region src/cli/analyze/dashboard.ts
425
1103
  const ANALYZE_GLOBAL_KEY = "__WEAPP_VITE_ANALYZE_RESULT__";
1104
+ const PREVIOUS_ANALYZE_GLOBAL_KEY = "__WEAPP_VITE_PREVIOUS_ANALYZE_RESULT__";
426
1105
  const DASHBOARD_EVENTS_GLOBAL_KEY = "__WEAPP_VITE_DASHBOARD_EVENTS__";
427
1106
  const ANALYZE_DASHBOARD_PACKAGE_NAME = "@weapp-vite/dashboard";
428
1107
  const ANALYZE_SSE_PATH = "/__weapp_vite_analyze";
1108
+ const FILE_CONTENT_PATH = "/__weapp_vite_file_content";
429
1109
  const DASHBOARD_EVENT_NAME = "weapp-dashboard:event";
1110
+ const MAX_FILE_CONTENT_BYTES = 2 * 1024 * 1024;
430
1111
  const require = createRequire(import.meta.url);
431
1112
  function createInstallCommand(agent) {
432
1113
  const resolved = resolveCommand(agent ?? "npm", "install", [ANALYZE_DASHBOARD_PACKAGE_NAME]);
@@ -462,6 +1143,18 @@ function resolveDashboardDistRoot(packageRoot, manifest) {
462
1143
  if (!fs$1.existsSync(distRoot)) return;
463
1144
  return { root: distRoot };
464
1145
  }
1146
+ function resolveDashboardDevRoot(packageRoot, manifest) {
1147
+ const devRoot = manifest?.weappViteDashboard?.devRoot;
1148
+ const devConfigFile = manifest?.weappViteDashboard?.devConfigFile;
1149
+ if (!devRoot || !devConfigFile) return;
1150
+ const root = path.resolve(packageRoot, devRoot);
1151
+ const configFile = path.resolve(root, devConfigFile);
1152
+ if (!fs$1.existsSync(root) || !fs$1.existsSync(configFile)) return;
1153
+ return {
1154
+ root,
1155
+ configFile
1156
+ };
1157
+ }
465
1158
  function resolveDashboardRoot(options) {
466
1159
  const resolvePaths = options?.cwd && options.cwd !== process.cwd() ? [options.cwd, process.cwd()] : options?.cwd ? [options.cwd] : void 0;
467
1160
  let dashboardPackageRoot;
@@ -475,18 +1168,121 @@ function resolveDashboardRoot(options) {
475
1168
  dashboardManifest = void 0;
476
1169
  }
477
1170
  if (dashboardPackageRoot) {
1171
+ const devResolved = resolveDashboardDevRoot(dashboardPackageRoot, dashboardManifest);
1172
+ if (devResolved) return devResolved;
478
1173
  const distResolved = resolveDashboardDistRoot(dashboardPackageRoot, dashboardManifest);
479
1174
  if (distResolved) return distResolved;
480
1175
  }
481
1176
  logger_default.warn(`[weapp-vite ui] 未安装可选仪表盘包 ${colors.bold(colors.green(ANALYZE_DASHBOARD_PACKAGE_NAME))},已自动降级关闭 dashboard 能力。`);
482
1177
  logger_default.info(`如需启用,请执行 ${colors.bold(colors.green(createInstallCommand(options?.packageManagerAgent)))}`);
483
1178
  }
484
- function createAnalyzeHtmlPlugin(state, runtimeEvents, onServerInstance, onBroadcastReady) {
1179
+ function normalizeDashboardRelativePath(value) {
1180
+ return value.replaceAll("\\", "/");
1181
+ }
1182
+ function stripDashboardFileQuery(value) {
1183
+ const queryIndex = value.indexOf("?");
1184
+ return queryIndex === -1 ? value : value.slice(0, queryIndex);
1185
+ }
1186
+ function addDashboardAllowedPath(paths, value) {
1187
+ if (!value || value.includes("\0")) return;
1188
+ const normalizedPath = normalizeDashboardRelativePath(stripDashboardFileQuery(value));
1189
+ if (!normalizedPath || path.isAbsolute(normalizedPath)) return;
1190
+ paths.add(normalizedPath);
1191
+ }
1192
+ function createDashboardContentAllowlist(result) {
1193
+ const artifactPaths = /* @__PURE__ */ new Set();
1194
+ const sourcePaths = /* @__PURE__ */ new Set();
1195
+ for (const packageReport of result.packages) for (const file of packageReport.files) {
1196
+ addDashboardAllowedPath(artifactPaths, file.file);
1197
+ addDashboardAllowedPath(sourcePaths, file.source);
1198
+ for (const module of file.modules ?? []) addDashboardAllowedPath(sourcePaths, module.source);
1199
+ }
1200
+ return {
1201
+ artifactPaths,
1202
+ sourcePaths
1203
+ };
1204
+ }
1205
+ function resolveDashboardContentPath(root, requestPath, options) {
1206
+ if (!root || !requestPath || requestPath.includes("\0")) return;
1207
+ const normalizedRequestPath = normalizeDashboardRelativePath(stripDashboardFileQuery(requestPath));
1208
+ if (path.isAbsolute(normalizedRequestPath)) return;
1209
+ if (!options.allowedPaths.has(normalizedRequestPath)) return;
1210
+ const resolvedRoot = path.resolve(root);
1211
+ const absolutePath = path.resolve(resolvedRoot, normalizedRequestPath);
1212
+ const relativePath = path.relative(resolvedRoot, absolutePath);
1213
+ if (!relativePath) return;
1214
+ if (!options.allowParent && (relativePath.startsWith("..") || path.isAbsolute(relativePath))) return;
1215
+ return {
1216
+ absolutePath,
1217
+ relativePath: options.allowParent ? normalizedRequestPath : normalizeDashboardRelativePath(relativePath)
1218
+ };
1219
+ }
1220
+ function resolveDashboardFileLanguage(filePath) {
1221
+ const extension = path.extname(filePath).toLowerCase();
1222
+ if (extension === ".js" || extension === ".mjs" || extension === ".cjs" || extension === ".wxs" || extension === ".sjs") return "javascript";
1223
+ if (extension === ".ts" || extension === ".mts" || extension === ".cts") return "typescript";
1224
+ if (extension === ".json" || extension === ".map") return "json";
1225
+ if (extension === ".css" || extension === ".wxss" || extension === ".scss" || extension === ".sass" || extension === ".less") return "css";
1226
+ if (extension === ".vue" || extension === ".wxml" || extension === ".html") return "html";
1227
+ return "plaintext";
1228
+ }
1229
+ function sendDashboardJson(res, statusCode, payload) {
1230
+ res.statusCode = statusCode;
1231
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
1232
+ res.end(JSON.stringify(payload));
1233
+ }
1234
+ async function sendDashboardFileContent(res, roots, allowlist, kind, requestPath) {
1235
+ const resolved = resolveDashboardContentPath(kind === "artifact" ? roots.artifactRoot : kind === "source" ? roots.sourceRoot : void 0, requestPath, {
1236
+ allowParent: kind === "source",
1237
+ allowedPaths: kind === "artifact" ? allowlist.artifactPaths : allowlist.sourcePaths
1238
+ });
1239
+ if (!resolved || kind !== "source" && kind !== "artifact") {
1240
+ sendDashboardJson(res, 400, {
1241
+ error: "invalid_request",
1242
+ message: "必须传入合法的 kind 和相对路径。"
1243
+ });
1244
+ return;
1245
+ }
1246
+ try {
1247
+ const stat = await fs$1.promises.stat(resolved.absolutePath);
1248
+ if (!stat.isFile()) {
1249
+ sendDashboardJson(res, 400, {
1250
+ error: "not_file",
1251
+ message: "目标路径不是文件。"
1252
+ });
1253
+ return;
1254
+ }
1255
+ if (stat.size > MAX_FILE_CONTENT_BYTES) {
1256
+ sendDashboardJson(res, 413, {
1257
+ error: "file_too_large",
1258
+ message: `文件超过 ${MAX_FILE_CONTENT_BYTES} 字节,已拒绝读取。`
1259
+ });
1260
+ return;
1261
+ }
1262
+ sendDashboardJson(res, 200, {
1263
+ kind,
1264
+ path: resolved.relativePath,
1265
+ language: resolveDashboardFileLanguage(resolved.relativePath),
1266
+ size: stat.size,
1267
+ content: await fs$1.promises.readFile(resolved.absolutePath, "utf8")
1268
+ });
1269
+ } catch (error) {
1270
+ const code = typeof error === "object" && error && "code" in error ? String(error.code) : "";
1271
+ sendDashboardJson(res, code === "ENOENT" ? 404 : 500, {
1272
+ error: code === "ENOENT" ? "not_found" : "read_failed",
1273
+ message: code === "ENOENT" ? "文件不存在。" : "读取文件失败。"
1274
+ });
1275
+ }
1276
+ }
1277
+ function createAnalyzeHtmlPlugin(state, runtimeEvents, contentRoots, contentAllowlist, onServerInstance, onBroadcastReady) {
485
1278
  const sseClients = /* @__PURE__ */ new Set();
486
1279
  const hotBridgeScript = `
487
1280
  const applyAnalyzePayload = (payload) => {
488
- window.${ANALYZE_GLOBAL_KEY} = payload
489
- window.dispatchEvent(new CustomEvent('weapp-analyze:update', { detail: payload }))
1281
+ const current = payload?.current ?? payload
1282
+ const previous = payload?.previous ?? window.${PREVIOUS_ANALYZE_GLOBAL_KEY} ?? null
1283
+ window.${ANALYZE_GLOBAL_KEY} = current
1284
+ window.${PREVIOUS_ANALYZE_GLOBAL_KEY} = previous
1285
+ window.dispatchEvent(new CustomEvent('weapp-analyze:update', { detail: { current, previous } }))
490
1286
  }
491
1287
  const applyDashboardEvents = (payload) => {
492
1288
  const events = Array.isArray(payload) ? payload : [payload]
@@ -532,6 +1328,11 @@ function createAnalyzeHtmlPlugin(state, runtimeEvents, onServerInstance, onBroad
532
1328
  children: `window.${ANALYZE_GLOBAL_KEY} = ${JSON.stringify(state.current)}`,
533
1329
  injectTo: "head-prepend"
534
1330
  },
1331
+ {
1332
+ tag: "script",
1333
+ children: `window.${PREVIOUS_ANALYZE_GLOBAL_KEY} = ${JSON.stringify(state.previous)}`,
1334
+ injectTo: "head-prepend"
1335
+ },
535
1336
  {
536
1337
  tag: "script",
537
1338
  children: `window.${DASHBOARD_EVENTS_GLOBAL_KEY} = ${JSON.stringify(runtimeEvents.current)}`,
@@ -557,7 +1358,12 @@ function createAnalyzeHtmlPlugin(state, runtimeEvents, onServerInstance, onBroad
557
1358
  configureServer(server) {
558
1359
  onServerInstance(server);
559
1360
  server.middlewares.use((req, res, next) => {
560
- if (req.url !== ANALYZE_SSE_PATH) {
1361
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
1362
+ if (url.pathname === FILE_CONTENT_PATH) {
1363
+ sendDashboardFileContent(res, contentRoots, contentAllowlist.current, url.searchParams.get("kind"), url.searchParams.get("path"));
1364
+ return;
1365
+ }
1366
+ if (url.pathname !== ANALYZE_SSE_PATH) {
561
1367
  next();
562
1368
  return;
563
1369
  }
@@ -565,7 +1371,7 @@ function createAnalyzeHtmlPlugin(state, runtimeEvents, onServerInstance, onBroad
565
1371
  res.setHeader("Content-Type", "text/event-stream");
566
1372
  res.setHeader("Cache-Control", "no-cache, no-transform");
567
1373
  res.setHeader("Connection", "keep-alive");
568
- res.write(`data: ${JSON.stringify(state.current)}\n\n`);
1374
+ res.write(`data: ${JSON.stringify(state)}\n\n`);
569
1375
  sseClients.add(res);
570
1376
  req.on("close", () => {
571
1377
  sseClients.delete(res);
@@ -604,17 +1410,24 @@ async function startAnalyzeDashboard(result, options) {
604
1410
  const resolved = resolveDashboardRoot(options);
605
1411
  if (!resolved) return;
606
1412
  const { root, configFile } = resolved;
607
- const state = { current: result };
1413
+ const state = {
1414
+ current: result,
1415
+ previous: options?.previousResult ?? null
1416
+ };
1417
+ const contentAllowlist = { current: createDashboardContentAllowlist(result) };
608
1418
  const runtimeEvents = { current: [createDashboardRuntimeEvent({
609
1419
  kind: "command",
610
1420
  level: "success",
611
1421
  title: options?.watch ? "dashboard watch session started" : "dashboard static session started",
612
1422
  detail: options?.watch ? "weapp-vite UI 已进入实时分析模式,后续 analyze 结果会继续推送到 dashboard。" : "weapp-vite UI 已进入静态分析模式,当前页面展示的是一次性分析结果。",
613
1423
  tags: options?.watch ? ["watch", "analyze"] : ["static", "analyze"]
614
- })] };
1424
+ }), ...(options?.initialEvents ?? []).map((event) => createDashboardRuntimeEvent(event))] };
615
1425
  let serverRef;
616
1426
  let broadcastAnalyzeResult;
617
- const plugins = [createAnalyzeHtmlPlugin(state, runtimeEvents, (server) => {
1427
+ const plugins = [createAnalyzeHtmlPlugin(state, runtimeEvents, {
1428
+ artifactRoot: options?.artifactRoot ?? (options?.cwd ? path.resolve(options.cwd, "dist") : void 0),
1429
+ sourceRoot: options?.cwd
1430
+ }, contentAllowlist, (server) => {
618
1431
  serverRef = server;
619
1432
  }, (broadcast) => {
620
1433
  broadcastAnalyzeResult = broadcast;
@@ -648,7 +1461,7 @@ async function startAnalyzeDashboard(result, options) {
648
1461
  serverRef.ws.send({
649
1462
  type: "custom",
650
1463
  event: "weapp-analyze:update",
651
- data: state.current
1464
+ data: state
652
1465
  });
653
1466
  serverRef.ws.send({
654
1467
  type: "custom",
@@ -656,7 +1469,7 @@ async function startAnalyzeDashboard(result, options) {
656
1469
  data: runtimeEvents.current
657
1470
  });
658
1471
  }
659
- broadcastAnalyzeResult?.(state.current);
1472
+ broadcastAnalyzeResult?.(state);
660
1473
  const emitRuntimeEvents = (events) => {
661
1474
  if (events.length === 0) return;
662
1475
  const nextEvents = events.map((event) => createDashboardRuntimeEvent(event));
@@ -668,8 +1481,10 @@ async function startAnalyzeDashboard(result, options) {
668
1481
  });
669
1482
  };
670
1483
  const handle = {
671
- async update(nextResult) {
1484
+ async update(nextResult, previousResult) {
1485
+ state.previous = previousResult ?? state.current;
672
1486
  state.current = nextResult;
1487
+ contentAllowlist.current = createDashboardContentAllowlist(nextResult);
673
1488
  emitRuntimeEvents([{
674
1489
  kind: "build",
675
1490
  level: "info",
@@ -680,9 +1495,9 @@ async function startAnalyzeDashboard(result, options) {
680
1495
  if (serverRef) serverRef.ws.send({
681
1496
  type: "custom",
682
1497
  event: "weapp-analyze:update",
683
- data: nextResult
1498
+ data: state
684
1499
  });
685
- broadcastAnalyzeResult?.(nextResult);
1500
+ broadcastAnalyzeResult?.(state);
686
1501
  },
687
1502
  emitRuntimeEvents,
688
1503
  waitForExit: () => waitPromise,
@@ -870,6 +1685,13 @@ function printAnalysisSummary(result) {
870
1685
  logger_default.info(`- ${segments.join(",")}`);
871
1686
  }
872
1687
  }
1688
+ const componentUsages = result.components ?? [];
1689
+ if (componentUsages.length > 0) {
1690
+ const suggestions = componentUsages.flatMap((component) => component.suggestions);
1691
+ logger_default.info(`组件依赖:${componentUsages.length} 个组件,${suggestions.length} 条分包优化建议`);
1692
+ for (const suggestion of suggestions.slice(0, 5)) logger_default.info(`- ${suggestion.message}`);
1693
+ if (suggestions.length > 5) logger_default.info(`- …其余 ${suggestions.length - 5} 条组件建议请使用 ${colors.bold(colors.green("weapp-vite analyze --json"))} 查看`);
1694
+ }
873
1695
  const duplicates = result.modules.filter((module) => module.packages.length > 1);
874
1696
  if (duplicates.length === 0) {
875
1697
  logger_default.info("未检测到跨包复用的源码模块。");
@@ -886,6 +1708,16 @@ function printAnalysisSummary(result) {
886
1708
  }
887
1709
  if (duplicates.length > limit) logger_default.info(`- …其余 ${duplicates.length - limit} 项请使用 ${colors.bold(colors.green("weapp-vite analyze --json"))} 查看`);
888
1710
  }
1711
+ function printBudgetCheckSummary(result) {
1712
+ const exceededItems = createAnalyzeBudgetCheck(result).filter((item) => item.status === "exceeded");
1713
+ if (exceededItems.length === 0) {
1714
+ logger_default.success("包体预算检查通过");
1715
+ return false;
1716
+ }
1717
+ logger_default.error(`包体预算检查失败:${exceededItems.length} 项超限`);
1718
+ for (const item of exceededItems) logger_default.error(`- ${item.label}:${formatAnalyzeBytes(item.currentBytes)} / ${formatAnalyzeBytes(item.limitBytes)} (${(item.ratio * 100).toFixed(1)}%)`);
1719
+ return true;
1720
+ }
889
1721
  function printWebAnalysisSummary(result) {
890
1722
  logger_default.success("Web 静态分析完成");
891
1723
  logger_default.info(`- 配置状态:${result.web.enabled ? "已启用 weapp.web" : "未启用 weapp.web"}`);
@@ -899,12 +1731,13 @@ function printWebAnalysisSummary(result) {
899
1731
  logger_default.warn(`- 未支持范围:${result.unsupportedScopes.join(";")}`);
900
1732
  for (const limitation of result.limitations) logger_default.warn(`- 限制:${limitation}`);
901
1733
  }
902
- async function writeAnalyzeResult(result, outputOption, configService) {
1734
+ async function writeAnalyzeResult(result, outputOption, configService, format = "json", previousResult) {
903
1735
  if (!outputOption) return;
904
1736
  const baseDir = configService.cwd;
905
1737
  const resolvedOutputPath = path.isAbsolute(outputOption) ? outputOption : path.resolve(baseDir, outputOption);
906
1738
  await fs.ensureDir(path.dirname(resolvedOutputPath));
907
- await fs.writeFile(resolvedOutputPath, `${JSON.stringify(result, null, 2)}\n`, "utf8");
1739
+ const content = format === "markdown" && "packages" in result ? createAnalyzeMarkdownReport(result, previousResult) : format === "pr" && "packages" in result ? createAnalyzePrMarkdownReport(result, previousResult) : JSON.stringify(result, null, 2);
1740
+ await fs.writeFile(resolvedOutputPath, `${content}\n`, "utf8");
908
1741
  const relativeOutput = configService.relativeCwd(resolvedOutputPath);
909
1742
  logger_default.success(`分析结果已写入 ${colors.green(relativeOutput)}`);
910
1743
  return resolvedOutputPath;
@@ -944,10 +1777,15 @@ function printHmrProfileAnalysisSummary(result, configService) {
944
1777
  }
945
1778
  }
946
1779
  function registerAnalyzeCommand(cli) {
947
- cli.command("analyze [root]", "analyze 两端包体与源码映射").option("--hmr-profile [file]", `[string | boolean] 分析 HMR JSONL profile,省略值时优先读取配置,否则回退到默认路径`).option("--json", `[boolean] 输出 JSON 结果`).option("--output <file>", `[string] 将分析结果写入指定文件(JSON)`).option("-p, --platform <platform>", `[string] target platform (weapp | h5)`).option("--project-config <path>", `[string] project config path (miniprogram only)`).action(async (root, options) => {
1780
+ cli.command("analyze [root]", "analyze 两端包体与源码映射").option("--hmr-profile [file]", `[string | boolean] 分析 HMR JSONL profile,省略值时优先读取配置,否则回退到默认路径`).option("--json", `[boolean] 输出 JSON 结果`).option("--markdown", `[boolean] 输出 Markdown 报告`).option("--report <type>", `[string] 输出指定报告类型(pr)`).option("--budget-check", `[boolean] 检查 analyze 预算,超过预算时返回非 0 退出码`).option("--output <file>", `[string] 将分析结果写入指定文件(JSON 或 Markdown)`).option("-p, --platform <platform>", `[string] target platform (weapp | h5)`).option("--project-config <path>", `[string] project config path (miniprogram only)`).action(async (root, options) => {
948
1781
  filterDuplicateOptions(options);
949
1782
  const configFile = resolveConfigFile(options);
950
1783
  const outputJson = coerceBooleanOption(options.json);
1784
+ const outputMarkdown = coerceBooleanOption(options.markdown);
1785
+ const reportType = typeof options.report === "string" ? options.report.trim() : "";
1786
+ const outputPrReport = reportType === "pr";
1787
+ if (reportType && !outputPrReport) throw new Error(`不支持的 analyze report 类型:${reportType}`);
1788
+ const budgetCheck = coerceBooleanOption(options.budgetCheck);
951
1789
  const targets = resolveRuntimeTargets(options);
952
1790
  const inlineConfig = createInlineConfig(targets.mpPlatform);
953
1791
  try {
@@ -960,7 +1798,7 @@ function registerAnalyzeCommand(cli) {
960
1798
  projectConfigPath: options.projectConfig
961
1799
  });
962
1800
  logRuntimeTarget(targets, {
963
- silent: outputJson,
1801
+ silent: outputJson || outputMarkdown,
964
1802
  resolvedConfigPlatform: ctx.configService.platform
965
1803
  });
966
1804
  const outputOption = typeof options.output === "string" ? options.output.trim() : "";
@@ -991,15 +1829,26 @@ function registerAnalyzeCommand(cli) {
991
1829
  logger_default.warn("当前命令不支持该平台,请通过 --platform weapp 或 --platform h5 指定目标。");
992
1830
  return;
993
1831
  }
1832
+ const previousResult = await readLatestAnalyzeHistorySnapshot(ctx.configService);
994
1833
  const result = await analyzeSubpackages(ctx);
995
- const writtenPath = await writeAnalyzeResult(result, outputOption, ctx.configService);
996
- if (outputJson) {
1834
+ await writeAnalyzeHistorySnapshot(result, ctx.configService);
1835
+ const writtenPath = await writeAnalyzeResult(result, outputOption, ctx.configService, outputPrReport ? "pr" : outputMarkdown ? "markdown" : "json", previousResult);
1836
+ if (outputPrReport) {
1837
+ if (!writtenPath) process.stdout.write(`${createAnalyzePrMarkdownReport(result, previousResult)}\n`);
1838
+ } else if (outputMarkdown) {
1839
+ if (!writtenPath) process.stdout.write(`${createAnalyzeMarkdownReport(result, previousResult)}\n`);
1840
+ } else if (outputJson) {
997
1841
  if (!writtenPath) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
998
- } else {
1842
+ }
1843
+ if (budgetCheck ? printBudgetCheckSummary(result) : false) process.exitCode = 1;
1844
+ if (budgetCheck) return;
1845
+ if (!outputPrReport && !outputMarkdown && !outputJson) {
999
1846
  printAnalysisSummary(result);
1000
1847
  await startAnalyzeDashboard(result, {
1848
+ artifactRoot: ctx.configService.outDir,
1001
1849
  cwd: ctx.configService.cwd,
1002
- packageManagerAgent: ctx.configService.packageManager.agent
1850
+ packageManagerAgent: ctx.configService.packageManager.agent,
1851
+ previousResult
1003
1852
  });
1004
1853
  }
1005
1854
  } catch (error) {
@@ -1541,20 +2390,33 @@ function registerBuildCommand(cli) {
1541
2390
  warningBytes: configService.weappViteConfig.packageSizeWarningBytes
1542
2391
  });
1543
2392
  if (enableAnalyze) {
2393
+ const analyzeStartedAt = Date.now();
2394
+ const previousAnalyzeResult = await readLatestAnalyzeHistorySnapshot(configService);
1544
2395
  const analyzeResult = await analyzeSubpackages(ctx);
2396
+ await writeAnalyzeHistorySnapshot(analyzeResult, configService);
2397
+ const analyzeDurationMs = Date.now() - analyzeStartedAt;
1545
2398
  analyzeHandle = await startAnalyzeDashboard(analyzeResult, {
1546
2399
  watch: true,
2400
+ artifactRoot: configService.outDir,
1547
2401
  cwd: configService.cwd,
1548
- packageManagerAgent: configService.packageManager.agent
2402
+ packageManagerAgent: configService.packageManager.agent,
2403
+ previousResult: previousAnalyzeResult,
2404
+ initialEvents: [{
2405
+ kind: "build",
2406
+ level: "success",
2407
+ title: "mini build completed",
2408
+ detail: `生产构建已完成,当前 analyze 结果包含 ${analyzeResult.packages.length} 个包。`,
2409
+ durationMs: miniBuildDurationMs,
2410
+ tags: ["build", "mini"]
2411
+ }, {
2412
+ kind: "build",
2413
+ level: "success",
2414
+ title: "analyze completed",
2415
+ detail: `分析已完成,当前包含 ${analyzeResult.packages.length} 个包与 ${analyzeResult.modules.length} 个模块。`,
2416
+ durationMs: analyzeDurationMs,
2417
+ tags: ["build", "analyze"]
2418
+ }]
1549
2419
  }) ?? void 0;
1550
- emitDashboardEvents$1(analyzeHandle, [{
1551
- kind: "build",
1552
- level: "success",
1553
- title: "mini build completed",
1554
- detail: `生产构建已完成,当前 analyze 结果包含 ${analyzeResult.packages.length} 个包。`,
1555
- durationMs: miniBuildDurationMs,
1556
- tags: ["build", "mini"]
1557
- }]);
1558
2420
  }
1559
2421
  }
1560
2422
  const webConfig = configService.weappWebConfig;
@@ -2784,7 +3646,7 @@ function resolveRunnableHotkeyDefinition(input) {
2784
3646
  }
2785
3647
  //#endregion
2786
3648
  //#region package.json
2787
- var version = "6.16.1";
3649
+ var version = "6.16.2";
2788
3650
  //#endregion
2789
3651
  //#region src/cli/devHotkeys/format.ts
2790
3652
  const FULLWIDTH_ASCII_START = 65281;
@@ -3099,6 +3961,12 @@ async function collectOutputFiles(root) {
3099
3961
  }
3100
3962
  return files;
3101
3963
  }
3964
+ function getCompressedSizes(content) {
3965
+ return {
3966
+ gzipSize: gzipSync(content).byteLength,
3967
+ brotliSize: brotliCompressSync(content).byteLength
3968
+ };
3969
+ }
3102
3970
  async function analyzeUiFallback(ctx) {
3103
3971
  const { configService, scanService } = ctx;
3104
3972
  await scanService.loadAppEntry();
@@ -3136,16 +4004,19 @@ async function analyzeUiFallback(ctx) {
3136
4004
  const relativeFile = path.relative(distRoot, absoluteFile).replace(REG_DIST_POSIX_SEP, "/");
3137
4005
  const packageInfo = classifyPackage(relativeFile);
3138
4006
  const stat = await fs$2.stat(absoluteFile);
4007
+ const content = await fs$2.readFile(absoluteFile);
3139
4008
  ensurePackage(packageInfo.id, packageInfo.type).files.push({
3140
4009
  file: relativeFile,
3141
4010
  type: relativeFile.endsWith(".js") ? "chunk" : "asset",
3142
4011
  from: packageInfo.type === "independent" ? "independent" : "main",
3143
4012
  size: stat.size,
4013
+ ...getCompressedSizes(content),
3144
4014
  isEntry: relativeFile === "app.js" || REG_DIST_PAGE_ENTRY.test(relativeFile),
3145
4015
  source: relativeFile.endsWith(".js") ? void 0 : relativeFile
3146
4016
  });
3147
4017
  }
3148
4018
  return {
4019
+ metadata: createAnalyzeMetadata(configService),
3149
4020
  packages: Array.from(packages.values()).sort((a, b) => {
3150
4021
  if (a.id === "__main__") return -1;
3151
4022
  if (b.id === "__main__") return 1;
@@ -3166,6 +4037,7 @@ function createAnalyzeController(options) {
3166
4037
  let analyzeHandle;
3167
4038
  const runAnalyze = async () => {
3168
4039
  const startedAt = Date.now();
4040
+ const previousResult = await readLatestAnalyzeHistorySnapshot(configService);
3169
4041
  try {
3170
4042
  const result = await analyzeSubpackages(await createCompilerContext({
3171
4043
  key: `serve-ui-analyze:${process.pid}:${++analyzeRunId}`,
@@ -3178,23 +4050,33 @@ function createAnalyzeController(options) {
3178
4050
  projectConfigPath: cliOptions.projectConfig,
3179
4051
  syncSupportFiles: false
3180
4052
  }));
3181
- if (hasAnalyzeData(result)) return {
3182
- result,
3183
- durationMs: Date.now() - startedAt,
3184
- mode: "full"
3185
- };
4053
+ if (hasAnalyzeData(result)) {
4054
+ await writeAnalyzeHistorySnapshot(result, configService);
4055
+ return {
4056
+ result,
4057
+ previousResult,
4058
+ durationMs: Date.now() - startedAt,
4059
+ mode: "full"
4060
+ };
4061
+ }
3186
4062
  } catch (error) {
3187
4063
  const message = error instanceof Error ? error.message : String(error);
3188
4064
  logger_default.warn(`[ui] 完整分析失败,已回退到 dist 文件扫描:${message}`);
4065
+ const result = await analyzeUiFallback(ctx);
4066
+ await writeAnalyzeHistorySnapshot(result, configService);
3189
4067
  return {
3190
- result: await analyzeUiFallback(ctx),
4068
+ result,
4069
+ previousResult,
3191
4070
  durationMs: Date.now() - startedAt,
3192
4071
  mode: "fallback",
3193
4072
  fallbackReason: message
3194
4073
  };
3195
4074
  }
4075
+ const result = await analyzeUiFallback(ctx);
4076
+ await writeAnalyzeHistorySnapshot(result, configService);
3196
4077
  return {
3197
- result: await analyzeUiFallback(ctx),
4078
+ result,
4079
+ previousResult,
3198
4080
  durationMs: Date.now() - startedAt,
3199
4081
  mode: "fallback",
3200
4082
  fallbackReason: "完整分析结果为空,已回退到 dist 文件扫描。"
@@ -3218,7 +4100,7 @@ function createAnalyzeController(options) {
3218
4100
  durationMs: next.durationMs,
3219
4101
  tags: ["analyze", "fallback"]
3220
4102
  }]);
3221
- await analyzeHandle.update(next.result);
4103
+ await analyzeHandle.update(next.result, next.previousResult);
3222
4104
  emitDashboardEvents(analyzeHandle, [{
3223
4105
  kind: next.mode === "fallback" ? "diagnostic" : "build",
3224
4106
  level: next.mode === "fallback" ? "warning" : "success",
@@ -3251,9 +4133,23 @@ function createAnalyzeController(options) {
3251
4133
  const initialAnalyze = await runAnalyze();
3252
4134
  analyzeHandle = await startDashboard(initialAnalyze.result, {
3253
4135
  watch: true,
4136
+ artifactRoot: configService.outDir,
3254
4137
  cwd: configService.cwd,
3255
4138
  packageManagerAgent: configService.packageManager.agent,
3256
- silentStartupLog: true
4139
+ silentStartupLog: true,
4140
+ previousResult: initialAnalyze.previousResult,
4141
+ initialEvents: [{
4142
+ kind: initialAnalyze.mode === "fallback" ? "diagnostic" : "build",
4143
+ level: initialAnalyze.mode === "fallback" ? "warning" : "success",
4144
+ title: initialAnalyze.mode === "fallback" ? "initial analyze fallback completed" : "initial analyze completed",
4145
+ detail: initialAnalyze.mode === "fallback" ? initialAnalyze.fallbackReason ?? "完整分析不可用,已回退到 dist 文件扫描。" : `开发态首份 analyze 结果已生成,包含 ${initialAnalyze.result.packages.length} 个包与 ${initialAnalyze.result.modules.length} 个模块。`,
4146
+ durationMs: initialAnalyze.durationMs,
4147
+ tags: initialAnalyze.mode === "fallback" ? [
4148
+ "analyze",
4149
+ "fallback",
4150
+ "initial"
4151
+ ] : ["analyze", "initial"]
4152
+ }]
3257
4153
  }) ?? void 0;
3258
4154
  emitDashboardEvents(analyzeHandle, [{
3259
4155
  kind: "command",
@@ -3413,10 +4309,23 @@ function registerServeCommand(cli) {
3413
4309
  if (targets.runMini) {
3414
4310
  const miniBuildStartedAt = Date.now();
3415
4311
  const buildResult = await buildService.build(options);
3416
- logger_default.success(`小程序初次构建完成,耗时:${formatDuration(Date.now() - miniBuildStartedAt)}`);
4312
+ const miniBuildDurationMs = Date.now() - miniBuildStartedAt;
4313
+ logger_default.success(`小程序初次构建完成,耗时:${formatDuration(miniBuildDurationMs)}`);
3417
4314
  if (enableAnalyze) {
3418
4315
  await analyzeController.startDashboard(startAnalyzeDashboard);
3419
4316
  analyzeHandle = analyzeController.getHandle();
4317
+ analyzeController.emitRuntimeEvents([{
4318
+ kind: "build",
4319
+ level: "success",
4320
+ title: "mini initial build completed",
4321
+ detail: "小程序开发态初次构建已完成,dashboard 可继续监听后续刷新。",
4322
+ durationMs: miniBuildDurationMs,
4323
+ tags: [
4324
+ "dev",
4325
+ "mini",
4326
+ "initial"
4327
+ ]
4328
+ }]);
3420
4329
  await analyzeController.bindWatcher(buildResult).runInitialUpdate();
3421
4330
  }
3422
4331
  }