weapp-vite 7.4.0 → 7.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -3
- package/THIRD_PARTY_NOTICES.md +60 -0
- package/bin/bootstrap.js +14 -1
- package/dist/action-B3YZMA-h.mjs +255 -0
- package/dist/action-BELcDNNv.mjs +139 -0
- package/dist/action-BOk3vGI7.mjs +1110 -0
- package/dist/action-DlC6JYpt.mjs +95 -0
- package/dist/action-zEDY7oh_.mjs +247 -0
- package/dist/adapters-CcHrsYaj.mjs +1 -0
- package/dist/analyze-DN79YOAH.mjs +184 -0
- package/dist/artifacts-BOyC9Fy-.mjs +106 -0
- package/dist/assets-C5_YYXNk.mjs +637 -0
- package/dist/auto-import-components/resolvers.mjs +1 -2
- package/dist/auto-import-presets/wevu-router.mjs +1 -2
- package/dist/auto-import-presets/wevu.mjs +1 -2
- package/dist/auto-routes.mjs +2 -1
- package/dist/budget-CTLOHwgI.d.mts +177 -0
- package/dist/budget-EOeEycc_.mjs +65 -0
- package/dist/builtProject-C29udgXY.mjs +541 -0
- package/dist/chunkStrategy-DY_LZIIe.mjs +1448 -0
- package/dist/classifier-DBJY__kg.mjs +88 -0
- package/dist/cli-acceptance.d.mts +1 -0
- package/dist/cli-acceptance.mjs +21 -0
- package/dist/cli.mjs +519 -5049
- package/dist/compatibility.mjs +1 -2
- package/dist/{config-Dbvj5yK7.d.mts → config-Bf6DWhyb.d.mts} +220 -76
- package/dist/config.d.mts +3 -3
- package/dist/config.mjs +1 -1
- package/dist/{constants-WOsG3Eqd.mjs → constants-DEZdcsMn.mjs} +1 -1
- package/dist/{createContext-UOEgeYnA.mjs → createCompilerContextInstance-aYwi8wQt.mjs} +36862 -32020
- package/dist/createContext-tApLtII-.mjs +15 -0
- package/dist/dashboard/analyze.d.mts +48 -0
- package/dist/dashboard/analyze.mjs +3 -0
- package/dist/dashboard/vite.d.mts +1 -1
- package/dist/dashboard.d.mts +3 -2
- package/dist/dashboard.mjs +1748 -1
- package/dist/dashboardTransport-C_Y6jMp-.mjs +16 -0
- package/dist/dashboardViteBridge-DCGR43aM.mjs +302 -0
- package/dist/dev-C2fgr_Xd.mjs +67 -0
- package/dist/devProvider-D-hTfsEO.mjs +3027 -0
- package/dist/docs/README.md +59 -3
- package/dist/docs/ai-workflows.md +4 -0
- package/dist/docs/doctor.md +38 -0
- package/dist/docs/getting-started.md +3 -1
- package/dist/docs/index.md +3 -1
- package/dist/docs/mcp.md +104 -1
- package/dist/docs/testing.md +29 -2
- package/dist/docs/troubleshooting.md +16 -0
- package/dist/docs/uni-app-component-libraries.md +8 -7
- package/dist/docs/upload.md +40 -3
- package/dist/docs/vite-plugin.md +171 -0
- package/dist/docs/vue-sfc.md +42 -0
- package/dist/docs/weapp-config.md +54 -0
- package/dist/docs/wevu-authoring.md +10 -0
- package/dist/doctor-worker.d.mts +1 -0
- package/dist/doctor-worker.mjs +60 -0
- package/dist/doctor.d.mts +144 -0
- package/dist/doctor.mjs +850 -0
- package/dist/engine-C66J0HLz.mjs +54 -0
- package/dist/eslint.mjs +1 -2
- package/dist/{file-giE9UMZa.mjs → file-CcIOmqhC.mjs} +58 -24
- package/dist/file-DtcteQ4G.mjs +1 -0
- package/dist/files-BqxFIPRp.mjs +45 -0
- package/dist/forwardConsole-B0Reo8Lf.mjs +511 -0
- package/dist/getInstance-BAIaU2y9.mjs +1 -0
- package/dist/getInstance-Dt9IQDKr.mjs +28 -0
- package/dist/history-BIiRCzbq.mjs +784 -0
- package/dist/i18n.d.mts +1 -1
- package/dist/index-D5rgBzYZ.d.mts +740 -0
- package/dist/index.d.mts +12 -10
- package/dist/index.mjs +3 -2
- package/dist/json.d.mts +1 -1
- package/dist/json.mjs +1 -2
- package/dist/lib-DjccSI1B.mjs +143 -0
- package/dist/lifecycle-C36WSbQS.mjs +413 -0
- package/dist/loadConfig-BWIsDXR4.mjs +59 -0
- package/dist/loadConfig-D-BpBgsk.mjs +1 -0
- package/dist/logBuildAppFinish-ChjwATMv.mjs +57 -0
- package/dist/logger-DGn5iEVN.mjs +1 -0
- package/dist/mcp-CYgX9HpO.mjs +86 -0
- package/dist/{mcp-BG6TliEg.mjs → mcp-Cj8Maqo6.mjs} +16 -7
- package/dist/mcp.d.mts +6 -3
- package/dist/mcp.mjs +1 -2
- package/dist/mcpClient-DVLvFLZv.mjs +287 -0
- package/dist/metadata-hwF7QRcS.mjs +59 -0
- package/dist/nodeEnv-BmH8UZPR.mjs +7 -0
- package/dist/openIde-CroqOgk5.mjs +648 -0
- package/dist/options-jqFwfD1G.mjs +34 -0
- package/dist/package-Cmakyy3J.mjs +4 -0
- package/dist/path-CYYIQGS7.mjs +44 -0
- package/dist/{platform-DTWs5QRi.mjs → platform-CnkySBKz.mjs} +1 -1
- package/dist/processCleanup-BQn2kTcK.mjs +19 -0
- package/dist/protocol-Cg6Jdlvb.mjs +141 -0
- package/dist/reader-sh_POQ9-.mjs +93 -0
- package/dist/realpathScope-Tf9mz-fh.mjs +23 -0
- package/dist/report-hyonPGbZ.mjs +107 -0
- package/dist/resolvedId-CiDl7SC1.mjs +60 -0
- package/dist/{resolvers-icNxkpcy.mjs → resolvers-Dod-euqX.mjs} +4 -1
- package/dist/resolvers.mjs +1 -2
- package/dist/runtime-CLUhQsdA.mjs +43 -0
- package/dist/{runtime-Ee2HY6gR.d.mts → runtime-DpVf714f.d.mts} +1 -1
- package/dist/runtime.d.mts +1 -1
- package/dist/runtime.mjs +1 -2
- package/dist/schematics-kO26eQRT.mjs +91 -0
- package/dist/session-D3zsdzIy.mjs +452 -0
- package/dist/test.d.mts +1 -0
- package/dist/test.mjs +204 -45
- package/dist/{types-CgZn5X6j.d.mts → types-D-kfuyVa.d.mts} +1 -2
- package/dist/types-D0FFexRF.d.mts +39 -0
- package/dist/types.d.mts +3 -3
- package/dist/{upload-D0RcEWrq.mjs → upload-DozK8UdN.mjs} +278 -15
- package/dist/upload-worker.mjs +136 -3
- package/dist/utils-Dy51PYAC.mjs +1034 -0
- package/dist/vite.d.mts +9 -0
- package/dist/vite.mjs +309 -0
- package/dist/volar.d.mts +2 -2
- package/dist/web-apis.mjs +1 -2
- package/dist/wxs-BT8btflA.mjs +112 -0
- package/dist/wxs-Z4VQDeJ_.mjs +1 -0
- package/package.json +80 -48
- package/dist/adapters-BwnMMIZR.mjs +0 -2
- package/dist/dashboard-BzaVesmQ.mjs +0 -558
- package/dist/file-D19nbDoN.mjs +0 -2
- package/dist/getInstance-CYF1vl5l.mjs +0 -2
- package/dist/index-DbqVupg0.d.mts +0 -246
- package/dist/logger-mt4mSTqV.mjs +0 -2
|
@@ -0,0 +1,1110 @@
|
|
|
1
|
+
import { c as getBackendForCapability } from "./createCompilerContextInstance-aYwi8wQt.mjs";
|
|
2
|
+
import { l as resolveHmrProfileJsonPath } from "./devProvider-D-hTfsEO.mjs";
|
|
3
|
+
import { c as findJsEntry, d as findVueEntry, u as findTemplateEntry } from "./file-CcIOmqhC.mjs";
|
|
4
|
+
import { b as resolvePreloadPackageIdentifier, v as collectPreloadPages, x as suggestPreloadRules, y as findPreloadRuleKey } from "./utils-Dy51PYAC.mjs";
|
|
5
|
+
import { r as logger_default, t as colors } from "./logger-DGn5iEVN.mjs";
|
|
6
|
+
import { t as createCompilerContext } from "./createContext-tApLtII-.mjs";
|
|
7
|
+
import { i as resolveRuntimeTargets, n as logRuntimeTarget, t as createInlineConfig } from "./runtime-CLUhQsdA.mjs";
|
|
8
|
+
import { a as parseDashboardUiHost, o as resolveConfigFile, r as filterDuplicateOptions, t as coerceBooleanOption } from "./options-jqFwfD1G.mjs";
|
|
9
|
+
import { t as readHmrProfileLines } from "./reader-sh_POQ9-.mjs";
|
|
10
|
+
import { n as createDashboardArtifactSnapshot } from "./artifacts-BOyC9Fy-.mjs";
|
|
11
|
+
import { i as startAnalyzeDashboard, n as writeAnalyzeHistorySnapshot, r as analyzeSubpackages, t as readLatestAnalyzeHistorySnapshot } from "./history-BIiRCzbq.mjs";
|
|
12
|
+
import { t as setCommandNodeEnv } from "./nodeEnv-BmH8UZPR.mjs";
|
|
13
|
+
import { t as createAnalyzeBudgetCheck } from "./budget-EOeEycc_.mjs";
|
|
14
|
+
import { r as createAnalyzeComparison, t as createDuplicateModuleInsights } from "./analyze-DN79YOAH.mjs";
|
|
15
|
+
import { t as terminateStaleSassEmbeddedProcess } from "./processCleanup-BQn2kTcK.mjs";
|
|
16
|
+
import process from "node:process";
|
|
17
|
+
import path from "pathe";
|
|
18
|
+
import { fs } from "@weapp-core/shared/fs";
|
|
19
|
+
import { parse } from "vue/compiler-sfc";
|
|
20
|
+
//#region src/cli/commands/analyze/webResult.ts
|
|
21
|
+
function normalizeDisplayPath(value) {
|
|
22
|
+
return value || ".";
|
|
23
|
+
}
|
|
24
|
+
function getDefaultWebAnalyzeScopes() {
|
|
25
|
+
return {
|
|
26
|
+
supported: [
|
|
27
|
+
"weapp.web 配置解析(enable/root/srcDir/outDir)",
|
|
28
|
+
"runtime.executionMode 静态解析(compat/safe/strict)",
|
|
29
|
+
"JSON 报告输出(--json/--output)"
|
|
30
|
+
],
|
|
31
|
+
unsupported: [
|
|
32
|
+
"分包产物体积分析(仅小程序)",
|
|
33
|
+
"源码模块包体映射(仅小程序)",
|
|
34
|
+
"分析仪表盘(dashboard)"
|
|
35
|
+
]
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function createWebAnalyzeResult(configService, options) {
|
|
39
|
+
const webConfig = configService.weappWebConfig;
|
|
40
|
+
const executionMode = webConfig?.pluginOptions.runtime?.executionMode ?? "compat";
|
|
41
|
+
const scope = getDefaultWebAnalyzeScopes();
|
|
42
|
+
const limitations = ["当前仅提供静态配置分析,不执行 Web 产物扫描。"];
|
|
43
|
+
if (!webConfig?.enabled) limitations.push("未检测到启用的 weapp.web 配置。");
|
|
44
|
+
return {
|
|
45
|
+
runtime: "web",
|
|
46
|
+
platform: options.platform,
|
|
47
|
+
mode: configService.mode,
|
|
48
|
+
generatedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
49
|
+
experimental: true,
|
|
50
|
+
configFile: configService.configFilePath ? normalizeDisplayPath(configService.relativeCwd(configService.configFilePath)) : void 0,
|
|
51
|
+
web: {
|
|
52
|
+
enabled: Boolean(webConfig?.enabled),
|
|
53
|
+
root: webConfig?.root ? normalizeDisplayPath(configService.relativeCwd(webConfig.root)) : void 0,
|
|
54
|
+
srcDir: webConfig?.srcDir,
|
|
55
|
+
outDir: webConfig?.outDir ? normalizeDisplayPath(configService.relativeCwd(webConfig.outDir)) : void 0,
|
|
56
|
+
executionMode
|
|
57
|
+
},
|
|
58
|
+
supportedScopes: scope.supported,
|
|
59
|
+
unsupportedScopes: scope.unsupported,
|
|
60
|
+
limitations
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/analyze/hmr/attribution.ts
|
|
65
|
+
const nonnegative = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
66
|
+
function difference(whole, ...parts) {
|
|
67
|
+
return nonnegative(whole) && parts.every(nonnegative) && whole >= parts.reduce((sum, value) => sum + value, 0) ? whole - parts.reduce((sum, value) => sum + value, 0) : null;
|
|
68
|
+
}
|
|
69
|
+
/** 外层时段互不重叠;插件 hook 属于 bundler 内部的嵌套观测,不能再次累加。 */
|
|
70
|
+
function attributeHmrProfile(sample, external) {
|
|
71
|
+
const value = (key) => nonnegative(sample[key]) ? sample[key] : null;
|
|
72
|
+
const phases = sample.pipeline === "stateful" ? Object.fromEntries((sample.profileMode === "delivery" ? [
|
|
73
|
+
"sourceToBatchMs",
|
|
74
|
+
"deliveryQueueMs",
|
|
75
|
+
"prepareMs",
|
|
76
|
+
"commitQueueMs",
|
|
77
|
+
"commitMs",
|
|
78
|
+
"publishMs"
|
|
79
|
+
] : [
|
|
80
|
+
"sourceToBatchMs",
|
|
81
|
+
"snapshotBuildMs",
|
|
82
|
+
"snapshotPublishMs"
|
|
83
|
+
]).map((key) => [key, value(key)])) : {
|
|
84
|
+
batchWaitMs: value("batchWaitMs"),
|
|
85
|
+
queueWaitMs: value("queueWaitMs"),
|
|
86
|
+
otherBeforeBuildMs: difference(sample.watchToDirtyMs, sample.batchWaitMs, sample.queueWaitMs),
|
|
87
|
+
bundlerMs: value("bundlerMs")
|
|
88
|
+
};
|
|
89
|
+
const profileResidualMs = sample.pipeline === "stateful" ? difference(sample.totalMs, ...Object.values(phases)) : difference(sample.totalMs, sample.watchToDirtyMs, sample.bundlerMs);
|
|
90
|
+
let externalStatus = external ? "unknown" : "not-observed";
|
|
91
|
+
let sourceToWatcherMs = null;
|
|
92
|
+
let afterProfileToVisibleMs = null;
|
|
93
|
+
let eventId = null;
|
|
94
|
+
if (external && sample.schemaVersion === 1 && sample.correlation === "known" && sample.clock?.durations === "performance.now" && nonnegative(sample.clock.timeOrigin) && nonnegative(external.writtenAtEpochMs) && external.visibleAtEpochMs >= external.writtenAtEpochMs) {
|
|
95
|
+
const events = (sample.sourceEvents ?? []).filter((event) => event.file?.replaceAll("\\", "/") === external.sourceFile.replaceAll("\\", "/") && sample.clock.timeOrigin + event.receivedAtMs >= external.writtenAtEpochMs && sample.clock.timeOrigin + event.receivedAtMs <= external.visibleAtEpochMs);
|
|
96
|
+
if (events.length === 1 && sample.sourceEvents?.length === 1) {
|
|
97
|
+
const received = sample.clock.timeOrigin + events[0].receivedAtMs;
|
|
98
|
+
sourceToWatcherMs = received - external.writtenAtEpochMs;
|
|
99
|
+
afterProfileToVisibleMs = difference(external.visibleAtEpochMs - received, sample.totalMs);
|
|
100
|
+
eventId = events[0].eventId;
|
|
101
|
+
externalStatus = afterProfileToVisibleMs === null ? "unknown" : "matched";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
schemaVersion: 1,
|
|
106
|
+
pipeline: sample.pipeline ?? "standard",
|
|
107
|
+
status: Object.values(phases).every(nonnegative) && profileResidualMs !== null ? "complete" : "partial",
|
|
108
|
+
phases,
|
|
109
|
+
profileResidualMs,
|
|
110
|
+
external: {
|
|
111
|
+
status: externalStatus,
|
|
112
|
+
eventId,
|
|
113
|
+
sourceToWatcherMs,
|
|
114
|
+
afterProfileToVisibleMs
|
|
115
|
+
},
|
|
116
|
+
nestedMetrics: "hook durations overlap; do not add to outer phases"
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region src/analyze/hmr.ts
|
|
121
|
+
function createMetricSummary(values) {
|
|
122
|
+
if (!values.length) return { count: 0 };
|
|
123
|
+
const total = values.reduce((sum, value) => sum + value, 0);
|
|
124
|
+
return {
|
|
125
|
+
count: values.length,
|
|
126
|
+
averageMs: total / values.length,
|
|
127
|
+
maxMs: Math.max(...values)
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function createOperationSummary(values) {
|
|
131
|
+
if (!values.length) return { count: 0 };
|
|
132
|
+
const total = values.reduce((sum, value) => sum + value, 0);
|
|
133
|
+
return {
|
|
134
|
+
count: values.length,
|
|
135
|
+
average: total / values.length,
|
|
136
|
+
max: Math.max(...values)
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function sortCountEntries(map) {
|
|
140
|
+
return [...map.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0])).map(([name, count]) => ({
|
|
141
|
+
name,
|
|
142
|
+
count
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
function collectCounts(target, values) {
|
|
146
|
+
for (const value of values ?? []) {
|
|
147
|
+
if (!value) continue;
|
|
148
|
+
target.set(value, (target.get(value) ?? 0) + 1);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function isFiniteNumber(value) {
|
|
152
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* @description 聚合 HMR JSONL profile,为命令行与后续仪表盘复用。
|
|
156
|
+
*/
|
|
157
|
+
async function analyzeHmrProfile(options) {
|
|
158
|
+
const content = await fs.readFile(options.profilePath, "utf8");
|
|
159
|
+
const { samples, coverage: inputCoverage, skippedLineCount } = readHmrProfileLines(content);
|
|
160
|
+
const eventCounts = /* @__PURE__ */ new Map();
|
|
161
|
+
const dirtyReasonCounts = /* @__PURE__ */ new Map();
|
|
162
|
+
const pendingReasonCounts = /* @__PURE__ */ new Map();
|
|
163
|
+
const batchWaitValues = [];
|
|
164
|
+
const queueWaitValues = [];
|
|
165
|
+
const bundlerValues = [];
|
|
166
|
+
const finalizePrepareValues = [];
|
|
167
|
+
const finalizeTemplateValues = [];
|
|
168
|
+
const finalizePublishValues = [];
|
|
169
|
+
const publicationValidateValues = [];
|
|
170
|
+
const publicationIndependentValues = [];
|
|
171
|
+
const publicationPruneValues = [];
|
|
172
|
+
const totalValues = [];
|
|
173
|
+
const buildCoreValues = [];
|
|
174
|
+
const buildStartValues = [];
|
|
175
|
+
const pluginResolveValues = [];
|
|
176
|
+
const transformValues = [];
|
|
177
|
+
const coreTransformValues = [];
|
|
178
|
+
const wevuTransformValues = [];
|
|
179
|
+
const vueTransformValues = [];
|
|
180
|
+
const vueReadSourceValues = [];
|
|
181
|
+
const vueCompileValues = [];
|
|
182
|
+
const vueFinalizeCompiledValues = [];
|
|
183
|
+
const vueFinalizeCodeValues = [];
|
|
184
|
+
const coreLoadValues = [];
|
|
185
|
+
const entryLoadValues = [];
|
|
186
|
+
const entryCodeReadValues = [];
|
|
187
|
+
const entrySidecarResolveValues = [];
|
|
188
|
+
const entryJsonReadValues = [];
|
|
189
|
+
const entryVueConfigValues = [];
|
|
190
|
+
const entryTemplateScanValues = [];
|
|
191
|
+
const entryScriptSetupValues = [];
|
|
192
|
+
const entryVueSignatureValues = [];
|
|
193
|
+
const entryAutoImportValues = [];
|
|
194
|
+
const entryPrepareValues = [];
|
|
195
|
+
const entryEmitOutputValues = [];
|
|
196
|
+
const entryStyleScanValues = [];
|
|
197
|
+
const entryStyleReadValues = [];
|
|
198
|
+
const entryResolveValues = [];
|
|
199
|
+
const entryChunkEmitValues = [];
|
|
200
|
+
const entryChunkLoadValues = [];
|
|
201
|
+
const entryChunkEmitFileValues = [];
|
|
202
|
+
const entryLayoutValues = [];
|
|
203
|
+
const requestGlobalsValues = [];
|
|
204
|
+
const weapiResolveValues = [];
|
|
205
|
+
const renderStartValues = [];
|
|
206
|
+
const generateBundleValues = [];
|
|
207
|
+
const generateSharedValues = [];
|
|
208
|
+
const generateRewriteValues = [];
|
|
209
|
+
const generateModuleGraphValues = [];
|
|
210
|
+
const snapshotResolveValues = [];
|
|
211
|
+
const snapshotBuildValues = [];
|
|
212
|
+
const writeValues = [];
|
|
213
|
+
const watchToDirtyValues = [];
|
|
214
|
+
const emitValues = [];
|
|
215
|
+
const sharedChunkValues = [];
|
|
216
|
+
const chunkEmitCountValues = [];
|
|
217
|
+
const loadCountValues = [];
|
|
218
|
+
const resolveCountValues = [];
|
|
219
|
+
const skippedLoadedCountValues = [];
|
|
220
|
+
for (const sample of samples) {
|
|
221
|
+
if (isFiniteNumber(sample.batchWaitMs)) batchWaitValues.push(sample.batchWaitMs);
|
|
222
|
+
if (isFiniteNumber(sample.queueWaitMs)) queueWaitValues.push(sample.queueWaitMs);
|
|
223
|
+
if (isFiniteNumber(sample.bundlerMs)) bundlerValues.push(sample.bundlerMs);
|
|
224
|
+
if (isFiniteNumber(sample.finalizePrepareMs)) finalizePrepareValues.push(sample.finalizePrepareMs);
|
|
225
|
+
if (isFiniteNumber(sample.finalizeTemplateMs)) finalizeTemplateValues.push(sample.finalizeTemplateMs);
|
|
226
|
+
if (isFiniteNumber(sample.finalizePublishMs)) finalizePublishValues.push(sample.finalizePublishMs);
|
|
227
|
+
if (isFiniteNumber(sample.publicationValidateMs)) publicationValidateValues.push(sample.publicationValidateMs);
|
|
228
|
+
if (isFiniteNumber(sample.publicationIndependentMs)) publicationIndependentValues.push(sample.publicationIndependentMs);
|
|
229
|
+
if (isFiniteNumber(sample.publicationPruneMs)) publicationPruneValues.push(sample.publicationPruneMs);
|
|
230
|
+
totalValues.push(sample.totalMs);
|
|
231
|
+
if (sample.event) eventCounts.set(sample.event, (eventCounts.get(sample.event) ?? 0) + 1);
|
|
232
|
+
if (isFiniteNumber(sample.buildCoreMs)) buildCoreValues.push(sample.buildCoreMs);
|
|
233
|
+
if (isFiniteNumber(sample.buildStartMs)) buildStartValues.push(sample.buildStartMs);
|
|
234
|
+
if (isFiniteNumber(sample.pluginResolveMs)) pluginResolveValues.push(sample.pluginResolveMs);
|
|
235
|
+
if (isFiniteNumber(sample.transformMs)) transformValues.push(sample.transformMs);
|
|
236
|
+
if (isFiniteNumber(sample.coreTransformMs)) coreTransformValues.push(sample.coreTransformMs);
|
|
237
|
+
if (isFiniteNumber(sample.wevuTransformMs)) wevuTransformValues.push(sample.wevuTransformMs);
|
|
238
|
+
if (isFiniteNumber(sample.vueTransformMs)) vueTransformValues.push(sample.vueTransformMs);
|
|
239
|
+
if (isFiniteNumber(sample.vueReadSourceMs)) vueReadSourceValues.push(sample.vueReadSourceMs);
|
|
240
|
+
if (isFiniteNumber(sample.vueCompileMs)) vueCompileValues.push(sample.vueCompileMs);
|
|
241
|
+
if (isFiniteNumber(sample.vueFinalizeCompiledMs)) vueFinalizeCompiledValues.push(sample.vueFinalizeCompiledMs);
|
|
242
|
+
if (isFiniteNumber(sample.vueFinalizeCodeMs)) vueFinalizeCodeValues.push(sample.vueFinalizeCodeMs);
|
|
243
|
+
if (isFiniteNumber(sample.coreLoadMs)) coreLoadValues.push(sample.coreLoadMs);
|
|
244
|
+
if (isFiniteNumber(sample.entryLoadMs)) entryLoadValues.push(sample.entryLoadMs);
|
|
245
|
+
if (isFiniteNumber(sample.entryCodeReadMs)) entryCodeReadValues.push(sample.entryCodeReadMs);
|
|
246
|
+
if (isFiniteNumber(sample.entrySidecarResolveMs)) entrySidecarResolveValues.push(sample.entrySidecarResolveMs);
|
|
247
|
+
if (isFiniteNumber(sample.entryJsonReadMs)) entryJsonReadValues.push(sample.entryJsonReadMs);
|
|
248
|
+
if (isFiniteNumber(sample.entryVueConfigMs)) entryVueConfigValues.push(sample.entryVueConfigMs);
|
|
249
|
+
if (isFiniteNumber(sample.entryTemplateScanMs)) entryTemplateScanValues.push(sample.entryTemplateScanMs);
|
|
250
|
+
if (isFiniteNumber(sample.entryScriptSetupMs)) entryScriptSetupValues.push(sample.entryScriptSetupMs);
|
|
251
|
+
if (isFiniteNumber(sample.entryVueSignatureMs)) entryVueSignatureValues.push(sample.entryVueSignatureMs);
|
|
252
|
+
if (isFiniteNumber(sample.entryAutoImportMs)) entryAutoImportValues.push(sample.entryAutoImportMs);
|
|
253
|
+
if (isFiniteNumber(sample.entryPrepareMs)) entryPrepareValues.push(sample.entryPrepareMs);
|
|
254
|
+
if (isFiniteNumber(sample.entryEmitOutputMs)) entryEmitOutputValues.push(sample.entryEmitOutputMs);
|
|
255
|
+
if (isFiniteNumber(sample.entryStyleScanMs)) entryStyleScanValues.push(sample.entryStyleScanMs);
|
|
256
|
+
if (isFiniteNumber(sample.entryStyleReadMs)) entryStyleReadValues.push(sample.entryStyleReadMs);
|
|
257
|
+
if (isFiniteNumber(sample.entryResolveMs)) entryResolveValues.push(sample.entryResolveMs);
|
|
258
|
+
if (isFiniteNumber(sample.entryChunkEmitMs)) entryChunkEmitValues.push(sample.entryChunkEmitMs);
|
|
259
|
+
if (isFiniteNumber(sample.entryChunkLoadMs)) entryChunkLoadValues.push(sample.entryChunkLoadMs);
|
|
260
|
+
if (isFiniteNumber(sample.entryChunkEmitFileMs)) entryChunkEmitFileValues.push(sample.entryChunkEmitFileMs);
|
|
261
|
+
if (isFiniteNumber(sample.entryLayoutMs)) entryLayoutValues.push(sample.entryLayoutMs);
|
|
262
|
+
if (isFiniteNumber(sample.requestGlobalsMs)) requestGlobalsValues.push(sample.requestGlobalsMs);
|
|
263
|
+
if (isFiniteNumber(sample.weapiResolveMs)) weapiResolveValues.push(sample.weapiResolveMs);
|
|
264
|
+
if (isFiniteNumber(sample.renderStartMs)) renderStartValues.push(sample.renderStartMs);
|
|
265
|
+
if (isFiniteNumber(sample.generateBundleMs)) generateBundleValues.push(sample.generateBundleMs);
|
|
266
|
+
if (isFiniteNumber(sample.generateSharedMs)) generateSharedValues.push(sample.generateSharedMs);
|
|
267
|
+
if (isFiniteNumber(sample.generateRewriteMs)) generateRewriteValues.push(sample.generateRewriteMs);
|
|
268
|
+
if (isFiniteNumber(sample.generateModuleGraphMs)) generateModuleGraphValues.push(sample.generateModuleGraphMs);
|
|
269
|
+
if (isFiniteNumber(sample.snapshotResolveMs)) snapshotResolveValues.push(sample.snapshotResolveMs);
|
|
270
|
+
if (isFiniteNumber(sample.snapshotBuildMs)) snapshotBuildValues.push(sample.snapshotBuildMs);
|
|
271
|
+
if (isFiniteNumber(sample.writeMs)) writeValues.push(sample.writeMs);
|
|
272
|
+
if (isFiniteNumber(sample.watchToDirtyMs)) watchToDirtyValues.push(sample.watchToDirtyMs);
|
|
273
|
+
if (isFiniteNumber(sample.emitMs)) emitValues.push(sample.emitMs);
|
|
274
|
+
if (isFiniteNumber(sample.sharedChunkResolveMs)) sharedChunkValues.push(sample.sharedChunkResolveMs);
|
|
275
|
+
if (isFiniteNumber(sample.chunkEmitCount)) chunkEmitCountValues.push(sample.chunkEmitCount);
|
|
276
|
+
if (isFiniteNumber(sample.loadCount)) loadCountValues.push(sample.loadCount);
|
|
277
|
+
if (isFiniteNumber(sample.resolveCount)) resolveCountValues.push(sample.resolveCount);
|
|
278
|
+
if (isFiniteNumber(sample.skippedLoadedCount)) skippedLoadedCountValues.push(sample.skippedLoadedCount);
|
|
279
|
+
collectCounts(dirtyReasonCounts, sample.dirtyReasonSummary);
|
|
280
|
+
collectCounts(pendingReasonCounts, sample.pendingReasonSummary);
|
|
281
|
+
}
|
|
282
|
+
const orderedByTime = [...samples].sort((left, right) => {
|
|
283
|
+
const leftTime = typeof left.timestamp === "string" ? Date.parse(left.timestamp) : NaN;
|
|
284
|
+
const rightTime = typeof right.timestamp === "string" ? Date.parse(right.timestamp) : NaN;
|
|
285
|
+
if (Number.isFinite(leftTime) && Number.isFinite(rightTime)) return leftTime - rightTime;
|
|
286
|
+
return 0;
|
|
287
|
+
});
|
|
288
|
+
const slowestSamples = [...samples].sort((left, right) => (right.totalMs ?? 0) - (left.totalMs ?? 0)).slice(0, options.topSlowest ?? 5);
|
|
289
|
+
return {
|
|
290
|
+
runtime: "mini",
|
|
291
|
+
kind: "hmr-profile",
|
|
292
|
+
generatedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
293
|
+
profilePath: options.profilePath,
|
|
294
|
+
sampleCount: samples.length,
|
|
295
|
+
skippedLineCount,
|
|
296
|
+
inputCoverage,
|
|
297
|
+
timelines: samples.map((sample) => ({
|
|
298
|
+
batchId: sample.batchId,
|
|
299
|
+
completionBoundary: sample.completionBoundary,
|
|
300
|
+
...attributeHmrProfile(sample)
|
|
301
|
+
})),
|
|
302
|
+
firstTimestamp: orderedByTime[0]?.timestamp,
|
|
303
|
+
lastTimestamp: orderedByTime[orderedByTime.length - 1]?.timestamp,
|
|
304
|
+
metrics: {
|
|
305
|
+
batchWaitMs: createMetricSummary(batchWaitValues),
|
|
306
|
+
queueWaitMs: createMetricSummary(queueWaitValues),
|
|
307
|
+
bundlerMs: createMetricSummary(bundlerValues),
|
|
308
|
+
finalizePrepareMs: createMetricSummary(finalizePrepareValues),
|
|
309
|
+
finalizeTemplateMs: createMetricSummary(finalizeTemplateValues),
|
|
310
|
+
finalizePublishMs: createMetricSummary(finalizePublishValues),
|
|
311
|
+
publicationValidateMs: createMetricSummary(publicationValidateValues),
|
|
312
|
+
publicationIndependentMs: createMetricSummary(publicationIndependentValues),
|
|
313
|
+
publicationPruneMs: createMetricSummary(publicationPruneValues),
|
|
314
|
+
totalMs: createMetricSummary(totalValues),
|
|
315
|
+
buildCoreMs: createMetricSummary(buildCoreValues),
|
|
316
|
+
buildStartMs: createMetricSummary(buildStartValues),
|
|
317
|
+
pluginResolveMs: createMetricSummary(pluginResolveValues),
|
|
318
|
+
transformMs: createMetricSummary(transformValues),
|
|
319
|
+
coreTransformMs: createMetricSummary(coreTransformValues),
|
|
320
|
+
wevuTransformMs: createMetricSummary(wevuTransformValues),
|
|
321
|
+
vueTransformMs: createMetricSummary(vueTransformValues),
|
|
322
|
+
vueReadSourceMs: createMetricSummary(vueReadSourceValues),
|
|
323
|
+
vueCompileMs: createMetricSummary(vueCompileValues),
|
|
324
|
+
vueFinalizeCompiledMs: createMetricSummary(vueFinalizeCompiledValues),
|
|
325
|
+
vueFinalizeCodeMs: createMetricSummary(vueFinalizeCodeValues),
|
|
326
|
+
coreLoadMs: createMetricSummary(coreLoadValues),
|
|
327
|
+
entryLoadMs: createMetricSummary(entryLoadValues),
|
|
328
|
+
entryCodeReadMs: createMetricSummary(entryCodeReadValues),
|
|
329
|
+
entrySidecarResolveMs: createMetricSummary(entrySidecarResolveValues),
|
|
330
|
+
entryJsonReadMs: createMetricSummary(entryJsonReadValues),
|
|
331
|
+
entryVueConfigMs: createMetricSummary(entryVueConfigValues),
|
|
332
|
+
entryTemplateScanMs: createMetricSummary(entryTemplateScanValues),
|
|
333
|
+
entryScriptSetupMs: createMetricSummary(entryScriptSetupValues),
|
|
334
|
+
entryVueSignatureMs: createMetricSummary(entryVueSignatureValues),
|
|
335
|
+
entryAutoImportMs: createMetricSummary(entryAutoImportValues),
|
|
336
|
+
entryPrepareMs: createMetricSummary(entryPrepareValues),
|
|
337
|
+
entryEmitOutputMs: createMetricSummary(entryEmitOutputValues),
|
|
338
|
+
entryStyleScanMs: createMetricSummary(entryStyleScanValues),
|
|
339
|
+
entryStyleReadMs: createMetricSummary(entryStyleReadValues),
|
|
340
|
+
entryResolveMs: createMetricSummary(entryResolveValues),
|
|
341
|
+
entryChunkEmitMs: createMetricSummary(entryChunkEmitValues),
|
|
342
|
+
entryChunkLoadMs: createMetricSummary(entryChunkLoadValues),
|
|
343
|
+
entryChunkEmitFileMs: createMetricSummary(entryChunkEmitFileValues),
|
|
344
|
+
entryLayoutMs: createMetricSummary(entryLayoutValues),
|
|
345
|
+
requestGlobalsMs: createMetricSummary(requestGlobalsValues),
|
|
346
|
+
weapiResolveMs: createMetricSummary(weapiResolveValues),
|
|
347
|
+
renderStartMs: createMetricSummary(renderStartValues),
|
|
348
|
+
generateBundleMs: createMetricSummary(generateBundleValues),
|
|
349
|
+
generateSharedMs: createMetricSummary(generateSharedValues),
|
|
350
|
+
generateRewriteMs: createMetricSummary(generateRewriteValues),
|
|
351
|
+
generateModuleGraphMs: createMetricSummary(generateModuleGraphValues),
|
|
352
|
+
snapshotResolveMs: createMetricSummary(snapshotResolveValues),
|
|
353
|
+
snapshotBuildMs: createMetricSummary(snapshotBuildValues),
|
|
354
|
+
writeMs: createMetricSummary(writeValues),
|
|
355
|
+
watchToDirtyMs: createMetricSummary(watchToDirtyValues),
|
|
356
|
+
emitMs: createMetricSummary(emitValues),
|
|
357
|
+
sharedChunkResolveMs: createMetricSummary(sharedChunkValues)
|
|
358
|
+
},
|
|
359
|
+
operations: {
|
|
360
|
+
chunkEmitCount: createOperationSummary(chunkEmitCountValues),
|
|
361
|
+
loadCount: createOperationSummary(loadCountValues),
|
|
362
|
+
resolveCount: createOperationSummary(resolveCountValues),
|
|
363
|
+
skippedLoadedCount: createOperationSummary(skippedLoadedCountValues)
|
|
364
|
+
},
|
|
365
|
+
events: sortCountEntries(eventCounts),
|
|
366
|
+
dirtyReasons: sortCountEntries(dirtyReasonCounts),
|
|
367
|
+
pendingReasons: sortCountEntries(pendingReasonCounts),
|
|
368
|
+
slowestSamples
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
//#endregion
|
|
372
|
+
//#region src/analyze/preload/budget.ts
|
|
373
|
+
const PRELOAD_LIMIT_BYTES = 2097152;
|
|
374
|
+
function createPackageSizeMap(result) {
|
|
375
|
+
return new Map((result?.packages ?? []).map((pkg) => [pkg.id, pkg.files.reduce((total, file) => total + (file.size ?? 0), 0)]));
|
|
376
|
+
}
|
|
377
|
+
function getConfiguredPackages(rule, appJson) {
|
|
378
|
+
if (!rule || typeof rule !== "object" || !("packages" in rule)) return [];
|
|
379
|
+
const packages = rule.packages;
|
|
380
|
+
if (!Array.isArray(packages)) return [];
|
|
381
|
+
return packages.flatMap((item) => typeof item === "string" ? [resolvePreloadPackageIdentifier(item, appJson)] : []).filter((item) => Boolean(item));
|
|
382
|
+
}
|
|
383
|
+
function createPreloadBudgets(appJson, result, configuredRules, packageAnalysis) {
|
|
384
|
+
const packageSizes = createPackageSizeMap(packageAnalysis);
|
|
385
|
+
const bySourcePackage = /* @__PURE__ */ new Map();
|
|
386
|
+
const ensureSourcePackage = (page) => {
|
|
387
|
+
const sourcePackage = page.packageRoot ?? "__main__";
|
|
388
|
+
let entry = bySourcePackage.get(sourcePackage);
|
|
389
|
+
if (!entry) {
|
|
390
|
+
entry = {
|
|
391
|
+
sourceType: page.packageRoot ? page.independent ? "independent" : "subpackage" : "main",
|
|
392
|
+
targets: /* @__PURE__ */ new Map()
|
|
393
|
+
};
|
|
394
|
+
bySourcePackage.set(sourcePackage, entry);
|
|
395
|
+
}
|
|
396
|
+
return entry;
|
|
397
|
+
};
|
|
398
|
+
for (const page of result.pages) {
|
|
399
|
+
const configuredKey = findPreloadRuleKey(page.route, configuredRules);
|
|
400
|
+
if (configuredKey === void 0) continue;
|
|
401
|
+
const entry = ensureSourcePackage(page);
|
|
402
|
+
for (const packageRoot of getConfiguredPackages(configuredRules[configuredKey], appJson)) {
|
|
403
|
+
const target = entry.targets.get(packageRoot) ?? {
|
|
404
|
+
configured: false,
|
|
405
|
+
suggested: false
|
|
406
|
+
};
|
|
407
|
+
target.configured = true;
|
|
408
|
+
entry.targets.set(packageRoot, target);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
const pageByRoute = new Map(result.pages.map((page) => [page.route, page]));
|
|
412
|
+
for (const suggestion of result.suggestions) {
|
|
413
|
+
const page = pageByRoute.get(suggestion.page);
|
|
414
|
+
if (!page) continue;
|
|
415
|
+
const entry = ensureSourcePackage(page);
|
|
416
|
+
const target = entry.targets.get(suggestion.packageRoot) ?? {
|
|
417
|
+
configured: false,
|
|
418
|
+
suggested: false
|
|
419
|
+
};
|
|
420
|
+
target.suggested = true;
|
|
421
|
+
entry.targets.set(suggestion.packageRoot, target);
|
|
422
|
+
}
|
|
423
|
+
return [...bySourcePackage.entries()].map(([sourcePackage, entry]) => {
|
|
424
|
+
const targets = [...entry.targets.entries()].map(([packageRoot, state]) => {
|
|
425
|
+
const packageId = packageRoot === "__APP__" ? "__main__" : packageRoot;
|
|
426
|
+
return {
|
|
427
|
+
packageRoot,
|
|
428
|
+
bytes: packageSizes.get(packageId),
|
|
429
|
+
...state
|
|
430
|
+
};
|
|
431
|
+
}).sort((left, right) => left.packageRoot.localeCompare(right.packageRoot));
|
|
432
|
+
const unknownPackages = targets.filter((target) => target.bytes === void 0).map((target) => target.packageRoot);
|
|
433
|
+
const estimatedBytes = targets.reduce((total, target) => total + (target.bytes ?? 0), 0);
|
|
434
|
+
const status = estimatedBytes > PRELOAD_LIMIT_BYTES ? "exceeded" : unknownPackages.length > 0 ? "unknown" : "ok";
|
|
435
|
+
return {
|
|
436
|
+
sourcePackage,
|
|
437
|
+
sourceType: entry.sourceType,
|
|
438
|
+
limitBytes: PRELOAD_LIMIT_BYTES,
|
|
439
|
+
estimatedBytes,
|
|
440
|
+
remainingBytes: unknownPackages.length === 0 ? Math.max(0, PRELOAD_LIMIT_BYTES - estimatedBytes) : void 0,
|
|
441
|
+
status,
|
|
442
|
+
targets,
|
|
443
|
+
unknownPackages
|
|
444
|
+
};
|
|
445
|
+
}).sort((left, right) => left.sourcePackage.localeCompare(right.sourcePackage));
|
|
446
|
+
}
|
|
447
|
+
//#endregion
|
|
448
|
+
//#region src/analyze/preload/index.ts
|
|
449
|
+
async function readFileIfExists(filePath) {
|
|
450
|
+
if (!filePath) return;
|
|
451
|
+
try {
|
|
452
|
+
return await fs.readFile(filePath, "utf8");
|
|
453
|
+
} catch {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function splitVueSource(source, filename) {
|
|
458
|
+
try {
|
|
459
|
+
const { descriptor } = parse(source, { filename });
|
|
460
|
+
return {
|
|
461
|
+
template: descriptor.template?.content,
|
|
462
|
+
script: [descriptor.script?.content, descriptor.scriptSetup?.content].filter((content) => Boolean(content)).join("\n")
|
|
463
|
+
};
|
|
464
|
+
} catch {
|
|
465
|
+
return {
|
|
466
|
+
template: source,
|
|
467
|
+
script: source
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
async function collectPageSources(ctx, route) {
|
|
472
|
+
const base = path.resolve(ctx.configService.absoluteSrcRoot, route);
|
|
473
|
+
const [templateEntry, scriptEntry, vueEntry] = await Promise.all([
|
|
474
|
+
findTemplateEntry(base, ctx.configService.platform),
|
|
475
|
+
findJsEntry(base),
|
|
476
|
+
findVueEntry(base)
|
|
477
|
+
]);
|
|
478
|
+
if (vueEntry) {
|
|
479
|
+
const source = await readFileIfExists(vueEntry);
|
|
480
|
+
return source ? splitVueSource(source, vueEntry) : {};
|
|
481
|
+
}
|
|
482
|
+
const [template, script] = await Promise.all([readFileIfExists(templateEntry.path), readFileIfExists(scriptEntry.path)]);
|
|
483
|
+
return {
|
|
484
|
+
template,
|
|
485
|
+
script
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
function aggregateSuggestions(result, configuredRules, appJson) {
|
|
489
|
+
const byPage = /* @__PURE__ */ new Map();
|
|
490
|
+
for (const suggestion of result.suggestions) {
|
|
491
|
+
const current = byPage.get(suggestion.page) ?? {
|
|
492
|
+
page: suggestion.page,
|
|
493
|
+
packages: [],
|
|
494
|
+
evidence: [],
|
|
495
|
+
alreadyConfigured: []
|
|
496
|
+
};
|
|
497
|
+
if (!current.packages.includes(suggestion.packageRoot)) current.packages.push(suggestion.packageRoot);
|
|
498
|
+
current.evidence.push({
|
|
499
|
+
target: suggestion.target,
|
|
500
|
+
packageRoot: suggestion.packageRoot,
|
|
501
|
+
source: suggestion.source
|
|
502
|
+
});
|
|
503
|
+
const configuredKey = findPreloadRuleKey(suggestion.page, configuredRules);
|
|
504
|
+
const existingRule = configuredKey === void 0 ? void 0 : configuredRules[configuredKey];
|
|
505
|
+
const existingPackages = existingRule && typeof existingRule === "object" && "packages" in existingRule ? existingRule.packages : void 0;
|
|
506
|
+
if ((Array.isArray(existingPackages) ? existingPackages.flatMap((item) => typeof item === "string" ? [resolvePreloadPackageIdentifier(item, appJson)] : []) : []).includes(suggestion.packageRoot)) current.alreadyConfigured.push(suggestion.packageRoot);
|
|
507
|
+
byPage.set(suggestion.page, current);
|
|
508
|
+
}
|
|
509
|
+
return [...byPage.values()].map((suggestion) => ({
|
|
510
|
+
...suggestion,
|
|
511
|
+
packages: suggestion.packages.sort(),
|
|
512
|
+
alreadyConfigured: [...new Set(suggestion.alreadyConfigured)].sort(),
|
|
513
|
+
evidence: suggestion.evidence.sort((left, right) => left.target.localeCompare(right.target))
|
|
514
|
+
}));
|
|
515
|
+
}
|
|
516
|
+
async function analyzePreloadRules(ctx, options = {}) {
|
|
517
|
+
const appJson = (await ctx.scanService.loadAppEntry()).json;
|
|
518
|
+
const pages = collectPreloadPages(appJson);
|
|
519
|
+
const pageSources = /* @__PURE__ */ new Map();
|
|
520
|
+
const sourceEntries = await Promise.all(pages.map(async (page) => [page.route, await collectPageSources(ctx, page.route)]));
|
|
521
|
+
for (const [route, source] of sourceEntries) pageSources.set(route, source);
|
|
522
|
+
const configuredRules = appJson.preloadRule && typeof appJson.preloadRule === "object" ? appJson.preloadRule : {};
|
|
523
|
+
const suggestions = suggestPreloadRules(appJson, pageSources);
|
|
524
|
+
return {
|
|
525
|
+
runtime: "mini",
|
|
526
|
+
kind: "preload",
|
|
527
|
+
generatedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
528
|
+
platform: ctx.configService.platform,
|
|
529
|
+
pages: pages.map((page) => page.route),
|
|
530
|
+
configuredRules,
|
|
531
|
+
suggestions: aggregateSuggestions(suggestions, configuredRules, appJson),
|
|
532
|
+
budgets: createPreloadBudgets(appJson, suggestions, configuredRules, options.packageAnalysis),
|
|
533
|
+
uncoveredPages: suggestions.uncovered,
|
|
534
|
+
limitations: [
|
|
535
|
+
"仅分析静态路由字面量;动态路由、后端配置和运行时守卫不会被推断。",
|
|
536
|
+
"建议使用分包 root 作为 packages 值;预算按触发页所属包聚合,未知体积目标仍需人工复核。",
|
|
537
|
+
"静态跳转只能证明可达性,不能代表真实访问频率,正式启用前仍需结合业务数据确认。",
|
|
538
|
+
"该命令只提供可审计建议,不会自动修改源码或覆盖手写的 app.json.preloadRule。"
|
|
539
|
+
]
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
//#endregion
|
|
543
|
+
//#region src/analyze/subpackages/report.ts
|
|
544
|
+
function formatAnalyzeBytes(bytes) {
|
|
545
|
+
if (!bytes || Number.isNaN(bytes)) return "0 B";
|
|
546
|
+
const units = [
|
|
547
|
+
"B",
|
|
548
|
+
"KB",
|
|
549
|
+
"MB",
|
|
550
|
+
"GB"
|
|
551
|
+
];
|
|
552
|
+
let value = bytes;
|
|
553
|
+
let unitIndex = 0;
|
|
554
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
555
|
+
value /= 1024;
|
|
556
|
+
unitIndex++;
|
|
557
|
+
}
|
|
558
|
+
return `${value.toFixed(value >= 100 || unitIndex === 0 ? 0 : 2)} ${units[unitIndex]}`;
|
|
559
|
+
}
|
|
560
|
+
function formatDelta(bytes) {
|
|
561
|
+
if (bytes === null) return "缺少体积测量";
|
|
562
|
+
if (typeof bytes !== "number" || Number.isNaN(bytes) || bytes === 0) return "无变化";
|
|
563
|
+
return `${bytes > 0 ? "+" : "-"}${formatAnalyzeBytes(Math.abs(bytes))}`;
|
|
564
|
+
}
|
|
565
|
+
function getFileSize(file) {
|
|
566
|
+
return file.size ?? 0;
|
|
567
|
+
}
|
|
568
|
+
function getCompressedSize(file) {
|
|
569
|
+
return file.brotliSize ?? file.gzipSize ?? 0;
|
|
570
|
+
}
|
|
571
|
+
function formatBudgetStatus(item) {
|
|
572
|
+
if (item.status === "unknown") return "归因或产物字节不足,无法验收";
|
|
573
|
+
if (item.status === "ok") return "正常";
|
|
574
|
+
return `${item.status === "exceeded" ? "超预算" : "接近预算"} ${(item.ratio * 100).toFixed(1)}%`;
|
|
575
|
+
}
|
|
576
|
+
function createActionItems(options) {
|
|
577
|
+
const actions = [];
|
|
578
|
+
const exceededItems = options.budgetItems.filter((item) => item.status === "exceeded");
|
|
579
|
+
const warningItems = options.budgetItems.filter((item) => item.status === "warning");
|
|
580
|
+
const topDuplicate = options.duplicateInsights.find((item) => item.estimatedSavingBytes > 0);
|
|
581
|
+
if (exceededItems.length > 0) actions.push(`处理 ${exceededItems[0].label} 预算超限:当前 ${formatAnalyzeBytes(exceededItems[0].currentBytes)},限制 ${formatAnalyzeBytes(exceededItems[0].limitBytes)}。`);
|
|
582
|
+
else if (warningItems.length > 0) actions.push(`关注 ${warningItems[0].label} 预算接近阈值:当前 ${(warningItems[0].ratio * 100).toFixed(1)}%。`);
|
|
583
|
+
if (topDuplicate) actions.push(`优先处理重复模块 ${topDuplicate.source},估算可节省 ${formatAnalyzeBytes(topDuplicate.estimatedSavingBytes)}。`);
|
|
584
|
+
if (actions.length === 0) actions.push("当前没有预算超限或高收益重复模块,保持观察即可。");
|
|
585
|
+
return actions;
|
|
586
|
+
}
|
|
587
|
+
function createReportGrowth(comparison) {
|
|
588
|
+
if (!comparison) return [];
|
|
589
|
+
return [...comparison.files, ...comparison.modules].filter((item) => item.deltaBytes !== null && item.deltaBytes > 0).sort((a, b) => b.deltaBytes - a.deltaBytes || (a.category ?? "").localeCompare(b.category ?? "") || a.label.localeCompare(b.label));
|
|
590
|
+
}
|
|
591
|
+
function createAnalyzeIncrementCategorySummary(items) {
|
|
592
|
+
const map = /* @__PURE__ */ new Map();
|
|
593
|
+
for (const item of items) {
|
|
594
|
+
const category = item.category ?? "";
|
|
595
|
+
const entry = map.get(category) ?? {
|
|
596
|
+
category,
|
|
597
|
+
count: 0,
|
|
598
|
+
deltaBytes: 0
|
|
599
|
+
};
|
|
600
|
+
entry.count += 1;
|
|
601
|
+
entry.deltaBytes += item.deltaBytes;
|
|
602
|
+
map.set(category, entry);
|
|
603
|
+
}
|
|
604
|
+
return [...map.values()].sort((a, b) => b.deltaBytes - a.deltaBytes || b.count - a.count || a.category.localeCompare(b.category));
|
|
605
|
+
}
|
|
606
|
+
function createAnalyzePrMarkdownReport(result, previousResult) {
|
|
607
|
+
const files = result.packages.flatMap((pkg) => pkg.files.map((file) => ({
|
|
608
|
+
pkg,
|
|
609
|
+
file
|
|
610
|
+
})));
|
|
611
|
+
const totalBytes = files.reduce((sum, item) => sum + getFileSize(item.file), 0);
|
|
612
|
+
const compressedBytes = files.reduce((sum, item) => sum + getCompressedSize(item.file), 0);
|
|
613
|
+
const comparison = previousResult ? createAnalyzeComparison(result, previousResult) : void 0;
|
|
614
|
+
const incrementItems = createReportGrowth(comparison);
|
|
615
|
+
const incrementSummary = createAnalyzeIncrementCategorySummary(incrementItems);
|
|
616
|
+
const duplicateInsights = createDuplicateModuleInsights(result);
|
|
617
|
+
const budgetItems = createAnalyzeBudgetCheck(result);
|
|
618
|
+
const budgetIssues = budgetItems.filter((item) => item.status !== "ok");
|
|
619
|
+
const actionItems = createActionItems({
|
|
620
|
+
budgetItems,
|
|
621
|
+
duplicateInsights
|
|
622
|
+
}).slice(0, 3);
|
|
623
|
+
const budgetRows = budgetIssues.slice(0, 5).map((item) => `| ${item.label} | ${formatAnalyzeBytes(item.currentBytes)} | ${formatAnalyzeBytes(item.limitBytes)} | ${formatBudgetStatus(item)} |`).join("\n");
|
|
624
|
+
const incrementRows = incrementItems.slice(0, 8).map((item) => `| ${item.label} | ${item.category} | ${item.packageLabel} | ${formatAnalyzeBytes(item.deltaBytes)} | ${item.advice} |`).join("\n");
|
|
625
|
+
const sourceRows = incrementSummary.slice(0, 6).map((item) => `| ${item.category} | ${item.count} | ${formatAnalyzeBytes(item.deltaBytes)} |`).join("\n");
|
|
626
|
+
const duplicateRows = duplicateInsights.slice(0, 5).map((module) => `| ${module.source} | ${module.packageCount} | ${formatAnalyzeBytes(module.estimatedSavingBytes)} | ${module.advice} |`).join("\n");
|
|
627
|
+
return [
|
|
628
|
+
"## weapp-vite analyze PR 摘要",
|
|
629
|
+
"",
|
|
630
|
+
`- 总产物体积:${formatAnalyzeBytes(totalBytes)}(较上次 ${formatDelta(comparison?.deltaBytes)})`,
|
|
631
|
+
`- 压缩后体积:${formatAnalyzeBytes(compressedBytes)}`,
|
|
632
|
+
`- 预算告警:${budgetIssues.length}`,
|
|
633
|
+
`- 增量归因:${incrementItems.length > 0 ? `${incrementItems.length} 项正向增长` : "无正向增长"}`,
|
|
634
|
+
`- 跨包复用:${duplicateInsights.length}`,
|
|
635
|
+
"",
|
|
636
|
+
"### 建议动作",
|
|
637
|
+
"",
|
|
638
|
+
...actionItems.map((item) => `- ${item}`),
|
|
639
|
+
"",
|
|
640
|
+
"### 预算状态",
|
|
641
|
+
"",
|
|
642
|
+
"| 对象 | 当前体积 | 预算 | 状态 |",
|
|
643
|
+
"| --- | ---: | ---: | --- |",
|
|
644
|
+
budgetRows || "| - | 0 B | 0 B | 正常 |",
|
|
645
|
+
"",
|
|
646
|
+
"### 增量来源",
|
|
647
|
+
"",
|
|
648
|
+
"| 来源 | 项数 | 增量 |",
|
|
649
|
+
"| --- | ---: | ---: |",
|
|
650
|
+
sourceRows || "| - | 0 | 0 B |",
|
|
651
|
+
"",
|
|
652
|
+
"### Top 增量",
|
|
653
|
+
"",
|
|
654
|
+
"| 文件/模块 | 来源 | 包 | 增量 | 建议 |",
|
|
655
|
+
"| --- | --- | --- | ---: | --- |",
|
|
656
|
+
incrementRows || "| - | - | - | 0 B | - |",
|
|
657
|
+
"",
|
|
658
|
+
"### 重复模块",
|
|
659
|
+
"",
|
|
660
|
+
"| 模块 | 包数量 | 估算可节省 | 建议 |",
|
|
661
|
+
"| --- | ---: | ---: | --- |",
|
|
662
|
+
duplicateRows || "| - | 0 | 0 B | - |",
|
|
663
|
+
""
|
|
664
|
+
].join("\n");
|
|
665
|
+
}
|
|
666
|
+
function createAnalyzeMarkdownReport(result, previousResult) {
|
|
667
|
+
const files = result.packages.flatMap((pkg) => pkg.files.map((file) => ({
|
|
668
|
+
pkg,
|
|
669
|
+
file
|
|
670
|
+
})));
|
|
671
|
+
const totalBytes = files.reduce((sum, item) => sum + getFileSize(item.file), 0);
|
|
672
|
+
const compressedBytes = files.reduce((sum, item) => sum + getCompressedSize(item.file), 0);
|
|
673
|
+
const duplicateInsights = createDuplicateModuleInsights(result);
|
|
674
|
+
const comparison = previousResult ? createAnalyzeComparison(result, previousResult) : void 0;
|
|
675
|
+
const incrementItems = createReportGrowth(comparison);
|
|
676
|
+
const incrementSummary = createAnalyzeIncrementCategorySummary(incrementItems);
|
|
677
|
+
const budgetItems = createAnalyzeBudgetCheck(result);
|
|
678
|
+
const packageDeltas = new Map(comparison?.packages.map((item) => [item.packageId, item.deltaBytes]));
|
|
679
|
+
const budgets = result.metadata?.budgets;
|
|
680
|
+
const budgetIssues = budgetItems.filter((item) => item.status !== "ok");
|
|
681
|
+
const actionItems = createActionItems({
|
|
682
|
+
budgetItems,
|
|
683
|
+
duplicateInsights
|
|
684
|
+
});
|
|
685
|
+
const packageRows = result.packages.map((pkg) => {
|
|
686
|
+
const size = pkg.files.reduce((sum, file) => sum + getFileSize(file), 0);
|
|
687
|
+
const compressed = pkg.files.reduce((sum, file) => sum + getCompressedSize(file), 0);
|
|
688
|
+
const delta = packageDeltas.has(pkg.id) ? packageDeltas.get(pkg.id) : 0;
|
|
689
|
+
const budgetStatus = budgetItems.find((item) => item.id === pkg.id);
|
|
690
|
+
return `| ${pkg.label} | ${pkg.type} | ${formatAnalyzeBytes(size)} | ${formatAnalyzeBytes(compressed)} | ${formatDelta(delta)} | ${budgetStatus ? formatBudgetStatus(budgetStatus) : "正常"} |`;
|
|
691
|
+
}).join("\n");
|
|
692
|
+
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");
|
|
693
|
+
const duplicateRows = duplicateInsights.slice(0, 10).map((module) => `| ${module.source} | ${module.sourceType} | ${module.packageCount} | ${formatAnalyzeBytes(module.estimatedSavingBytes)} | ${module.advice} |`).join("\n");
|
|
694
|
+
const budgetRows = budgetIssues.map((item) => `| ${item.label} | ${item.scope} | ${formatAnalyzeBytes(item.currentBytes)} | ${formatAnalyzeBytes(item.limitBytes)} | ${formatBudgetStatus(item)} |`).join("\n");
|
|
695
|
+
const incrementRows = incrementItems.slice(0, 10).map((item) => `| ${item.label} | ${item.category} | ${item.packageLabel} | ${formatAnalyzeBytes(item.deltaBytes)} | ${item.advice} |`).join("\n");
|
|
696
|
+
const incrementSummaryRows = incrementSummary.slice(0, 8).map((item) => `| ${item.category} | ${item.count} | ${formatAnalyzeBytes(item.deltaBytes)} |`).join("\n");
|
|
697
|
+
return [
|
|
698
|
+
"# weapp-vite analyze 报告",
|
|
699
|
+
"",
|
|
700
|
+
`生成时间:${result.metadata?.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString()}`,
|
|
701
|
+
"",
|
|
702
|
+
"## 本次变化摘要",
|
|
703
|
+
"",
|
|
704
|
+
`- 总产物体积:${formatAnalyzeBytes(totalBytes)}`,
|
|
705
|
+
`- 压缩后体积:${formatAnalyzeBytes(compressedBytes)}`,
|
|
706
|
+
`- 较上次:${formatDelta(comparison?.deltaBytes)}`,
|
|
707
|
+
`- 包体数量:${result.packages.length}`,
|
|
708
|
+
`- 源码模块:${result.modules.length}`,
|
|
709
|
+
`- 跨包复用:${duplicateInsights.length}`,
|
|
710
|
+
`- 预算来源:${budgets?.source === "config" ? "配置" : "默认"}`,
|
|
711
|
+
"",
|
|
712
|
+
"## 预算告警",
|
|
713
|
+
"",
|
|
714
|
+
"| 对象 | 范围 | 当前体积 | 预算 | 状态 |",
|
|
715
|
+
"| --- | --- | ---: | ---: | --- |",
|
|
716
|
+
budgetRows || "| - | - | 0 B | 0 B | 正常 |",
|
|
717
|
+
"",
|
|
718
|
+
"## 建议动作",
|
|
719
|
+
"",
|
|
720
|
+
...actionItems.map((item) => `- ${item}`),
|
|
721
|
+
"",
|
|
722
|
+
"## 增量归因",
|
|
723
|
+
"",
|
|
724
|
+
"| 来源 | 项数 | 增量 |",
|
|
725
|
+
"| --- | ---: | ---: |",
|
|
726
|
+
incrementSummaryRows || "| - | 0 | 0 B |",
|
|
727
|
+
"",
|
|
728
|
+
"| 文件/模块 | 来源 | 包 | 增量 | 建议 |",
|
|
729
|
+
"| --- | --- | --- | ---: | --- |",
|
|
730
|
+
incrementRows || "| - | - | - | 0 B | - |",
|
|
731
|
+
"",
|
|
732
|
+
"## 包体预算",
|
|
733
|
+
"",
|
|
734
|
+
"| 包 | 类型 | 体积 | 压缩后 | 较上次 | 预算 |",
|
|
735
|
+
"| --- | --- | ---: | ---: | ---: | --- |",
|
|
736
|
+
packageRows || "| - | - | 0 B | 0 B | 无变化 | 正常 |",
|
|
737
|
+
"",
|
|
738
|
+
"## Top 文件",
|
|
739
|
+
"",
|
|
740
|
+
"| 文件 | 包 | 类型 | 体积 | 压缩后 |",
|
|
741
|
+
"| --- | --- | --- | ---: | ---: |",
|
|
742
|
+
topFileRows || "| - | - | - | 0 B | 0 B |",
|
|
743
|
+
"",
|
|
744
|
+
"## 重复模块",
|
|
745
|
+
"",
|
|
746
|
+
"| 模块 | 来源 | 包数量 | 估算可节省 | 建议 |",
|
|
747
|
+
"| --- | --- | ---: | ---: | --- |",
|
|
748
|
+
duplicateRows || "| - | - | 0 | 0 B | - |",
|
|
749
|
+
""
|
|
750
|
+
].join("\n");
|
|
751
|
+
}
|
|
752
|
+
//#endregion
|
|
753
|
+
//#region src/cli/analyze/output.ts
|
|
754
|
+
/** CLI 单次分析期间保留 JSON 专用写入入口,配置、插件和清理日志走 stderr。 */
|
|
755
|
+
async function withAnalyzeOutput(json, run) {
|
|
756
|
+
const stdoutWrite = process.stdout.write;
|
|
757
|
+
const writeJson = (value) => stdoutWrite.call(process.stdout, `${JSON.stringify(value, null, 2)}\n`);
|
|
758
|
+
if (json) process.stdout.write = process.stderr.write.bind(process.stderr);
|
|
759
|
+
try {
|
|
760
|
+
await run(writeJson);
|
|
761
|
+
} finally {
|
|
762
|
+
process.stdout.write = stdoutWrite;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
//#endregion
|
|
766
|
+
//#region src/cli/commands/analyze/action.ts
|
|
767
|
+
function printAnalysisSummary(result) {
|
|
768
|
+
const packageLabelMap = /* @__PURE__ */ new Map();
|
|
769
|
+
const packageModuleSet = /* @__PURE__ */ new Map();
|
|
770
|
+
for (const pkg of result.packages) packageLabelMap.set(pkg.id, pkg.label);
|
|
771
|
+
for (const module of result.modules) for (const pkgRef of module.packages) {
|
|
772
|
+
const set = packageModuleSet.get(pkgRef.packageId) ?? /* @__PURE__ */ new Set();
|
|
773
|
+
set.add(module.id);
|
|
774
|
+
packageModuleSet.set(pkgRef.packageId, set);
|
|
775
|
+
}
|
|
776
|
+
logger_default.success("分包分析完成");
|
|
777
|
+
for (const pkg of result.packages) {
|
|
778
|
+
const chunkCount = pkg.files.filter((file) => file.type === "chunk").length;
|
|
779
|
+
const assetCount = pkg.files.length - chunkCount;
|
|
780
|
+
const moduleCount = packageModuleSet.get(pkg.id)?.size ?? 0;
|
|
781
|
+
logger_default.info(`- ${pkg.label}:${chunkCount} 个模块产物,${assetCount} 个资源,覆盖 ${moduleCount} 个源码模块`);
|
|
782
|
+
}
|
|
783
|
+
if (result.subPackages.length > 0) {
|
|
784
|
+
logger_default.info("分包配置:");
|
|
785
|
+
for (const descriptor of result.subPackages) {
|
|
786
|
+
const segments = [descriptor.root];
|
|
787
|
+
if (descriptor.name) segments.push(`别名:${descriptor.name}`);
|
|
788
|
+
if (descriptor.independent) segments.push("独立构建");
|
|
789
|
+
logger_default.info(`- ${segments.join(",")}`);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
const componentUsages = result.components ?? [];
|
|
793
|
+
if (componentUsages.length > 0) {
|
|
794
|
+
const suggestions = componentUsages.flatMap((component) => component.suggestions);
|
|
795
|
+
logger_default.info(`组件依赖:${componentUsages.length} 个组件,${suggestions.length} 条分包优化建议`);
|
|
796
|
+
for (const suggestion of suggestions.slice(0, 5)) logger_default.info(`- ${suggestion.message}`);
|
|
797
|
+
if (suggestions.length > 5) logger_default.info(`- …其余 ${suggestions.length - 5} 条组件建议请使用 ${colors.bold(colors.green("weapp-vite analyze --json"))} 查看`);
|
|
798
|
+
}
|
|
799
|
+
const duplicates = result.modules.filter((module) => module.packages.length > 1);
|
|
800
|
+
if (duplicates.length === 0) {
|
|
801
|
+
logger_default.info("未检测到跨包复用的源码模块。");
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
logger_default.info(`跨包复用/复制源码共 ${duplicates.length} 项:`);
|
|
805
|
+
const limit = 10;
|
|
806
|
+
const entries = duplicates.slice(0, limit);
|
|
807
|
+
for (const module of entries) {
|
|
808
|
+
const placements = module.packages.map((pkgRef) => {
|
|
809
|
+
return `${packageLabelMap.get(pkgRef.packageId) ?? pkgRef.packageId} → ${pkgRef.files.join(", ")}`;
|
|
810
|
+
}).join(";");
|
|
811
|
+
logger_default.info(`- ${module.source} (${module.sourceType}):${placements}`);
|
|
812
|
+
}
|
|
813
|
+
if (duplicates.length > limit) logger_default.info(`- …其余 ${duplicates.length - limit} 项请使用 ${colors.bold(colors.green("weapp-vite analyze --json"))} 查看`);
|
|
814
|
+
}
|
|
815
|
+
function printBudgetCheckSummary(result) {
|
|
816
|
+
const exceededItems = createAnalyzeBudgetCheck(result).filter((item) => item.status === "exceeded" || item.status === "unknown");
|
|
817
|
+
if (exceededItems.length === 0) {
|
|
818
|
+
logger_default.success("包体预算检查通过");
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
logger_default.error(`包体预算检查失败:${exceededItems.length} 项超限`);
|
|
822
|
+
for (const item of exceededItems) {
|
|
823
|
+
logger_default.error(`- ${item.label}:${formatAnalyzeBytes(item.currentBytes)} / ${formatAnalyzeBytes(item.limitBytes)} (${(item.ratio * 100).toFixed(1)}%)`);
|
|
824
|
+
if (item.status === "unknown") logger_default.error(" 产物字节或 runtime 归因不足,无法验收预算。");
|
|
825
|
+
logger_default.error(` 文件:${item.files.join(", ") || "未知"}`);
|
|
826
|
+
}
|
|
827
|
+
return true;
|
|
828
|
+
}
|
|
829
|
+
function printGlassEaselCheckSummary(result) {
|
|
830
|
+
const { glassEasel } = result;
|
|
831
|
+
if (!glassEasel.detected) {
|
|
832
|
+
logger_default.info("未检测到 glass-easel 配置。");
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
835
|
+
if (glassEasel.diagnostics.length === 0) {
|
|
836
|
+
logger_default.success("glass-easel 迁移检查通过");
|
|
837
|
+
return false;
|
|
838
|
+
}
|
|
839
|
+
logger_default.info(`glass-easel 迁移检查:${glassEasel.summary.errors} 个错误,${glassEasel.summary.warnings} 个警告`);
|
|
840
|
+
for (const diagnostic of glassEasel.diagnostics) {
|
|
841
|
+
const location = diagnostic.line ? `:${diagnostic.line}:${diagnostic.column}` : "";
|
|
842
|
+
const message = `[${diagnostic.code}] ${diagnostic.file}${location} ${diagnostic.message}`;
|
|
843
|
+
if (diagnostic.severity === "error") logger_default.error(message);
|
|
844
|
+
else logger_default.warn(message);
|
|
845
|
+
}
|
|
846
|
+
return glassEasel.summary.errors > 0;
|
|
847
|
+
}
|
|
848
|
+
function printWebAnalysisSummary(result) {
|
|
849
|
+
logger_default.success("Web 静态分析完成");
|
|
850
|
+
logger_default.info(`- 配置状态:${result.web.enabled ? "已启用 weapp.web" : "未启用 weapp.web"}`);
|
|
851
|
+
if (result.web.enabled) {
|
|
852
|
+
logger_default.info(`- root:${result.web.root ?? "."}`);
|
|
853
|
+
logger_default.info(`- srcDir:${result.web.srcDir ?? "."}`);
|
|
854
|
+
logger_default.info(`- outDir:${result.web.outDir ?? "dist/web"}`);
|
|
855
|
+
}
|
|
856
|
+
logger_default.info(`- executionMode:${result.web.executionMode}`);
|
|
857
|
+
logger_default.info(`- 支持范围:${result.supportedScopes.join(";")}`);
|
|
858
|
+
logger_default.warn(`- 未支持范围:${result.unsupportedScopes.join(";")}`);
|
|
859
|
+
for (const limitation of result.limitations) logger_default.warn(`- 限制:${limitation}`);
|
|
860
|
+
}
|
|
861
|
+
function printPreloadAnalysisSummary(result) {
|
|
862
|
+
logger_default.success("preloadRule 静态分析完成");
|
|
863
|
+
if (result.suggestions.length === 0) logger_default.info("未检测到可以证明的跨分包静态跳转。");
|
|
864
|
+
for (const suggestion of result.suggestions) {
|
|
865
|
+
const packages = suggestion.packages.join("、");
|
|
866
|
+
const covered = suggestion.alreadyConfigured.length > 0 ? `,已配置:${suggestion.alreadyConfigured.join("、")}` : "";
|
|
867
|
+
logger_default.info(`- ${suggestion.page} -> ${packages}${covered}`);
|
|
868
|
+
for (const evidence of suggestion.evidence.slice(0, 5)) logger_default.info(` - ${evidence.source}:${evidence.target}`);
|
|
869
|
+
}
|
|
870
|
+
if (result.uncoveredPages.length > 0) logger_default.warn(`- 未找到可扫描源码的页面:${result.uncoveredPages.join("、")}`);
|
|
871
|
+
if (result.budgets.length > 0) {
|
|
872
|
+
logger_default.info("预下载额度:");
|
|
873
|
+
for (const budget of result.budgets) {
|
|
874
|
+
const size = `${formatAnalyzeBytes(budget.estimatedBytes)} / ${formatAnalyzeBytes(budget.limitBytes)}`;
|
|
875
|
+
const unknown = budget.unknownPackages.length > 0 ? `,未知:${budget.unknownPackages.join("、")}` : "";
|
|
876
|
+
const status = budget.status === "exceeded" ? "超限" : budget.status === "unknown" ? "待确认" : "正常";
|
|
877
|
+
logger_default.info(`- ${budget.sourcePackage}:${size},${status}${unknown}`);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
for (const limitation of result.limitations) logger_default.warn(`- 限制:${limitation}`);
|
|
881
|
+
}
|
|
882
|
+
async function writeAnalyzeResult(result, outputOption, configService, format = "json", previousResult) {
|
|
883
|
+
if (!outputOption) return;
|
|
884
|
+
const baseDir = configService.cwd;
|
|
885
|
+
const resolvedOutputPath = path.isAbsolute(outputOption) ? outputOption : path.resolve(baseDir, outputOption);
|
|
886
|
+
await fs.ensureDir(path.dirname(resolvedOutputPath));
|
|
887
|
+
const content = format === "markdown" && "packages" in result ? createAnalyzeMarkdownReport(result, previousResult) : format === "pr" && "packages" in result ? createAnalyzePrMarkdownReport(result, previousResult) : JSON.stringify(result, null, 2);
|
|
888
|
+
await fs.writeFile(resolvedOutputPath, `${content}\n`, "utf8");
|
|
889
|
+
const relativeOutput = configService.relativeCwd(resolvedOutputPath);
|
|
890
|
+
logger_default.success(`分析结果已写入 ${colors.green(relativeOutput)}`);
|
|
891
|
+
return resolvedOutputPath;
|
|
892
|
+
}
|
|
893
|
+
function formatMetricSummary(label, metric) {
|
|
894
|
+
if (!metric.count || metric.averageMs === void 0 || metric.maxMs === void 0) return;
|
|
895
|
+
return `${label} avg ${metric.averageMs.toFixed(2)} ms,max ${metric.maxMs.toFixed(2)} ms`;
|
|
896
|
+
}
|
|
897
|
+
function formatOperationSummary(label, operation) {
|
|
898
|
+
if (!operation.count || operation.average === void 0 || operation.max === void 0) return;
|
|
899
|
+
return `${label} avg ${operation.average.toFixed(1)},max ${operation.max.toFixed(0)}`;
|
|
900
|
+
}
|
|
901
|
+
function formatCountItems(items, limit = 5) {
|
|
902
|
+
return items.slice(0, limit).map((item) => `${item.name} x${item.count}`).join(",");
|
|
903
|
+
}
|
|
904
|
+
function printHmrProfileAnalysisSummary(result, configService) {
|
|
905
|
+
logger_default.success("HMR profile 分析完成");
|
|
906
|
+
logger_default.info(`- profile:${colors.green(configService.relativeCwd(result.profilePath))}`);
|
|
907
|
+
logger_default.info(`- 样本:${result.sampleCount} 条`);
|
|
908
|
+
if (result.firstTimestamp && result.lastTimestamp) logger_default.info(`- 时间范围:${result.firstTimestamp} -> ${result.lastTimestamp}`);
|
|
909
|
+
const totalSummary = formatMetricSummary("total", result.metrics.totalMs);
|
|
910
|
+
const buildStartSummary = formatMetricSummary("build-start", result.metrics.buildStartMs);
|
|
911
|
+
const pluginResolveSummary = formatMetricSummary("plugin-resolve", result.metrics.pluginResolveMs);
|
|
912
|
+
const coreLoadSummary = formatMetricSummary("core-load", result.metrics.coreLoadMs);
|
|
913
|
+
const entryLoadSummary = formatMetricSummary("entry-load", result.metrics.entryLoadMs);
|
|
914
|
+
const vueReadSourceSummary = formatMetricSummary("vue-read", result.metrics.vueReadSourceMs);
|
|
915
|
+
const vueCompileSummary = formatMetricSummary("vue-compile", result.metrics.vueCompileMs);
|
|
916
|
+
const vueFinalizeCompiledSummary = formatMetricSummary("vue-finalize-compiled", result.metrics.vueFinalizeCompiledMs);
|
|
917
|
+
const vueFinalizeCodeSummary = formatMetricSummary("vue-finalize-code", result.metrics.vueFinalizeCodeMs);
|
|
918
|
+
const entryEmitOutputSummary = formatMetricSummary("entry-emit-output", result.metrics.entryEmitOutputMs);
|
|
919
|
+
const entryTemplateScanSummary = formatMetricSummary("entry-template-scan", result.metrics.entryTemplateScanMs);
|
|
920
|
+
const entryAutoImportSummary = formatMetricSummary("entry-auto-import", result.metrics.entryAutoImportMs);
|
|
921
|
+
const entryScriptSetupSummary = formatMetricSummary("entry-script-setup", result.metrics.entryScriptSetupMs);
|
|
922
|
+
const entryVueSignatureSummary = formatMetricSummary("entry-vue-signature", result.metrics.entryVueSignatureMs);
|
|
923
|
+
const entrySidecarResolveSummary = formatMetricSummary("entry-sidecar-resolve", result.metrics.entrySidecarResolveMs);
|
|
924
|
+
const entryJsonReadSummary = formatMetricSummary("entry-json-read", result.metrics.entryJsonReadMs);
|
|
925
|
+
const entryVueConfigSummary = formatMetricSummary("entry-vue-config", result.metrics.entryVueConfigMs);
|
|
926
|
+
const entryPrepareSummary = formatMetricSummary("entry-prepare", result.metrics.entryPrepareMs);
|
|
927
|
+
const entryResolveSummary = formatMetricSummary("entry-resolve", result.metrics.entryResolveMs);
|
|
928
|
+
const entryStyleScanSummary = formatMetricSummary("entry-style-scan", result.metrics.entryStyleScanMs);
|
|
929
|
+
const entryStyleReadSummary = formatMetricSummary("entry-style-read", result.metrics.entryStyleReadMs);
|
|
930
|
+
const entryCodeReadSummary = formatMetricSummary("entry-code-read", result.metrics.entryCodeReadMs);
|
|
931
|
+
const entryChunkEmitSummary = formatMetricSummary("entry-chunk-emit", result.metrics.entryChunkEmitMs);
|
|
932
|
+
const entryChunkLoadSummary = formatMetricSummary("entry-chunk-load", result.metrics.entryChunkLoadMs);
|
|
933
|
+
const entryChunkEmitFileSummary = formatMetricSummary("entry-chunk-emit-file", result.metrics.entryChunkEmitFileMs);
|
|
934
|
+
const entryLayoutSummary = formatMetricSummary("entry-layout", result.metrics.entryLayoutMs);
|
|
935
|
+
const requestGlobalsSummary = formatMetricSummary("request-globals", result.metrics.requestGlobalsMs);
|
|
936
|
+
const weapiResolveSummary = formatMetricSummary("weapi-resolve", result.metrics.weapiResolveMs);
|
|
937
|
+
const snapshotResolveSummary = formatMetricSummary("snapshot-resolve", result.metrics.snapshotResolveMs);
|
|
938
|
+
const snapshotBuildSummary = formatMetricSummary("snapshot-build", result.metrics.snapshotBuildMs);
|
|
939
|
+
const writeSummary = formatMetricSummary("write", result.metrics.writeMs);
|
|
940
|
+
const watchSummary = formatMetricSummary("watch->dirty", result.metrics.watchToDirtyMs);
|
|
941
|
+
const emitSummary = formatMetricSummary("emit", result.metrics.emitMs);
|
|
942
|
+
const sharedSummary = formatMetricSummary("shared", result.metrics.sharedChunkResolveMs);
|
|
943
|
+
const chunkEmitSummary = formatOperationSummary("chunk emit", result.operations.chunkEmitCount);
|
|
944
|
+
const loadSummary = formatOperationSummary("load calls", result.operations.loadCount);
|
|
945
|
+
const resolveSummary = formatOperationSummary("resolve calls", result.operations.resolveCount);
|
|
946
|
+
const skippedLoadedSummary = formatOperationSummary("loaded skips", result.operations.skippedLoadedCount);
|
|
947
|
+
for (const summary of [
|
|
948
|
+
totalSummary,
|
|
949
|
+
buildStartSummary,
|
|
950
|
+
pluginResolveSummary,
|
|
951
|
+
coreLoadSummary,
|
|
952
|
+
entryLoadSummary,
|
|
953
|
+
vueReadSourceSummary,
|
|
954
|
+
vueCompileSummary,
|
|
955
|
+
vueFinalizeCompiledSummary,
|
|
956
|
+
vueFinalizeCodeSummary,
|
|
957
|
+
entryEmitOutputSummary,
|
|
958
|
+
entryTemplateScanSummary,
|
|
959
|
+
entryAutoImportSummary,
|
|
960
|
+
entryScriptSetupSummary,
|
|
961
|
+
entryVueSignatureSummary,
|
|
962
|
+
entrySidecarResolveSummary,
|
|
963
|
+
entryJsonReadSummary,
|
|
964
|
+
entryVueConfigSummary,
|
|
965
|
+
entryPrepareSummary,
|
|
966
|
+
entryResolveSummary,
|
|
967
|
+
entryStyleScanSummary,
|
|
968
|
+
entryStyleReadSummary,
|
|
969
|
+
entryCodeReadSummary,
|
|
970
|
+
entryChunkEmitSummary,
|
|
971
|
+
entryChunkLoadSummary,
|
|
972
|
+
entryChunkEmitFileSummary,
|
|
973
|
+
entryLayoutSummary,
|
|
974
|
+
requestGlobalsSummary,
|
|
975
|
+
weapiResolveSummary,
|
|
976
|
+
snapshotResolveSummary,
|
|
977
|
+
snapshotBuildSummary,
|
|
978
|
+
writeSummary,
|
|
979
|
+
watchSummary,
|
|
980
|
+
emitSummary,
|
|
981
|
+
sharedSummary
|
|
982
|
+
]) if (summary) logger_default.info(`- ${summary}`);
|
|
983
|
+
for (const summary of [
|
|
984
|
+
loadSummary,
|
|
985
|
+
resolveSummary,
|
|
986
|
+
chunkEmitSummary,
|
|
987
|
+
skippedLoadedSummary
|
|
988
|
+
]) if (summary) logger_default.info(`- ${summary}`);
|
|
989
|
+
if (result.events.length) logger_default.info(`- 事件分布:${formatCountItems(result.events)}`);
|
|
990
|
+
if (result.dirtyReasons.length) logger_default.info(`- 主要 dirty 原因:${formatCountItems(result.dirtyReasons)}`);
|
|
991
|
+
if (result.pendingReasons.length) logger_default.info(`- 主要 pending 原因:${formatCountItems(result.pendingReasons)}`);
|
|
992
|
+
if (result.skippedLineCount > 0) logger_default.warn(`- 跳过 ${result.skippedLineCount} 条无法解析的 profile 记录`);
|
|
993
|
+
if (result.slowestSamples.length) {
|
|
994
|
+
logger_default.info("- 最慢样本:");
|
|
995
|
+
for (const sample of result.slowestSamples.slice(0, 3)) {
|
|
996
|
+
const fileLabel = sample.file ? configService.relativeCwd(sample.file) : "(unknown)";
|
|
997
|
+
logger_default.info(` - ${sample.totalMs?.toFixed(2) ?? "0.00"} ms,${sample.event ?? "unknown"},${fileLabel}`);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
/** 执行已匹配的分析命令,保持输出通道和上下文清理契约。 */
|
|
1002
|
+
async function runAnalyzeCommand(root, options) {
|
|
1003
|
+
setCommandNodeEnv("production");
|
|
1004
|
+
filterDuplicateOptions(options);
|
|
1005
|
+
options.uiHost = parseDashboardUiHost(options.uiHost);
|
|
1006
|
+
const configFile = resolveConfigFile(options);
|
|
1007
|
+
const outputJson = coerceBooleanOption(options.json);
|
|
1008
|
+
return withAnalyzeOutput(Boolean(outputJson), async (writeJson) => {
|
|
1009
|
+
const outputMarkdown = coerceBooleanOption(options.markdown);
|
|
1010
|
+
const reportType = typeof options.report === "string" ? options.report.trim() : "";
|
|
1011
|
+
const outputPrReport = reportType === "pr";
|
|
1012
|
+
if (reportType && !outputPrReport) throw new Error(`不支持的 analyze report 类型:${reportType}`);
|
|
1013
|
+
const budgetCheck = coerceBooleanOption(options.budgetCheck);
|
|
1014
|
+
const glassEaselCheck = coerceBooleanOption(options.glassEaselCheck);
|
|
1015
|
+
const launchDashboard = !outputJson && !outputMarkdown && !outputPrReport && !budgetCheck && !glassEaselCheck;
|
|
1016
|
+
const targets = resolveRuntimeTargets(options);
|
|
1017
|
+
const inlineConfig = createInlineConfig(targets);
|
|
1018
|
+
let ctx;
|
|
1019
|
+
try {
|
|
1020
|
+
ctx = await createCompilerContext({
|
|
1021
|
+
cwd: root,
|
|
1022
|
+
mode: options.mode ?? "production",
|
|
1023
|
+
configFile,
|
|
1024
|
+
inlineConfig,
|
|
1025
|
+
cliPlatform: targets.rawPlatform,
|
|
1026
|
+
projectConfigPath: options.projectConfig
|
|
1027
|
+
});
|
|
1028
|
+
logRuntimeTarget(targets, {
|
|
1029
|
+
silent: outputJson || outputMarkdown,
|
|
1030
|
+
resolvedConfigPlatform: ctx.configService.platform
|
|
1031
|
+
});
|
|
1032
|
+
const outputOption = typeof options.output === "string" ? options.output.trim() : "";
|
|
1033
|
+
if (options.hmrProfile !== void 0 && options.hmrProfile !== false) {
|
|
1034
|
+
const profileOption = typeof options.hmrProfile === "string" && options.hmrProfile.trim() ? options.hmrProfile.trim() : ctx.configService.weappViteConfig.hmr?.profileJson;
|
|
1035
|
+
const profilePath = resolveHmrProfileJsonPath({
|
|
1036
|
+
cwd: ctx.configService.cwd,
|
|
1037
|
+
option: profileOption,
|
|
1038
|
+
fallbackToDefault: true
|
|
1039
|
+
});
|
|
1040
|
+
if (!profilePath) throw new Error("未找到可用的 HMR profile 文件路径");
|
|
1041
|
+
const hmrProfileResult = await analyzeHmrProfile({ profilePath });
|
|
1042
|
+
const writtenPath = await writeAnalyzeResult(hmrProfileResult, outputOption, ctx.configService);
|
|
1043
|
+
if (outputJson) {
|
|
1044
|
+
if (!writtenPath) writeJson(hmrProfileResult);
|
|
1045
|
+
} else printHmrProfileAnalysisSummary(hmrProfileResult, ctx.configService);
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
if (coerceBooleanOption(options.preload)) {
|
|
1049
|
+
if (targets.kind !== "miniprogram" || ctx.configService.platform !== "weapp") throw new Error("preloadRule 分析目前仅支持微信小程序平台。");
|
|
1050
|
+
const packageAnalysis = await analyzeSubpackages(ctx);
|
|
1051
|
+
const preloadResult = await analyzePreloadRules(ctx, { packageAnalysis });
|
|
1052
|
+
const writtenPath = await writeAnalyzeResult(preloadResult, outputOption, ctx.configService);
|
|
1053
|
+
if (outputJson && !writtenPath) writeJson(preloadResult);
|
|
1054
|
+
if (!outputJson && !writtenPath) printPreloadAnalysisSummary(preloadResult);
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
if (getBackendForCapability(targets, "web", "analyze")) {
|
|
1058
|
+
const webResult = createWebAnalyzeResult(ctx.configService, { platform: "web" });
|
|
1059
|
+
const writtenPath = await writeAnalyzeResult(webResult, outputOption, ctx.configService);
|
|
1060
|
+
if (outputJson) {
|
|
1061
|
+
if (!writtenPath) writeJson(webResult);
|
|
1062
|
+
} else printWebAnalysisSummary(webResult);
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
if (!getBackendForCapability(targets, "miniprogram", "analyze")) {
|
|
1066
|
+
logger_default.warn("当前命令不支持该平台,请通过 --platform weapp 或 --platform web 指定目标。");
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
const previousResult = await readLatestAnalyzeHistorySnapshot(ctx.configService);
|
|
1070
|
+
ctx.runtimeState.glassEasel.silent = Boolean(outputJson || outputMarkdown || outputPrReport || glassEaselCheck);
|
|
1071
|
+
const artifactSnapshot = launchDashboard ? createDashboardArtifactSnapshot() : void 0;
|
|
1072
|
+
const result = artifactSnapshot ? await analyzeSubpackages(ctx, { onArtifact: artifactSnapshot.capture }) : await analyzeSubpackages(ctx);
|
|
1073
|
+
await writeAnalyzeHistorySnapshot(result, ctx.configService);
|
|
1074
|
+
const writtenPath = await writeAnalyzeResult(result, outputOption, ctx.configService, outputPrReport ? "pr" : outputMarkdown ? "markdown" : "json", previousResult);
|
|
1075
|
+
if (outputPrReport) {
|
|
1076
|
+
if (!writtenPath) process.stdout.write(`${createAnalyzePrMarkdownReport(result, previousResult)}\n`);
|
|
1077
|
+
} else if (outputMarkdown) {
|
|
1078
|
+
if (!writtenPath) process.stdout.write(`${createAnalyzeMarkdownReport(result, previousResult)}\n`);
|
|
1079
|
+
} else if (outputJson) {
|
|
1080
|
+
if (!writtenPath) writeJson(result);
|
|
1081
|
+
}
|
|
1082
|
+
const budgetFailed = budgetCheck ? outputJson ? createAnalyzeBudgetCheck(result).some((item) => item.status === "exceeded" || item.status === "unknown") : printBudgetCheckSummary(result) : false;
|
|
1083
|
+
const glassEaselFailed = glassEaselCheck && !outputJson ? printGlassEaselCheckSummary(result) : glassEaselCheck && result.glassEasel.summary.errors > 0;
|
|
1084
|
+
if (budgetFailed || glassEaselFailed) process.exitCode = 1;
|
|
1085
|
+
if (budgetCheck || glassEaselCheck) return;
|
|
1086
|
+
if (artifactSnapshot) {
|
|
1087
|
+
printAnalysisSummary(result);
|
|
1088
|
+
await startAnalyzeDashboard(result, {
|
|
1089
|
+
uiHost: options.uiHost,
|
|
1090
|
+
artifacts: artifactSnapshot.files,
|
|
1091
|
+
cwd: ctx.configService.cwd,
|
|
1092
|
+
packageManagerAgent: ctx.configService.packageManager.agent,
|
|
1093
|
+
pluginRoot: ctx.configService.absolutePluginRoot,
|
|
1094
|
+
srcRoot: ctx.configService.absoluteSrcRoot,
|
|
1095
|
+
previousResult
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
logger_default.error(error);
|
|
1100
|
+
process.exitCode = 1;
|
|
1101
|
+
} finally {
|
|
1102
|
+
if (ctx) {
|
|
1103
|
+
for (const backend of [...targets.entries].reverse()) if (backend.descriptor.capabilities.analyze) await backend.driver?.close?.(ctx);
|
|
1104
|
+
}
|
|
1105
|
+
terminateStaleSassEmbeddedProcess();
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
//#endregion
|
|
1110
|
+
export { runAnalyzeCommand };
|