weapp-vite 6.16.1 → 6.16.3

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,7 +1,7 @@
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-C775dw5P.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";
4
- import { a as resolveWeappMcpConfig, o as startWeappViteMcpServer } from "./mcp-BzcrPiku.mjs";
3
+ import { h as VERSION } from "./file-CueQM5Yi.mjs";
4
+ import { o as resolveWeappMcpConfig, s as startWeappViteMcpServer } from "./mcp-DV3K2AVD.mjs";
5
5
  import { createRequire } from "node:module";
6
6
  import path, { posix } from "pathe";
7
7
  import { defu } from "@weapp-core/shared";
@@ -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
+ })
421
668
  };
422
669
  }
423
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
+ };
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");
1100
+ }
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;
@@ -2479,6 +3341,7 @@ async function handleServer(options) {
2479
3341
  endpoint: options.endpoint,
2480
3342
  host: options.host,
2481
3343
  port: resolvePort(options.port),
3344
+ restEndpoint: options.rest === false ? false : options.restEndpoint,
2482
3345
  transport: resolvedTransport,
2483
3346
  unref: options.unref,
2484
3347
  workspaceRoot: options.workspaceRoot
@@ -2489,7 +3352,7 @@ async function handleServer(options) {
2489
3352
  })) logger_default.info(line);
2490
3353
  }
2491
3354
  function registerMcpCommand(cli) {
2492
- cli.command("mcp [...args]", "start weapp-vite MCP server or manage MCP client onboarding").option("--transport <type>", "[string] stdio | streamable-http | command | http", { default: "stdio" }).option("--host <host>", "[string] streamable-http host").option("--port <port>", "[number] streamable-http port").option("--endpoint <path>", "[string] streamable-http endpoint path").option("--unref", "[boolean] unref HTTP server to not block process exit").option("--url <url>", "[string] explicit HTTP MCP url").option("--workspace-root <path>", "[string] workspace root path, defaults to cwd").option("-y, --yes", "[boolean] write config without prompt").action(async (args, options) => {
3355
+ cli.command("mcp [...args]", "start weapp-vite MCP server or manage MCP client onboarding").option("--transport <type>", "[string] stdio | streamable-http | command | http", { default: "stdio" }).option("--host <host>", "[string] streamable-http host").option("--port <port>", "[number] streamable-http port").option("--endpoint <path>", "[string] streamable-http endpoint path").option("--rest-endpoint <path>", "[string] streamable-http REST runtime endpoint path").option("--no-rest", "[boolean] disable streamable-http REST runtime endpoints").option("--unref", "[boolean] unref HTTP server to not block process exit").option("--url <url>", "[string] explicit HTTP MCP url").option("--workspace-root <path>", "[string] workspace root path, defaults to cwd").option("-y, --yes", "[boolean] write config without prompt").action(async (args, options) => {
2493
3356
  const [subcommand, client] = args;
2494
3357
  if (subcommand === "init") {
2495
3358
  if (!client) throw new Error("缺少客户端名称,请使用:wv mcp init <codex|claude-code|cursor>");
@@ -2554,18 +3417,24 @@ function resolvePrepareRoot(input) {
2554
3417
  function formatPrepareSkipMessage$1(error) {
2555
3418
  return `跳过 .weapp-vite 支持文件预生成:${error instanceof Error ? error.message : String(error)}`;
2556
3419
  }
3420
+ function resolvePreparePlatform(options) {
3421
+ return typeof options.platform === "string" ? options.platform : typeof options.p === "string" ? options.p : void 0;
3422
+ }
2557
3423
  function registerPrepareCommand(cli) {
2558
- cli.command("prepare [...input]", "generate .weapp-vite support files").action(async (input, options) => {
3424
+ cli.command("prepare [...input]", "generate .weapp-vite support files").option("-p, --platform <platform>", `[string] target platform (weapp | h5)`).action(async (input, options) => {
2559
3425
  try {
2560
3426
  filterDuplicateOptions(options);
3427
+ const cwd = path.resolve(resolvePrepareRoot(input));
3428
+ const cliPlatform = resolvePreparePlatform(options);
2561
3429
  await syncProjectSupportFiles(await createCompilerContext({
2562
- cwd: path.resolve(resolvePrepareRoot(input)),
3430
+ cwd,
2563
3431
  isDev: false,
2564
3432
  mode: typeof options.mode === "string" ? options.mode : "development",
2565
3433
  configFile: resolveConfigFile(options),
2566
3434
  configLoader: "native",
2567
3435
  syncSupportFiles: false,
2568
- preloadAppEntry: false
3436
+ preloadAppEntry: false,
3437
+ ...cliPlatform ? { cliPlatform } : {}
2569
3438
  }));
2570
3439
  logger_default.info("已生成 .weapp-vite 支持文件。");
2571
3440
  } catch (error) {
@@ -2784,7 +3653,7 @@ function resolveRunnableHotkeyDefinition(input) {
2784
3653
  }
2785
3654
  //#endregion
2786
3655
  //#region package.json
2787
- var version = "6.16.1";
3656
+ var version = "6.16.3";
2788
3657
  //#endregion
2789
3658
  //#region src/cli/devHotkeys/format.ts
2790
3659
  const FULLWIDTH_ASCII_START = 65281;
@@ -2889,6 +3758,7 @@ function createToggleMcpAction(options) {
2889
3758
  endpoint: resolvedMcp.endpoint,
2890
3759
  host: resolvedMcp.host,
2891
3760
  port: resolvedMcp.port,
3761
+ restEndpoint: resolvedMcp.restEndpoint,
2892
3762
  transport: "streamable-http",
2893
3763
  unref: false,
2894
3764
  workspaceRoot: cwd
@@ -3099,6 +3969,12 @@ async function collectOutputFiles(root) {
3099
3969
  }
3100
3970
  return files;
3101
3971
  }
3972
+ function getCompressedSizes(content) {
3973
+ return {
3974
+ gzipSize: gzipSync(content).byteLength,
3975
+ brotliSize: brotliCompressSync(content).byteLength
3976
+ };
3977
+ }
3102
3978
  async function analyzeUiFallback(ctx) {
3103
3979
  const { configService, scanService } = ctx;
3104
3980
  await scanService.loadAppEntry();
@@ -3136,16 +4012,19 @@ async function analyzeUiFallback(ctx) {
3136
4012
  const relativeFile = path.relative(distRoot, absoluteFile).replace(REG_DIST_POSIX_SEP, "/");
3137
4013
  const packageInfo = classifyPackage(relativeFile);
3138
4014
  const stat = await fs$2.stat(absoluteFile);
4015
+ const content = await fs$2.readFile(absoluteFile);
3139
4016
  ensurePackage(packageInfo.id, packageInfo.type).files.push({
3140
4017
  file: relativeFile,
3141
4018
  type: relativeFile.endsWith(".js") ? "chunk" : "asset",
3142
4019
  from: packageInfo.type === "independent" ? "independent" : "main",
3143
4020
  size: stat.size,
4021
+ ...getCompressedSizes(content),
3144
4022
  isEntry: relativeFile === "app.js" || REG_DIST_PAGE_ENTRY.test(relativeFile),
3145
4023
  source: relativeFile.endsWith(".js") ? void 0 : relativeFile
3146
4024
  });
3147
4025
  }
3148
4026
  return {
4027
+ metadata: createAnalyzeMetadata(configService),
3149
4028
  packages: Array.from(packages.values()).sort((a, b) => {
3150
4029
  if (a.id === "__main__") return -1;
3151
4030
  if (b.id === "__main__") return 1;
@@ -3166,6 +4045,7 @@ function createAnalyzeController(options) {
3166
4045
  let analyzeHandle;
3167
4046
  const runAnalyze = async () => {
3168
4047
  const startedAt = Date.now();
4048
+ const previousResult = await readLatestAnalyzeHistorySnapshot(configService);
3169
4049
  try {
3170
4050
  const result = await analyzeSubpackages(await createCompilerContext({
3171
4051
  key: `serve-ui-analyze:${process.pid}:${++analyzeRunId}`,
@@ -3178,23 +4058,33 @@ function createAnalyzeController(options) {
3178
4058
  projectConfigPath: cliOptions.projectConfig,
3179
4059
  syncSupportFiles: false
3180
4060
  }));
3181
- if (hasAnalyzeData(result)) return {
3182
- result,
3183
- durationMs: Date.now() - startedAt,
3184
- mode: "full"
3185
- };
4061
+ if (hasAnalyzeData(result)) {
4062
+ await writeAnalyzeHistorySnapshot(result, configService);
4063
+ return {
4064
+ result,
4065
+ previousResult,
4066
+ durationMs: Date.now() - startedAt,
4067
+ mode: "full"
4068
+ };
4069
+ }
3186
4070
  } catch (error) {
3187
4071
  const message = error instanceof Error ? error.message : String(error);
3188
4072
  logger_default.warn(`[ui] 完整分析失败,已回退到 dist 文件扫描:${message}`);
4073
+ const result = await analyzeUiFallback(ctx);
4074
+ await writeAnalyzeHistorySnapshot(result, configService);
3189
4075
  return {
3190
- result: await analyzeUiFallback(ctx),
4076
+ result,
4077
+ previousResult,
3191
4078
  durationMs: Date.now() - startedAt,
3192
4079
  mode: "fallback",
3193
4080
  fallbackReason: message
3194
4081
  };
3195
4082
  }
4083
+ const result = await analyzeUiFallback(ctx);
4084
+ await writeAnalyzeHistorySnapshot(result, configService);
3196
4085
  return {
3197
- result: await analyzeUiFallback(ctx),
4086
+ result,
4087
+ previousResult,
3198
4088
  durationMs: Date.now() - startedAt,
3199
4089
  mode: "fallback",
3200
4090
  fallbackReason: "完整分析结果为空,已回退到 dist 文件扫描。"
@@ -3218,7 +4108,7 @@ function createAnalyzeController(options) {
3218
4108
  durationMs: next.durationMs,
3219
4109
  tags: ["analyze", "fallback"]
3220
4110
  }]);
3221
- await analyzeHandle.update(next.result);
4111
+ await analyzeHandle.update(next.result, next.previousResult);
3222
4112
  emitDashboardEvents(analyzeHandle, [{
3223
4113
  kind: next.mode === "fallback" ? "diagnostic" : "build",
3224
4114
  level: next.mode === "fallback" ? "warning" : "success",
@@ -3251,9 +4141,23 @@ function createAnalyzeController(options) {
3251
4141
  const initialAnalyze = await runAnalyze();
3252
4142
  analyzeHandle = await startDashboard(initialAnalyze.result, {
3253
4143
  watch: true,
4144
+ artifactRoot: configService.outDir,
3254
4145
  cwd: configService.cwd,
3255
4146
  packageManagerAgent: configService.packageManager.agent,
3256
- silentStartupLog: true
4147
+ silentStartupLog: true,
4148
+ previousResult: initialAnalyze.previousResult,
4149
+ initialEvents: [{
4150
+ kind: initialAnalyze.mode === "fallback" ? "diagnostic" : "build",
4151
+ level: initialAnalyze.mode === "fallback" ? "warning" : "success",
4152
+ title: initialAnalyze.mode === "fallback" ? "initial analyze fallback completed" : "initial analyze completed",
4153
+ detail: initialAnalyze.mode === "fallback" ? initialAnalyze.fallbackReason ?? "完整分析不可用,已回退到 dist 文件扫描。" : `开发态首份 analyze 结果已生成,包含 ${initialAnalyze.result.packages.length} 个包与 ${initialAnalyze.result.modules.length} 个模块。`,
4154
+ durationMs: initialAnalyze.durationMs,
4155
+ tags: initialAnalyze.mode === "fallback" ? [
4156
+ "analyze",
4157
+ "fallback",
4158
+ "initial"
4159
+ ] : ["analyze", "initial"]
4160
+ }]
3257
4161
  }) ?? void 0;
3258
4162
  emitDashboardEvents(analyzeHandle, [{
3259
4163
  kind: "command",
@@ -3413,10 +4317,23 @@ function registerServeCommand(cli) {
3413
4317
  if (targets.runMini) {
3414
4318
  const miniBuildStartedAt = Date.now();
3415
4319
  const buildResult = await buildService.build(options);
3416
- logger_default.success(`小程序初次构建完成,耗时:${formatDuration(Date.now() - miniBuildStartedAt)}`);
4320
+ const miniBuildDurationMs = Date.now() - miniBuildStartedAt;
4321
+ logger_default.success(`小程序初次构建完成,耗时:${formatDuration(miniBuildDurationMs)}`);
3417
4322
  if (enableAnalyze) {
3418
4323
  await analyzeController.startDashboard(startAnalyzeDashboard);
3419
4324
  analyzeHandle = analyzeController.getHandle();
4325
+ analyzeController.emitRuntimeEvents([{
4326
+ kind: "build",
4327
+ level: "success",
4328
+ title: "mini initial build completed",
4329
+ detail: "小程序开发态初次构建已完成,dashboard 可继续监听后续刷新。",
4330
+ durationMs: miniBuildDurationMs,
4331
+ tags: [
4332
+ "dev",
4333
+ "mini",
4334
+ "initial"
4335
+ ]
4336
+ }]);
3420
4337
  await analyzeController.bindWatcher(buildResult).runInitialUpdate();
3421
4338
  }
3422
4339
  }
@@ -3583,6 +4500,7 @@ async function maybeAutoStartMcpServer(argv, cliOptions) {
3583
4500
  endpoint: resolvedMcp.endpoint,
3584
4501
  host: resolvedMcp.host,
3585
4502
  port: resolvedMcp.port,
4503
+ restEndpoint: resolvedMcp.restEndpoint,
3586
4504
  quiet: true,
3587
4505
  transport: "streamable-http",
3588
4506
  unref: true,
@@ -3592,6 +4510,7 @@ async function maybeAutoStartMcpServer(argv, cliOptions) {
3592
4510
  const mcpUrl = `http://${resolvedMcp.host}:${resolvedMcp.port}${resolvedMcp.endpoint}`;
3593
4511
  logger_default.success("MCP 服务已自动启动:");
3594
4512
  logger_default.info(` ➜ ${colors.cyan(mcpUrl)}`);
4513
+ if (resolvedMcp.restEndpoint !== false) logger_default.info(` REST ➜ ${colors.cyan(`http://${resolvedMcp.host}:${resolvedMcp.port}${resolvedMcp.restEndpoint}`)}`);
3595
4514
  for (const line of formatMcpQuickStart({
3596
4515
  httpUrl: mcpUrl,
3597
4516
  transport: "http"