weapp-vite 6.16.3 → 6.16.5
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/auto-routes.mjs +1 -1
- package/dist/cli.mjs +2216 -2142
- package/dist/{config-Deromcuk.d.mts → config-CXABkCb8.d.mts} +42 -4
- package/dist/config.d.mts +1 -1
- package/dist/config.mjs +1 -1
- package/dist/{createContext-C775dw5P.mjs → createContext-CnilYzXS.mjs} +210 -95
- package/dist/file-DJL8Grbf.mjs +2 -0
- package/dist/{file-CueQM5Yi.mjs → file-D_s683Q5.mjs} +1 -1
- package/dist/getInstance-DDog7ywe.mjs +2 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +3 -3
- package/dist/json.d.mts +1 -1
- package/dist/mcp.d.mts +1 -1
- package/dist/{pluginHost-SJdl15d3.mjs → pluginHost-BEnGeaSo.mjs} +7 -4
- package/dist/types.d.mts +1 -1
- package/package.json +11 -11
- package/dist/file-D7c-LQA5.mjs +0 -2
- package/dist/getInstance-BzywMrU-.mjs +0 -2
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { C as
|
|
1
|
+
import { C as getDefaultIdeProjectRoot, S as createCjsConfigLoadError, T as isPathInside, _ as resolveWeappConfigFile, b as loadViteConfigFile, f as resolveWeappViteTarget, h as resolveHmrProfileJsonPath, m as SHARED_CHUNK_VIRTUAL_PREFIX, n as syncProjectSupportFiles, p as createSharedBuildConfig, r as syncManagedTsconfigBootstrapFiles, s as formatBytes, t as createCompilerContext, v as checkRuntime, w as shouldPassPlatformArgToIdeOpen, x as parseCommentJson, y as getProjectConfigFileName } from "./createContext-CnilYzXS.mjs";
|
|
2
2
|
import { r as logger_default, t as colors } from "./logger-CgxdNjvb.mjs";
|
|
3
|
-
import { h as VERSION } from "./file-
|
|
3
|
+
import { h as VERSION } from "./file-D_s683Q5.mjs";
|
|
4
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";
|
|
@@ -11,2336 +11,2384 @@ import process from "node:process";
|
|
|
11
11
|
import fs$2 from "node:fs/promises";
|
|
12
12
|
import { build, createServer } from "vite";
|
|
13
13
|
import os from "node:os";
|
|
14
|
-
import { execFile } from "node:child_process";
|
|
14
|
+
import { execFile, spawn } from "node:child_process";
|
|
15
15
|
import { Buffer } from "node:buffer";
|
|
16
16
|
import { cac } from "cac";
|
|
17
|
-
import { brotliCompressSync, gzipSync } from "node:zlib";
|
|
18
|
-
import { resolveCommand } from "package-manager-detector/commands";
|
|
19
17
|
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";
|
|
20
18
|
import { promisify } from "node:util";
|
|
19
|
+
import { brotliCompressSync, gzipSync } from "node:zlib";
|
|
20
|
+
import { resolveCommand } from "package-manager-detector/commands";
|
|
21
21
|
import { generateJs, generateJson, generateWxml, generateWxss } from "@weapp-core/schematics";
|
|
22
22
|
import { determineAgent } from "@vercel/detect-agent";
|
|
23
23
|
import { initConfig } from "@weapp-core/init";
|
|
24
24
|
import { createInterface } from "node:readline/promises";
|
|
25
25
|
import { clearTimeout, setTimeout as setTimeout$1 } from "node:timers";
|
|
26
|
-
//#region src/
|
|
27
|
-
function
|
|
28
|
-
if (
|
|
29
|
-
|
|
26
|
+
//#region src/cli/runtime.ts
|
|
27
|
+
function logRuntimeTarget(targets, options = {}) {
|
|
28
|
+
if (options.silent) return;
|
|
29
|
+
if (targets.label === "config") {
|
|
30
|
+
const resolvedPlatform = targets.platform ?? options.resolvedConfigPlatform;
|
|
31
|
+
if (resolvedPlatform) {
|
|
32
|
+
logger_default.info(`目标平台:${colors.green(resolvedPlatform)}`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
logger_default.info(`目标平台:使用配置文件中的 ${colors.bold(colors.green("weapp.platform"))}`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
logger_default.info(`目标平台:${colors.green(targets.label)}`);
|
|
39
|
+
}
|
|
40
|
+
function resolveRuntimeTargets(options) {
|
|
41
|
+
const rawPlatform = typeof options.platform === "string" ? options.platform : typeof options.p === "string" ? options.p : void 0;
|
|
42
|
+
const target = resolveWeappViteTarget(rawPlatform, { warn: (message) => logger_default.warn(message) });
|
|
30
43
|
return {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
44
|
+
runMini: target.runMini,
|
|
45
|
+
runWeb: target.runWeb,
|
|
46
|
+
platform: target.kind === "miniprogram" ? target.platform : void 0,
|
|
47
|
+
label: target.label,
|
|
48
|
+
rawPlatform
|
|
34
49
|
};
|
|
35
50
|
}
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
count
|
|
40
|
-
}));
|
|
51
|
+
function createInlineConfig(platform) {
|
|
52
|
+
if (!platform) return;
|
|
53
|
+
return { weapp: { platform } };
|
|
41
54
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/cli/openIde/execute.ts
|
|
57
|
+
function readArgOption(argv, ...names) {
|
|
58
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
59
|
+
const current = argv[index];
|
|
60
|
+
if (!names.includes(current)) continue;
|
|
61
|
+
const next = argv[index + 1];
|
|
62
|
+
if (typeof next === "string" && !next.startsWith("-")) return next;
|
|
46
63
|
}
|
|
47
64
|
}
|
|
48
|
-
function
|
|
49
|
-
|
|
65
|
+
async function tryExecuteWechatIdeCliCommandByAutomator(argv, projectPath) {
|
|
66
|
+
if (!projectPath) return false;
|
|
67
|
+
const command = argv[0];
|
|
68
|
+
if (!command) return false;
|
|
69
|
+
if (command === "compile") {
|
|
70
|
+
await compileWechatIdeByAutomator({ projectPath });
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
if (command === "cache") {
|
|
74
|
+
const cleanType = readArgOption(argv, "--clean", "-c");
|
|
75
|
+
if (cleanType !== "compile" && cleanType !== "all") return false;
|
|
76
|
+
await clearWechatIdeCacheByAutomator({
|
|
77
|
+
clean: cleanType,
|
|
78
|
+
projectPath
|
|
79
|
+
});
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
async function tryExecuteWechatIdeCliCommandByHttp(argv, projectPath) {
|
|
85
|
+
const command = argv[0];
|
|
86
|
+
if (!command) return false;
|
|
87
|
+
if (command === "compile") {
|
|
88
|
+
if (!projectPath) return false;
|
|
89
|
+
await openWechatIdeProjectByHttp(projectPath);
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
if (command === "reset-fileutils") {
|
|
93
|
+
if (!projectPath) return false;
|
|
94
|
+
await resetWechatIdeFileUtilsByHttp(projectPath);
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
if (command === "engine" && argv[1] === "build") {
|
|
98
|
+
const engineProjectPath = argv[2] || projectPath;
|
|
99
|
+
if (!engineProjectPath) return false;
|
|
100
|
+
await runWechatIdeEngineBuild(engineProjectPath, { logPath: readArgOption(argv, "--logPath", "-l") });
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
async function tryExecuteWechatIdeCliCommandByHelper(argv) {
|
|
106
|
+
const command = argv[0];
|
|
107
|
+
if (!command) return false;
|
|
108
|
+
if (command === "close") {
|
|
109
|
+
await closeWechatIdeProject();
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
if (command === "quit") {
|
|
113
|
+
await quitWechatIde();
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
if (command === "cache") {
|
|
117
|
+
const cleanType = readArgOption(argv, "--clean", "-c");
|
|
118
|
+
if (!cleanType) return false;
|
|
119
|
+
await clearWechatIdeCache({ clean: cleanType });
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
50
123
|
}
|
|
51
124
|
/**
|
|
52
|
-
* @description
|
|
125
|
+
* @description 统一执行 weapp-ide-cli 命令,并在登录失效时复用同一套重试交互。
|
|
53
126
|
*/
|
|
54
|
-
async function
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
127
|
+
async function executeWechatIdeCliCommand(argv, options = {}) {
|
|
128
|
+
const { automatorMode = "prefer", cancelLevel = "warn", httpMode = "prefer", onNonLoginError, onRetry, projectPath } = options;
|
|
129
|
+
await runWithSuspendedSharedInput(async () => {
|
|
130
|
+
if (httpMode !== "skip") try {
|
|
131
|
+
if (await tryExecuteWechatIdeCliCommandByHttp(argv, projectPath)) return;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (httpMode === "require") throw error;
|
|
134
|
+
}
|
|
61
135
|
try {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
136
|
+
if (await tryExecuteWechatIdeCliCommandByAutomator(argv, projectPath)) return;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (automatorMode === "require") throw error;
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
if (await tryExecuteWechatIdeCliCommandByHelper(argv)) return;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
if (onNonLoginError) {
|
|
144
|
+
onNonLoginError(error);
|
|
145
|
+
return;
|
|
66
146
|
}
|
|
67
|
-
|
|
68
|
-
} catch {
|
|
69
|
-
skippedLineCount += 1;
|
|
147
|
+
throw error;
|
|
70
148
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
149
|
+
await runRetryableCommand({
|
|
150
|
+
createCancelError: () => /* @__PURE__ */ new Error("cancelled"),
|
|
151
|
+
execute: async () => {
|
|
152
|
+
try {
|
|
153
|
+
await parse(argv);
|
|
154
|
+
return null;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (!isWechatIdeLoginRequiredError(error)) {
|
|
157
|
+
if (onNonLoginError) {
|
|
158
|
+
onNonLoginError(error);
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
return error;
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
isRetryableResult: (result) => result !== null,
|
|
167
|
+
onCancel: () => {},
|
|
168
|
+
onRetry: () => {
|
|
169
|
+
onRetry?.();
|
|
170
|
+
},
|
|
171
|
+
promptRetry: async (error) => await promptWechatIdeLoginRetry({
|
|
172
|
+
cancelLevel,
|
|
173
|
+
error,
|
|
174
|
+
logger: logger_default
|
|
175
|
+
}),
|
|
176
|
+
shouldRetry: (action) => action === "retry"
|
|
177
|
+
});
|
|
99
178
|
});
|
|
100
|
-
const slowestSamples = [...samples].sort((left, right) => (right.totalMs ?? 0) - (left.totalMs ?? 0)).slice(0, options.topSlowest ?? 5);
|
|
101
|
-
return {
|
|
102
|
-
runtime: "mini",
|
|
103
|
-
kind: "hmr-profile",
|
|
104
|
-
generatedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
105
|
-
profilePath: options.profilePath,
|
|
106
|
-
sampleCount: samples.length,
|
|
107
|
-
skippedLineCount,
|
|
108
|
-
firstTimestamp: orderedByTime[0]?.timestamp,
|
|
109
|
-
lastTimestamp: orderedByTime.at(-1)?.timestamp,
|
|
110
|
-
metrics: {
|
|
111
|
-
totalMs: createMetricSummary(totalValues),
|
|
112
|
-
buildCoreMs: createMetricSummary(buildCoreValues),
|
|
113
|
-
transformMs: createMetricSummary(transformValues),
|
|
114
|
-
writeMs: createMetricSummary(writeValues),
|
|
115
|
-
watchToDirtyMs: createMetricSummary(watchToDirtyValues),
|
|
116
|
-
emitMs: createMetricSummary(emitValues),
|
|
117
|
-
sharedChunkResolveMs: createMetricSummary(sharedChunkValues)
|
|
118
|
-
},
|
|
119
|
-
events: sortCountEntries(eventCounts),
|
|
120
|
-
dirtyReasons: sortCountEntries(dirtyReasonCounts),
|
|
121
|
-
pendingReasons: sortCountEntries(pendingReasonCounts),
|
|
122
|
-
slowestSamples
|
|
123
|
-
};
|
|
124
179
|
}
|
|
125
180
|
//#endregion
|
|
126
|
-
//#region src/
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return
|
|
134
|
-
}
|
|
135
|
-
|
|
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
|
-
};
|
|
181
|
+
//#region src/cli/openIde/close.ts
|
|
182
|
+
const execFileAsync = promisify(execFile);
|
|
183
|
+
async function closeIdeByAppleScript() {
|
|
184
|
+
if (process.platform !== "darwin") return false;
|
|
185
|
+
const appName = process.env.WEAPP_DEVTOOLS_APP_NAME || "wechatwebdevtools";
|
|
186
|
+
try {
|
|
187
|
+
await execFileAsync("osascript", ["-e", `tell application "${appName}" to quit`]);
|
|
188
|
+
return true;
|
|
189
|
+
} catch {
|
|
190
|
+
return false;
|
|
147
191
|
}
|
|
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
192
|
}
|
|
174
|
-
function
|
|
175
|
-
if (!
|
|
176
|
-
|
|
193
|
+
async function closeIdeByProcessKill(cliPath) {
|
|
194
|
+
if (!cliPath) return false;
|
|
195
|
+
const appContentsRoot = cliPath.includes(".app/") ? cliPath.slice(0, cliPath.indexOf(".app/") + 4) : path.dirname(path.dirname(cliPath));
|
|
196
|
+
try {
|
|
197
|
+
await execFileAsync("pkill", ["-f", appContentsRoot]);
|
|
198
|
+
return true;
|
|
199
|
+
} catch {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
177
202
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
203
|
+
/**
|
|
204
|
+
* @description 关闭微信开发者工具,并在 CLI 不可用时回退到系统级关闭。
|
|
205
|
+
*/
|
|
206
|
+
async function closeIde$1() {
|
|
207
|
+
const config = await getConfig();
|
|
208
|
+
const cliPath = config.cliPath?.trim() ? config.cliPath : null;
|
|
209
|
+
try {
|
|
210
|
+
await closeWechatIdeProject();
|
|
211
|
+
return true;
|
|
212
|
+
} catch (error) {
|
|
213
|
+
if (isWechatIdeLoginRequiredError(error)) try {
|
|
214
|
+
await executeWechatIdeCliCommand(["close"], {
|
|
215
|
+
cancelLevel: "warn",
|
|
216
|
+
onNonLoginError: (retryError) => logger_default.error(retryError),
|
|
217
|
+
onRetry: () => logger_default.info("正在重试连接微信开发者工具...")
|
|
218
|
+
});
|
|
219
|
+
return true;
|
|
220
|
+
} catch (retryError) {
|
|
221
|
+
logger_default.error(retryError);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
logger_default.warn("微信开发者工具 CLI close 执行失败,尝试回退为系统级关闭。");
|
|
225
|
+
logger_default.error(error);
|
|
226
|
+
}
|
|
227
|
+
if (await closeIdeByAppleScript()) {
|
|
228
|
+
logger_default.info("已回退为系统级关闭微信开发者工具。");
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
if (await closeIdeByProcessKill(cliPath)) {
|
|
232
|
+
logger_default.info("已回退为进程级关闭微信开发者工具。");
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
181
237
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (normalizedRequest.startsWith("/")) return normalizeRoute(normalizedRequest);
|
|
187
|
-
return normalizeRoute(normalizedRequest);
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region src/cli/openIde/reuse.ts
|
|
240
|
+
function formatReuseOpenedWechatIdePrompt() {
|
|
241
|
+
return `目标项目已在微信开发者工具中打开,已跳过重复打开。按 ${colors.bold(colors.green("r"))} 关闭当前窗口后重新打开。`;
|
|
188
242
|
}
|
|
189
|
-
function
|
|
190
|
-
|
|
243
|
+
async function openWechatIdeByAutomator(projectPath) {
|
|
244
|
+
(await launchAutomator({
|
|
245
|
+
projectPath,
|
|
246
|
+
trustProject: true
|
|
247
|
+
})).disconnect();
|
|
191
248
|
}
|
|
192
|
-
function
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
for (const page of toStringArray(item.pages)) pages.add(normalizeRoute(posix.join(item.root, page)));
|
|
249
|
+
async function connectOpenedProject(projectPath) {
|
|
250
|
+
try {
|
|
251
|
+
return await connectOpenedAutomator({
|
|
252
|
+
projectPath,
|
|
253
|
+
timeout: 3e3
|
|
254
|
+
});
|
|
255
|
+
} catch {
|
|
256
|
+
return null;
|
|
201
257
|
}
|
|
202
|
-
return pages;
|
|
203
258
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
259
|
+
/**
|
|
260
|
+
* @description 若当前项目已在微信开发者工具中打开且自动化可连通,则直接复用现有会话,避免重复拉起 IDE。
|
|
261
|
+
*/
|
|
262
|
+
async function tryReuseOpenedWechatIde(projectPath, closeIde) {
|
|
263
|
+
const miniProgram = await connectOpenedProject(projectPath);
|
|
264
|
+
if (!miniProgram) return null;
|
|
265
|
+
miniProgram.disconnect();
|
|
266
|
+
logger_default.info(formatReuseOpenedWechatIdePrompt());
|
|
267
|
+
if (await promptRetryKeypress({ logger: logger_default }) !== "retry") return {
|
|
268
|
+
reopened: false,
|
|
269
|
+
reused: true
|
|
212
270
|
};
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
271
|
+
logger_default.info(colors.bold(colors.green("正在关闭当前已打开项目,并重新拉起微信开发者工具...")));
|
|
272
|
+
if (!await closeIde()) logger_default.warn("关闭当前微信开发者工具失败,仍继续尝试重新打开目标项目。");
|
|
273
|
+
await openWechatIdeByAutomator(projectPath);
|
|
274
|
+
return {
|
|
275
|
+
reopened: true,
|
|
276
|
+
reused: false
|
|
218
277
|
};
|
|
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
278
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
});
|
|
239
|
-
} catch {}
|
|
240
|
-
}
|
|
241
|
-
return configs;
|
|
279
|
+
/**
|
|
280
|
+
* @description 对已打开的目标项目执行强制重开,以刷新最新构建产物。
|
|
281
|
+
*/
|
|
282
|
+
async function reopenOpenedWechatIde(projectPath, closeIde) {
|
|
283
|
+
const miniProgram = await connectOpenedProject(projectPath);
|
|
284
|
+
if (!miniProgram) return false;
|
|
285
|
+
miniProgram.disconnect();
|
|
286
|
+
logger_default.info("目标项目已在微信开发者工具中打开,当前命令将主动重开以刷新最新构建产物。");
|
|
287
|
+
if (!await closeIde()) logger_default.warn("关闭当前微信开发者工具失败,仍继续尝试重新打开目标项目。");
|
|
288
|
+
await openWechatIdeByAutomator(projectPath);
|
|
289
|
+
return true;
|
|
242
290
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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);
|
|
291
|
+
//#endregion
|
|
292
|
+
//#region src/cli/openIde/index.ts
|
|
293
|
+
function shouldLogAutomatorFallbackError() {
|
|
294
|
+
const flag = process.env.WEAPP_VITE_DEBUG_AUTOMATOR_OPEN;
|
|
295
|
+
return flag === "1" || flag === "true";
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* @description 执行 IDE 打开流程,并在登录失效时允许按键重试。
|
|
299
|
+
*/
|
|
300
|
+
async function runWechatIdeOpenWithRetry(argv) {
|
|
301
|
+
await executeWechatIdeCliCommand(argv, {
|
|
302
|
+
cancelLevel: "warn",
|
|
303
|
+
onNonLoginError: (error) => logger_default.error(error),
|
|
304
|
+
onRetry: () => {
|
|
305
|
+
logger_default.info(colors.bold(colors.green("正在重试连接微信开发者工具...")));
|
|
271
306
|
}
|
|
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
307
|
});
|
|
289
308
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
309
|
+
/**
|
|
310
|
+
* @description 根据 mpDistRoot 推导 IDE 项目目录(目录内应包含 project/mini 配置)
|
|
311
|
+
*/
|
|
312
|
+
function resolveIdeProjectPath(mpDistRoot) {
|
|
313
|
+
if (!mpDistRoot || !mpDistRoot.trim()) return;
|
|
314
|
+
const parent = path.dirname(mpDistRoot);
|
|
315
|
+
if (!parent || parent === "." || parent === "/") return;
|
|
316
|
+
return parent;
|
|
299
317
|
}
|
|
300
|
-
|
|
301
|
-
|
|
318
|
+
/**
|
|
319
|
+
* @description 结合 mpDistRoot 与配置根目录解析最终 IDE 项目目录。
|
|
320
|
+
*/
|
|
321
|
+
function resolveIdeProjectRoot(mpDistRoot, cwd) {
|
|
322
|
+
return resolveIdeProjectPath(mpDistRoot) ?? cwd;
|
|
302
323
|
}
|
|
303
|
-
function
|
|
304
|
-
|
|
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
|
-
};
|
|
324
|
+
async function closeIde() {
|
|
325
|
+
return await closeIde$1();
|
|
315
326
|
}
|
|
316
|
-
function
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
};
|
|
327
|
+
async function tryOpenWechatIdeByAutomator(projectPath, options) {
|
|
328
|
+
if (options.reuseOpenedProject === false) {
|
|
329
|
+
if (await reopenOpenedWechatIde(projectPath, closeIde)) return true;
|
|
330
|
+
}
|
|
331
|
+
const reuseResult = await tryReuseOpenedWechatIde(projectPath, closeIde);
|
|
332
|
+
if (reuseResult?.reused || reuseResult?.reopened) return true;
|
|
333
|
+
await openWechatIdeByAutomator(projectPath);
|
|
334
|
+
return true;
|
|
330
335
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
336
|
+
/**
|
|
337
|
+
* @description 打开后主动刷新微信开发者工具的项目索引,避免模拟器沿用过期 app 配置。
|
|
338
|
+
*/
|
|
339
|
+
async function stabilizeOpenedWechatIdeProject(projectPath, servicePortEnabled) {
|
|
340
|
+
if (servicePortEnabled === false) return;
|
|
341
|
+
try {
|
|
342
|
+
await executeWechatIdeCliCommand(["compile"], {
|
|
343
|
+
httpMode: "prefer",
|
|
344
|
+
onNonLoginError: (error) => logger_default.error(error),
|
|
345
|
+
projectPath
|
|
346
|
+
});
|
|
347
|
+
await executeWechatIdeCliCommand([
|
|
348
|
+
"reset-fileutils",
|
|
349
|
+
"-p",
|
|
350
|
+
projectPath
|
|
351
|
+
], {
|
|
352
|
+
httpMode: "prefer",
|
|
353
|
+
onNonLoginError: (error) => logger_default.error(error),
|
|
354
|
+
projectPath
|
|
355
|
+
});
|
|
356
|
+
await executeWechatIdeCliCommand([
|
|
357
|
+
"engine",
|
|
358
|
+
"build",
|
|
359
|
+
projectPath
|
|
360
|
+
], {
|
|
361
|
+
httpMode: "prefer",
|
|
362
|
+
onNonLoginError: (error) => logger_default.error(error),
|
|
363
|
+
projectPath
|
|
364
|
+
});
|
|
365
|
+
try {
|
|
366
|
+
await executeWechatIdeCliCommand(["compile"], {
|
|
367
|
+
automatorMode: "require",
|
|
368
|
+
httpMode: "skip",
|
|
369
|
+
projectPath
|
|
370
|
+
});
|
|
371
|
+
} catch (error) {
|
|
372
|
+
if (shouldLogAutomatorFallbackError()) logger_default.error(error);
|
|
339
373
|
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const VIRTUAL_MODULE_INDICATOR = "\0";
|
|
345
|
-
const VIRTUAL_PREFIX = `${SHARED_CHUNK_VIRTUAL_PREFIX}/`;
|
|
346
|
-
function classifyModuleSourceKind(options) {
|
|
347
|
-
if (options.isNodeModule) return "node_modules";
|
|
348
|
-
if (options.inSrc) return "src";
|
|
349
|
-
if (options.inPlugin) return "plugin";
|
|
350
|
-
return "workspace";
|
|
351
|
-
}
|
|
352
|
-
function resolvePluginAssetAbsolute(normalizedFileName, pluginRoot) {
|
|
353
|
-
if (!pluginRoot) return;
|
|
354
|
-
const pluginBase = posix.basename(pluginRoot);
|
|
355
|
-
if (normalizedFileName !== pluginBase && !normalizedFileName.startsWith(`${pluginBase}/`)) return;
|
|
356
|
-
const relative = normalizedFileName === pluginBase ? "" : normalizedFileName.slice(pluginBase.length + 1);
|
|
357
|
-
const absolute = posix.resolve(pluginRoot, relative);
|
|
358
|
-
return isPathInside(pluginRoot, absolute) ? absolute : void 0;
|
|
374
|
+
} catch (error) {
|
|
375
|
+
logger_default.warn("刷新微信开发者工具项目索引失败,已保留当前打开状态;如模拟器仍显示旧状态,可手动刷新一次。");
|
|
376
|
+
if (shouldLogAutomatorFallbackError()) logger_default.error(error);
|
|
377
|
+
}
|
|
359
378
|
}
|
|
360
|
-
function
|
|
361
|
-
const
|
|
362
|
-
|
|
379
|
+
function createIdeOpenArgv(platform, projectPath, options = {}) {
|
|
380
|
+
const argv = ["open", "-p"];
|
|
381
|
+
if (projectPath) argv.push(projectPath);
|
|
382
|
+
if (platform === "weapp" && options.trustProject !== false) argv.push("--trust-project");
|
|
383
|
+
if (platform && shouldPassPlatformArgToIdeOpen(platform)) argv.push("--platform", platform);
|
|
384
|
+
return argv;
|
|
363
385
|
}
|
|
364
|
-
function
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
386
|
+
async function openIde(platform, projectPath, options = {}) {
|
|
387
|
+
let bootstrapResult;
|
|
388
|
+
if (platform === "weapp" && projectPath) try {
|
|
389
|
+
bootstrapResult = await bootstrapWechatDevtoolsSettings({
|
|
390
|
+
projectPath,
|
|
391
|
+
trustProject: options.trustProject
|
|
392
|
+
});
|
|
393
|
+
} catch (error) {
|
|
394
|
+
logger_default.warn("检测微信开发者工具服务端口或写入项目信任状态失败,继续执行 open 流程。");
|
|
395
|
+
logger_default.error(error);
|
|
372
396
|
}
|
|
373
|
-
|
|
374
|
-
if (
|
|
375
|
-
|
|
397
|
+
if (platform === "weapp" && projectPath && bootstrapResult?.servicePortEnabled === false) logger_default.warn("检测到微信开发者工具服务端口当前处于关闭状态,已保留用户设置并回退到普通 open 流程。");
|
|
398
|
+
if (platform === "weapp" && projectPath && options.trustProject !== false && bootstrapResult?.servicePortEnabled !== false) try {
|
|
399
|
+
if (await tryOpenWechatIdeByAutomator(projectPath, options)) {
|
|
400
|
+
await stabilizeOpenedWechatIdeProject(projectPath, bootstrapResult?.servicePortEnabled);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
} catch (error) {
|
|
404
|
+
if (isAutomatorLoginError(error)) {
|
|
405
|
+
logger_default.error("检测到微信开发者工具登录状态失效,请先登录后重试。");
|
|
406
|
+
logger_default.warn(formatAutomatorLoginError(error));
|
|
407
|
+
}
|
|
408
|
+
logger_default.warn("通过 automator 启动微信开发者工具并自动信任项目失败,回退到普通 open 流程。");
|
|
409
|
+
if (shouldLogAutomatorFallbackError()) logger_default.error(error);
|
|
410
|
+
}
|
|
411
|
+
await runWechatIdeOpenWithRetry(createIdeOpenArgv(platform, projectPath, options));
|
|
412
|
+
if (platform === "weapp" && projectPath) await stabilizeOpenedWechatIdeProject(projectPath, bootstrapResult?.servicePortEnabled);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* @description 解析 IDE 相关命令所需的平台、项目目录与配置上下文。
|
|
416
|
+
*/
|
|
417
|
+
async function resolveIdeCommandContext(options) {
|
|
418
|
+
const cwd = options.cwd ?? process.cwd();
|
|
419
|
+
let platform = options.platform;
|
|
420
|
+
let projectPath = options.projectPath;
|
|
421
|
+
if (!platform || !projectPath) try {
|
|
422
|
+
const ctx = await createCompilerContext({
|
|
423
|
+
cwd,
|
|
424
|
+
mode: options.mode ?? "development",
|
|
425
|
+
configFile: options.configFile,
|
|
426
|
+
inlineConfig: createInlineConfig(platform),
|
|
427
|
+
cliPlatform: options.cliPlatform
|
|
428
|
+
});
|
|
429
|
+
platform ??= ctx.configService.platform;
|
|
430
|
+
if (!projectPath) projectPath = resolveIdeProjectRoot(ctx.configService.mpDistRoot, ctx.configService.cwd);
|
|
376
431
|
return {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
432
|
+
cwd: ctx.configService.cwd,
|
|
433
|
+
platform,
|
|
434
|
+
projectPath,
|
|
435
|
+
weappViteConfig: ctx.configService.weappViteConfig,
|
|
436
|
+
mpDistRoot: ctx.configService.mpDistRoot
|
|
380
437
|
};
|
|
438
|
+
} catch {}
|
|
439
|
+
if (!projectPath) {
|
|
440
|
+
const defaultProjectRoot = getDefaultIdeProjectRoot(platform);
|
|
441
|
+
if (defaultProjectRoot) projectPath = resolveIdeProjectRoot(defaultProjectRoot, cwd);
|
|
381
442
|
}
|
|
382
443
|
return {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
444
|
+
cwd,
|
|
445
|
+
platform,
|
|
446
|
+
projectPath
|
|
386
447
|
};
|
|
387
448
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
449
|
+
//#endregion
|
|
450
|
+
//#region src/cli/options.ts
|
|
451
|
+
function filterDuplicateOptions(options) {
|
|
452
|
+
for (const [key, value] of Object.entries(options)) if (Array.isArray(value)) options[key] = value.at(-1);
|
|
392
453
|
}
|
|
393
|
-
function
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const pluginRoot = configService.absolutePluginRoot;
|
|
397
|
-
const srcRoot = configService.absoluteSrcRoot;
|
|
398
|
-
const sourceType = classifyModuleSourceKind({
|
|
399
|
-
isNodeModule,
|
|
400
|
-
inSrc: isPathInside(srcRoot, absoluteId),
|
|
401
|
-
inPlugin: pluginRoot ? isPathInside(pluginRoot, absoluteId) : false
|
|
402
|
-
});
|
|
403
|
-
return {
|
|
404
|
-
source: configService.relativeAbsoluteSrcRoot(absoluteId),
|
|
405
|
-
sourceType
|
|
406
|
-
};
|
|
454
|
+
function resolveConfigFile(options) {
|
|
455
|
+
if (typeof options.config === "string") return options.config;
|
|
456
|
+
if (typeof options.c === "string") return options.c;
|
|
407
457
|
}
|
|
408
|
-
function
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
458
|
+
function convertBase(value) {
|
|
459
|
+
if (value === 0) return "";
|
|
460
|
+
return value;
|
|
461
|
+
}
|
|
462
|
+
function coerceBooleanOption(value) {
|
|
463
|
+
if (value === void 0) return;
|
|
464
|
+
if (typeof value === "boolean") return value;
|
|
465
|
+
if (typeof value === "string") {
|
|
466
|
+
const normalized = value.trim().toLowerCase();
|
|
467
|
+
if (normalized === "") return true;
|
|
468
|
+
if (normalized === "false" || normalized === "0" || normalized === "off" || normalized === "no") return false;
|
|
469
|
+
if (normalized === "true" || normalized === "1" || normalized === "on" || normalized === "yes") return true;
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
if (typeof value === "number") return value !== 0;
|
|
473
|
+
return Boolean(value);
|
|
474
|
+
}
|
|
475
|
+
function isUiEnabled(options) {
|
|
476
|
+
return Boolean(options.ui || options.analyze);
|
|
423
477
|
}
|
|
424
478
|
//#endregion
|
|
425
|
-
//#region src/
|
|
426
|
-
function
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
...classification,
|
|
431
|
-
files: /* @__PURE__ */ new Map()
|
|
479
|
+
//#region src/cli/commands/alipayExecute.ts
|
|
480
|
+
function createSpawnOptions() {
|
|
481
|
+
return {
|
|
482
|
+
shell: process.platform === "win32",
|
|
483
|
+
stdio: "inherit"
|
|
432
484
|
};
|
|
433
|
-
packages.set(classification.id, created);
|
|
434
|
-
return created;
|
|
435
485
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
486
|
+
/**
|
|
487
|
+
* @description 执行本机 minidev 命令。
|
|
488
|
+
*/
|
|
489
|
+
async function runSpawnMinidev(command, argv, runner) {
|
|
490
|
+
await new Promise((resolve, reject) => {
|
|
491
|
+
const child = runner(command, argv, createSpawnOptions());
|
|
492
|
+
child.on("error", (error) => {
|
|
493
|
+
reject(error);
|
|
494
|
+
});
|
|
495
|
+
child.on("exit", (code, signal) => {
|
|
496
|
+
if (code === 0) {
|
|
497
|
+
resolve();
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
reject(/* @__PURE__ */ new Error(signal ? `minidev ${argv[0] ?? ""} exited with signal ${signal}` : `minidev ${argv[0] ?? ""} exited with code ${code ?? "unknown"}`));
|
|
501
|
+
});
|
|
502
|
+
});
|
|
447
503
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
504
|
+
/**
|
|
505
|
+
* @description 使用系统进程执行本机 minidev 命令。
|
|
506
|
+
*/
|
|
507
|
+
async function spawnMinidev(command, argv) {
|
|
508
|
+
return await runSpawnMinidev(command, argv, spawn);
|
|
453
509
|
}
|
|
454
510
|
//#endregion
|
|
455
|
-
//#region src/
|
|
456
|
-
function
|
|
457
|
-
|
|
458
|
-
|
|
511
|
+
//#region src/cli/commands/alipay.ts
|
|
512
|
+
function normalizePassthroughArgs(args) {
|
|
513
|
+
return Array.isArray(args) ? args.filter((arg) => typeof arg === "string") : [];
|
|
514
|
+
}
|
|
515
|
+
function appendOption(argv, name, value) {
|
|
516
|
+
if (typeof value === "string" && value.trim()) argv.push(name, value);
|
|
517
|
+
}
|
|
518
|
+
function hasOption(argv, ...names) {
|
|
519
|
+
return argv.some((arg) => names.includes(arg) || names.some((name) => arg.startsWith(`${name}=`)));
|
|
520
|
+
}
|
|
521
|
+
function normalizeAlipayAction(action) {
|
|
522
|
+
if (action === "open") return "ide";
|
|
523
|
+
if (action === "ide" || action === "login" || action === "preview" || action === "upload") return action;
|
|
524
|
+
throw new Error(`未知 alipay 子命令: ${action ?? "(empty)"}`);
|
|
525
|
+
}
|
|
526
|
+
function resolveProjectPath(root, resolvedProjectPath, options) {
|
|
527
|
+
if (typeof options.project === "string" && options.project.trim()) return options.project;
|
|
528
|
+
return root ?? resolvedProjectPath;
|
|
529
|
+
}
|
|
530
|
+
function resolveMinidevCommand(options) {
|
|
531
|
+
return typeof options.minidev === "string" && options.minidev.trim() ? options.minidev : "minidev";
|
|
532
|
+
}
|
|
533
|
+
function createMinidevArgv(action, root, resolvedProjectPath, options) {
|
|
534
|
+
const passthroughArgs = normalizePassthroughArgs(options["--"]);
|
|
535
|
+
const argv = [action];
|
|
536
|
+
const projectPath = resolveProjectPath(root, resolvedProjectPath, options);
|
|
537
|
+
if ((action === "ide" || action === "preview" || action === "upload") && projectPath && !hasOption(passthroughArgs, "--project", "-p")) argv.push("--project", path.normalize(projectPath));
|
|
538
|
+
if ((action === "preview" || action === "upload") && !hasOption(passthroughArgs, "--app-id", "-a")) appendOption(argv, "--app-id", options.appId);
|
|
539
|
+
if ((action === "login" || action === "preview" || action === "upload") && !hasOption(passthroughArgs, "--client-type", "-c")) appendOption(argv, "--client-type", options.clientType);
|
|
540
|
+
if (action === "upload" && !hasOption(passthroughArgs, "--version", "-v")) appendOption(argv, "--version", options.version);
|
|
541
|
+
argv.push(...passthroughArgs);
|
|
542
|
+
return argv;
|
|
459
543
|
}
|
|
460
|
-
function
|
|
461
|
-
|
|
462
|
-
|
|
544
|
+
async function runAlipayCommand(action, root, options) {
|
|
545
|
+
const normalizedAction = normalizeAlipayAction(action);
|
|
546
|
+
filterDuplicateOptions(options);
|
|
547
|
+
const argv = createMinidevArgv(normalizedAction, root, (await resolveIdeCommandContext({
|
|
548
|
+
configFile: resolveConfigFile(options),
|
|
549
|
+
mode: options.mode ?? (normalizedAction === "upload" ? "production" : "development"),
|
|
550
|
+
platform: "alipay",
|
|
551
|
+
projectPath: root ?? options.project,
|
|
552
|
+
cliPlatform: "alipay"
|
|
553
|
+
})).projectPath, options);
|
|
554
|
+
const command = resolveMinidevCommand(options);
|
|
555
|
+
logger_default.info(`执行支付宝小程序 CLI:${command} ${argv.join(" ")}`);
|
|
556
|
+
await spawnMinidev(command, argv);
|
|
557
|
+
}
|
|
558
|
+
function registerAlipayCommand(cli) {
|
|
559
|
+
cli.command("alipay [action] [root]", "run Alipay minidev ide, login, preview, or upload").option("-a, --app-id <appId>", "[string] Alipay mini program appId").option("-c, --client-type <clientType>", "[string] minidev client type").option("--minidev <command>", "[string] minidev executable path or command name").option("--project <path>", "[string] Alipay mini program project path").option("--version <version>", "[string] upload version").allowUnknownOptions().action(async (action, root, options) => {
|
|
560
|
+
await runAlipayCommand(action, root, options);
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
//#endregion
|
|
564
|
+
//#region src/analyze/hmr.ts
|
|
565
|
+
function createMetricSummary(values) {
|
|
566
|
+
if (!values.length) return { count: 0 };
|
|
567
|
+
const total = values.reduce((sum, value) => sum + value, 0);
|
|
463
568
|
return {
|
|
464
|
-
|
|
465
|
-
|
|
569
|
+
count: values.length,
|
|
570
|
+
averageMs: total / values.length,
|
|
571
|
+
maxMs: Math.max(...values)
|
|
466
572
|
};
|
|
467
573
|
}
|
|
468
|
-
function
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
type: "chunk",
|
|
474
|
-
from: origin,
|
|
475
|
-
size: typeof chunk.code === "string" ? Buffer.byteLength(chunk.code, "utf8") : void 0,
|
|
476
|
-
...getCompressedSizes$1(chunk.code),
|
|
477
|
-
isEntry: chunk.isEntry,
|
|
478
|
-
modules: []
|
|
479
|
-
};
|
|
480
|
-
const moduleEntries = Object.entries(chunk.modules ?? {});
|
|
481
|
-
for (const [rawModuleId, info] of moduleEntries) {
|
|
482
|
-
const absoluteId = normalizeModuleId(rawModuleId);
|
|
483
|
-
if (!absoluteId) continue;
|
|
484
|
-
const { source, sourceType } = resolveModuleSourceType(absoluteId, ctx);
|
|
485
|
-
const moduleEntry = {
|
|
486
|
-
id: absoluteId,
|
|
487
|
-
source,
|
|
488
|
-
sourceType,
|
|
489
|
-
bytes: info?.renderedLength
|
|
490
|
-
};
|
|
491
|
-
if (typeof info?.code === "string") moduleEntry.originalBytes = Buffer.byteLength(info.code, "utf8");
|
|
492
|
-
chunkEntry.modules.push(moduleEntry);
|
|
493
|
-
registerModuleInPackage(modules, absoluteId, source, sourceType, classification.id, chunk.fileName);
|
|
494
|
-
}
|
|
495
|
-
if (chunkEntry.modules) chunkEntry.modules.sort((a, b) => a.source.localeCompare(b.source));
|
|
496
|
-
packageEntry.files.set(chunk.fileName, chunkEntry);
|
|
574
|
+
function sortCountEntries(map) {
|
|
575
|
+
return [...map.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0])).map(([name, count]) => ({
|
|
576
|
+
name,
|
|
577
|
+
count
|
|
578
|
+
}));
|
|
497
579
|
}
|
|
498
|
-
function
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const entry = {
|
|
503
|
-
file: asset.fileName,
|
|
504
|
-
type: "asset",
|
|
505
|
-
from: origin,
|
|
506
|
-
size: assetBuffer?.byteLength,
|
|
507
|
-
...getCompressedSizes$1(assetBuffer)
|
|
508
|
-
};
|
|
509
|
-
const assetSource = resolveAssetSource(asset.fileName, ctx);
|
|
510
|
-
if (assetSource) {
|
|
511
|
-
entry.source = assetSource.source;
|
|
512
|
-
registerModuleInPackage(modules, assetSource.absolute, assetSource.source, assetSource.sourceType, classification.id, asset.fileName);
|
|
580
|
+
function collectCounts(target, values) {
|
|
581
|
+
for (const value of values ?? []) {
|
|
582
|
+
if (!value) continue;
|
|
583
|
+
target.set(value, (target.get(value) ?? 0) + 1);
|
|
513
584
|
}
|
|
514
|
-
packageEntry.files.set(asset.fileName, entry);
|
|
515
|
-
}
|
|
516
|
-
function processOutput(output, origin, ctx, classifierContext, packages, modules) {
|
|
517
|
-
if (!output) return;
|
|
518
|
-
for (const item of output.output ?? []) if (item.type === "chunk") processChunk(item, origin, ctx, classifierContext, packages, modules);
|
|
519
|
-
else if (item.type === "asset") processAsset(item, origin, ctx, classifierContext, packages, modules);
|
|
520
585
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
function toArray(value) {
|
|
524
|
-
return Array.from(value);
|
|
586
|
+
function isFiniteNumber$1(value) {
|
|
587
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
525
588
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
const
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
589
|
+
/**
|
|
590
|
+
* @description 聚合 HMR JSONL profile,为命令行与后续仪表盘复用。
|
|
591
|
+
*/
|
|
592
|
+
async function analyzeHmrProfile(options) {
|
|
593
|
+
const lines = (await fs.readFile(options.profilePath, "utf8")).split(/\r?\n/);
|
|
594
|
+
const samples = [];
|
|
595
|
+
let skippedLineCount = 0;
|
|
596
|
+
for (const line of lines) {
|
|
597
|
+
const trimmed = line.trim();
|
|
598
|
+
if (!trimmed) continue;
|
|
599
|
+
try {
|
|
600
|
+
const parsed = JSON.parse(trimmed);
|
|
601
|
+
if (!isFiniteNumber$1(parsed.totalMs)) {
|
|
602
|
+
skippedLineCount += 1;
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
samples.push(parsed);
|
|
606
|
+
} catch {
|
|
607
|
+
skippedLineCount += 1;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
const eventCounts = /* @__PURE__ */ new Map();
|
|
611
|
+
const dirtyReasonCounts = /* @__PURE__ */ new Map();
|
|
612
|
+
const pendingReasonCounts = /* @__PURE__ */ new Map();
|
|
613
|
+
const totalValues = [];
|
|
614
|
+
const buildCoreValues = [];
|
|
615
|
+
const transformValues = [];
|
|
616
|
+
const writeValues = [];
|
|
617
|
+
const watchToDirtyValues = [];
|
|
618
|
+
const emitValues = [];
|
|
619
|
+
const sharedChunkValues = [];
|
|
620
|
+
for (const sample of samples) {
|
|
621
|
+
totalValues.push(sample.totalMs);
|
|
622
|
+
if (sample.event) eventCounts.set(sample.event, (eventCounts.get(sample.event) ?? 0) + 1);
|
|
623
|
+
if (isFiniteNumber$1(sample.buildCoreMs)) buildCoreValues.push(sample.buildCoreMs);
|
|
624
|
+
if (isFiniteNumber$1(sample.transformMs)) transformValues.push(sample.transformMs);
|
|
625
|
+
if (isFiniteNumber$1(sample.writeMs)) writeValues.push(sample.writeMs);
|
|
626
|
+
if (isFiniteNumber$1(sample.watchToDirtyMs)) watchToDirtyValues.push(sample.watchToDirtyMs);
|
|
627
|
+
if (isFiniteNumber$1(sample.emitMs)) emitValues.push(sample.emitMs);
|
|
628
|
+
if (isFiniteNumber$1(sample.sharedChunkResolveMs)) sharedChunkValues.push(sample.sharedChunkResolveMs);
|
|
629
|
+
collectCounts(dirtyReasonCounts, sample.dirtyReasonSummary);
|
|
630
|
+
collectCounts(pendingReasonCounts, sample.pendingReasonSummary);
|
|
631
|
+
}
|
|
632
|
+
const orderedByTime = [...samples].sort((left, right) => {
|
|
633
|
+
const leftTime = typeof left.timestamp === "string" ? Date.parse(left.timestamp) : NaN;
|
|
634
|
+
const rightTime = typeof right.timestamp === "string" ? Date.parse(right.timestamp) : NaN;
|
|
635
|
+
if (Number.isFinite(leftTime) && Number.isFinite(rightTime)) return leftTime - rightTime;
|
|
636
|
+
return 0;
|
|
549
637
|
});
|
|
550
|
-
|
|
638
|
+
const slowestSamples = [...samples].sort((left, right) => (right.totalMs ?? 0) - (left.totalMs ?? 0)).slice(0, options.topSlowest ?? 5);
|
|
639
|
+
return {
|
|
640
|
+
runtime: "mini",
|
|
641
|
+
kind: "hmr-profile",
|
|
642
|
+
generatedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
643
|
+
profilePath: options.profilePath,
|
|
644
|
+
sampleCount: samples.length,
|
|
645
|
+
skippedLineCount,
|
|
646
|
+
firstTimestamp: orderedByTime[0]?.timestamp,
|
|
647
|
+
lastTimestamp: orderedByTime.at(-1)?.timestamp,
|
|
648
|
+
metrics: {
|
|
649
|
+
totalMs: createMetricSummary(totalValues),
|
|
650
|
+
buildCoreMs: createMetricSummary(buildCoreValues),
|
|
651
|
+
transformMs: createMetricSummary(transformValues),
|
|
652
|
+
writeMs: createMetricSummary(writeValues),
|
|
653
|
+
watchToDirtyMs: createMetricSummary(watchToDirtyValues),
|
|
654
|
+
emitMs: createMetricSummary(emitValues),
|
|
655
|
+
sharedChunkResolveMs: createMetricSummary(sharedChunkValues)
|
|
656
|
+
},
|
|
657
|
+
events: sortCountEntries(eventCounts),
|
|
658
|
+
dirtyReasons: sortCountEntries(dirtyReasonCounts),
|
|
659
|
+
pendingReasons: sortCountEntries(pendingReasonCounts),
|
|
660
|
+
slowestSamples
|
|
661
|
+
};
|
|
551
662
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
663
|
+
//#endregion
|
|
664
|
+
//#region src/analyze/components/suggestions.ts
|
|
665
|
+
function createComponentSuggestion(usage) {
|
|
666
|
+
if (usage.componentPackage !== "__main__" || usage.crossPackageUsageCount === 0) return;
|
|
667
|
+
if (usage.crossPackageUsageCount === usage.placeholderCoveredCrossPackageUsageCount) return;
|
|
668
|
+
const pagePackages = Array.from(new Set(Array.from(usage.pages.values()).map((page) => page.packageId))).sort((left, right) => {
|
|
669
|
+
if (left === "__main__") return -1;
|
|
670
|
+
if (right === "__main__") return 1;
|
|
671
|
+
return left.localeCompare(right);
|
|
672
|
+
});
|
|
673
|
+
const subPackageIds = pagePackages.filter((packageId) => packageId !== "__main__");
|
|
674
|
+
const usedByMain = pagePackages.includes("__main__");
|
|
675
|
+
if (subPackageIds.length === 1 && !usedByMain) {
|
|
676
|
+
const targetPackage = subPackageIds[0];
|
|
565
677
|
return {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
678
|
+
kind: "move-to-subpackage",
|
|
679
|
+
component: usage.component,
|
|
680
|
+
componentPackage: usage.componentPackage,
|
|
681
|
+
targetPackage,
|
|
682
|
+
pagePackages,
|
|
683
|
+
message: `主包组件 ${usage.component} 仅被分包 ${targetPackage} 使用,建议评估移动到该分包。`
|
|
570
684
|
};
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
|
|
685
|
+
}
|
|
686
|
+
if (subPackageIds.length > 1) return {
|
|
687
|
+
kind: "shared-subpackage-or-placeholder",
|
|
688
|
+
component: usage.component,
|
|
689
|
+
componentPackage: usage.componentPackage,
|
|
690
|
+
pagePackages,
|
|
691
|
+
message: `主包组件 ${usage.component} 被多个分包使用,建议评估分包归属、共享策略或 componentPlaceholder。`
|
|
692
|
+
};
|
|
693
|
+
if (usedByMain && subPackageIds.length > 0) return {
|
|
694
|
+
kind: "split-or-async",
|
|
695
|
+
component: usage.component,
|
|
696
|
+
componentPackage: usage.componentPackage,
|
|
697
|
+
pagePackages,
|
|
698
|
+
message: `主包组件 ${usage.component} 同时被主包和分包使用,建议评估组件拆分、归属或异步化策略。`
|
|
699
|
+
};
|
|
574
700
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
const virtualFileBases = /* @__PURE__ */ new Map();
|
|
580
|
-
for (const [virtualPackageId, files] of virtualEntries) {
|
|
581
|
-
const combination = virtualPackageId.slice(8);
|
|
582
|
-
if (!combination) continue;
|
|
583
|
-
const segments = combination.split(/[_+]/).map((segment) => segment.trim()).filter(Boolean);
|
|
584
|
-
if (!segments.length) continue;
|
|
585
|
-
let matchingBases = virtualFileBases.get(virtualPackageId);
|
|
586
|
-
if (!matchingBases) {
|
|
587
|
-
matchingBases = Array.from(files).map((file) => posix.basename(file));
|
|
588
|
-
virtualFileBases.set(virtualPackageId, matchingBases);
|
|
589
|
-
}
|
|
590
|
-
for (const root of segments) {
|
|
591
|
-
if (!context.subPackageRoots.has(root)) continue;
|
|
592
|
-
const targetPackage = packages.get(root);
|
|
593
|
-
if (!targetPackage) continue;
|
|
594
|
-
const moduleFiles = moduleEntry.packages.get(root) ?? /* @__PURE__ */ new Set();
|
|
595
|
-
const targetFiles = Array.from(targetPackage.files.values()).filter((fileEntry) => {
|
|
596
|
-
if (!matchingBases?.length) return true;
|
|
597
|
-
const base = posix.basename(fileEntry.file);
|
|
598
|
-
return matchingBases.includes(base);
|
|
599
|
-
}).map((fileEntry) => fileEntry.file);
|
|
600
|
-
if (targetFiles.length === 0) {
|
|
601
|
-
const fallback = targetPackage.files.values().next().value;
|
|
602
|
-
if (fallback) moduleFiles.add(fallback.file);
|
|
603
|
-
} else for (const fileName of targetFiles) moduleFiles.add(fileName);
|
|
604
|
-
if (moduleFiles.size > 0) moduleEntry.packages.set(root, moduleFiles);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
}
|
|
701
|
+
//#endregion
|
|
702
|
+
//#region src/analyze/components/index.ts
|
|
703
|
+
function normalizeRoute(value) {
|
|
704
|
+
return posix.normalize(value.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\.json$/, ""));
|
|
608
705
|
}
|
|
609
|
-
function
|
|
610
|
-
|
|
611
|
-
return {
|
|
612
|
-
root: meta.subPackage.root ?? "",
|
|
613
|
-
independent: Boolean(meta.subPackage.independent),
|
|
614
|
-
name: meta.subPackage.name
|
|
615
|
-
};
|
|
616
|
-
}).filter((descriptor) => descriptor.root);
|
|
617
|
-
descriptors.sort((a, b) => a.root.localeCompare(b.root));
|
|
618
|
-
return descriptors;
|
|
706
|
+
function isRecord(value) {
|
|
707
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
619
708
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
async function analyzeSubpackages(ctx) {
|
|
623
|
-
const { configService, scanService, buildService } = ctx;
|
|
624
|
-
if (!configService || !scanService || !buildService) throw new Error("analyzeSubpackages 需要先初始化 configService、scanService 和 buildService。");
|
|
625
|
-
await scanService.loadAppEntry();
|
|
626
|
-
const subPackageMetas = scanService.loadSubPackages();
|
|
627
|
-
const subPackageRoots = /* @__PURE__ */ new Set();
|
|
628
|
-
const independentRoots = /* @__PURE__ */ new Set();
|
|
629
|
-
for (const meta of subPackageMetas) {
|
|
630
|
-
const root = meta.subPackage.root;
|
|
631
|
-
if (root) {
|
|
632
|
-
subPackageRoots.add(root);
|
|
633
|
-
if (meta.subPackage.independent) independentRoots.add(root);
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
const classifierContext = {
|
|
637
|
-
subPackageRoots,
|
|
638
|
-
independentRoots
|
|
639
|
-
};
|
|
640
|
-
const mainResult = await build(configService.merge(void 0, createSharedBuildConfig(configService, scanService), { build: {
|
|
641
|
-
write: false,
|
|
642
|
-
watch: null
|
|
643
|
-
} }));
|
|
644
|
-
const mainOutputs = Array.isArray(mainResult) ? mainResult : [mainResult];
|
|
645
|
-
const packages = /* @__PURE__ */ new Map();
|
|
646
|
-
const modules = /* @__PURE__ */ new Map();
|
|
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
|
-
}
|
|
657
|
-
expandVirtualModulePlacements(modules, packages, classifierContext);
|
|
658
|
-
const subPackages = summarizeSubPackages(subPackageMetas);
|
|
659
|
-
return {
|
|
660
|
-
metadata: createAnalyzeMetadata(ctx.configService),
|
|
661
|
-
packages: summarizePackages(packages),
|
|
662
|
-
modules: summarizeModules(modules),
|
|
663
|
-
subPackages,
|
|
664
|
-
components: analyzeComponentUsage({
|
|
665
|
-
jsonConfigs: componentJsonConfigs,
|
|
666
|
-
subPackages
|
|
667
|
-
})
|
|
668
|
-
};
|
|
709
|
+
function toStringArray(value) {
|
|
710
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim() !== "") : [];
|
|
669
711
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
function createSnapshotFileName(date = /* @__PURE__ */ new Date()) {
|
|
674
|
-
return `${date.toISOString().replace(/[:.]/g, "-")}${jsonExtension}`;
|
|
712
|
+
function readUsingComponents(config) {
|
|
713
|
+
if (!isRecord(config) || !isRecord(config.usingComponents)) return [];
|
|
714
|
+
return Object.entries(config.usingComponents).filter((entry) => typeof entry[1] === "string" && entry[1].trim() !== "");
|
|
675
715
|
}
|
|
676
|
-
function
|
|
677
|
-
|
|
716
|
+
function readComponentPlaceholder(config) {
|
|
717
|
+
if (!isRecord(config) || !isRecord(config.componentPlaceholder)) return /* @__PURE__ */ new Set();
|
|
718
|
+
return new Set(Object.keys(config.componentPlaceholder));
|
|
678
719
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
720
|
+
function resolveComponentRoute(owner, request) {
|
|
721
|
+
const normalizedRequest = request.replace(/\\/g, "/").trim();
|
|
722
|
+
if (!normalizedRequest || normalizedRequest.startsWith("plugin://")) return;
|
|
723
|
+
if (normalizedRequest.startsWith(".")) return normalizeRoute(posix.join(posix.dirname(owner), normalizedRequest));
|
|
724
|
+
if (normalizedRequest.startsWith("/")) return normalizeRoute(normalizedRequest);
|
|
725
|
+
return normalizeRoute(normalizedRequest);
|
|
726
|
+
}
|
|
727
|
+
function resolvePackageId(route, subPackages) {
|
|
728
|
+
return subPackages.map((item) => item.root).filter(Boolean).sort((left, right) => right.length - left.length).find((root) => route === root || route.startsWith(`${root}/`)) ?? "__main__";
|
|
729
|
+
}
|
|
730
|
+
function collectAppPages(configs) {
|
|
731
|
+
const appJson = configs.get("app");
|
|
732
|
+
if (!isRecord(appJson)) return /* @__PURE__ */ new Set();
|
|
733
|
+
const pages = /* @__PURE__ */ new Set();
|
|
734
|
+
for (const page of toStringArray(appJson.pages)) pages.add(normalizeRoute(page));
|
|
735
|
+
const subPackages = Array.isArray(appJson.subPackages) ? appJson.subPackages : Array.isArray(appJson.subpackages) ? appJson.subpackages : [];
|
|
736
|
+
for (const item of subPackages) {
|
|
737
|
+
if (!isRecord(item) || typeof item.root !== "string") continue;
|
|
738
|
+
for (const page of toStringArray(item.pages)) pages.add(normalizeRoute(posix.join(item.root, page)));
|
|
684
739
|
}
|
|
740
|
+
return pages;
|
|
685
741
|
}
|
|
686
|
-
|
|
687
|
-
const
|
|
688
|
-
|
|
742
|
+
function registerUsage(usageMap, edge, page, pagePackage, componentPackage) {
|
|
743
|
+
const usage = usageMap.get(edge.component) ?? {
|
|
744
|
+
component: edge.component,
|
|
745
|
+
componentPackage,
|
|
746
|
+
totalUsageCount: 0,
|
|
747
|
+
pages: /* @__PURE__ */ new Map(),
|
|
748
|
+
crossPackageUsageCount: 0,
|
|
749
|
+
placeholderCoveredCrossPackageUsageCount: 0
|
|
750
|
+
};
|
|
751
|
+
usage.totalUsageCount += 1;
|
|
752
|
+
const pageUsage = usage.pages.get(page) ?? {
|
|
753
|
+
page,
|
|
754
|
+
packageId: pagePackage,
|
|
755
|
+
usageCount: 0
|
|
756
|
+
};
|
|
757
|
+
pageUsage.usageCount += 1;
|
|
758
|
+
usage.pages.set(page, pageUsage);
|
|
759
|
+
if (pagePackage !== componentPackage) {
|
|
760
|
+
usage.crossPackageUsageCount += 1;
|
|
761
|
+
if (edge.placeholderCovered) usage.placeholderCoveredCrossPackageUsageCount += 1;
|
|
762
|
+
}
|
|
763
|
+
usageMap.set(edge.component, usage);
|
|
764
|
+
}
|
|
765
|
+
function collectAnalyzeComponentJsonConfigs(output) {
|
|
766
|
+
if (!output) return [];
|
|
767
|
+
const configs = [];
|
|
768
|
+
for (const item of output.output ?? []) {
|
|
769
|
+
if (item.type !== "asset" || !item.fileName.endsWith(".json")) continue;
|
|
770
|
+
const asset = item;
|
|
771
|
+
if (typeof asset.source !== "string") continue;
|
|
689
772
|
try {
|
|
690
|
-
|
|
773
|
+
configs.push({
|
|
774
|
+
file: normalizeRoute(asset.fileName),
|
|
775
|
+
config: JSON.parse(asset.source)
|
|
776
|
+
});
|
|
691
777
|
} 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;
|
|
778
|
+
}
|
|
779
|
+
return configs;
|
|
703
780
|
}
|
|
704
|
-
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
781
|
+
function analyzeComponentUsage(options) {
|
|
782
|
+
const configs = /* @__PURE__ */ new Map();
|
|
783
|
+
for (const item of options.jsonConfigs) configs.set(normalizeRoute(item.file), item.config);
|
|
784
|
+
const pages = collectAppPages(configs);
|
|
785
|
+
const graph = /* @__PURE__ */ new Map();
|
|
786
|
+
const packageMap = /* @__PURE__ */ new Map();
|
|
787
|
+
for (const route of configs.keys()) packageMap.set(route, resolvePackageId(route, options.subPackages));
|
|
788
|
+
for (const [owner, config] of configs) {
|
|
789
|
+
const placeholders = readComponentPlaceholder(config);
|
|
790
|
+
const edges = readUsingComponents(config).map(([name, request]) => {
|
|
791
|
+
const component = resolveComponentRoute(owner, request);
|
|
792
|
+
return component && configs.has(component) ? {
|
|
793
|
+
owner,
|
|
794
|
+
component,
|
|
795
|
+
placeholderCovered: placeholders.has(name)
|
|
796
|
+
} : void 0;
|
|
797
|
+
}).filter((edge) => Boolean(edge));
|
|
798
|
+
if (edges.length > 0) graph.set(owner, edges);
|
|
799
|
+
}
|
|
800
|
+
const usageMap = /* @__PURE__ */ new Map();
|
|
801
|
+
const visit = (owner, page, stack) => {
|
|
802
|
+
for (const edge of graph.get(owner) ?? []) {
|
|
803
|
+
const componentPackage = packageMap.get(edge.component) ?? "__main__";
|
|
804
|
+
registerUsage(usageMap, edge, page, packageMap.get(page) ?? "__main__", componentPackage);
|
|
805
|
+
if (stack.has(edge.component)) continue;
|
|
806
|
+
const nextStack = new Set(stack);
|
|
807
|
+
nextStack.add(edge.component);
|
|
808
|
+
visit(edge.component, page, nextStack);
|
|
715
809
|
}
|
|
716
810
|
};
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
811
|
+
for (const page of pages) visit(page, page, new Set([page]));
|
|
812
|
+
return Array.from(usageMap.values()).map((usage) => {
|
|
813
|
+
const pages = Array.from(usage.pages.values()).sort((left, right) => left.page.localeCompare(right.page));
|
|
814
|
+
const suggestions = [createComponentSuggestion(usage)].filter((item) => Boolean(item));
|
|
815
|
+
return {
|
|
816
|
+
component: usage.component,
|
|
817
|
+
componentPackage: usage.componentPackage,
|
|
818
|
+
totalUsageCount: usage.totalUsageCount,
|
|
819
|
+
pageUsageCount: pages.length,
|
|
820
|
+
pages,
|
|
821
|
+
suggestions
|
|
822
|
+
};
|
|
823
|
+
}).sort((left, right) => {
|
|
824
|
+
const usageDelta = right.totalUsageCount - left.totalUsageCount;
|
|
825
|
+
return usageDelta !== 0 ? usageDelta : left.component.localeCompare(right.component);
|
|
826
|
+
});
|
|
720
827
|
}
|
|
721
828
|
//#endregion
|
|
722
|
-
//#region src/analyze/subpackages/
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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))}`;
|
|
829
|
+
//#region src/analyze/subpackages/metadata.ts
|
|
830
|
+
const defaultTotalBudgetBytes = 20 * 1024 * 1024;
|
|
831
|
+
const defaultPackageBudgetBytes = 2 * 1024 * 1024;
|
|
832
|
+
const defaultWarningRatio = .85;
|
|
833
|
+
const defaultHistoryDir = ".weapp-vite/analyze-history";
|
|
834
|
+
const defaultHistoryLimit = 20;
|
|
835
|
+
function resolveBudgetValue(value, fallback) {
|
|
836
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
742
837
|
}
|
|
743
|
-
function
|
|
744
|
-
return
|
|
838
|
+
function resolveHistoryLimit(value) {
|
|
839
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : defaultHistoryLimit;
|
|
745
840
|
}
|
|
746
|
-
function
|
|
747
|
-
|
|
841
|
+
function resolveAnalyzeBudgets(configService) {
|
|
842
|
+
const budgets = configService.weappViteConfig.analyze?.budgets;
|
|
843
|
+
const legacyPackageBudget = configService.weappViteConfig.packageSizeWarningBytes;
|
|
844
|
+
const packageFallback = resolveBudgetValue(legacyPackageBudget, defaultPackageBudgetBytes);
|
|
845
|
+
return {
|
|
846
|
+
totalBytes: resolveBudgetValue(budgets?.totalBytes, defaultTotalBudgetBytes),
|
|
847
|
+
mainBytes: resolveBudgetValue(budgets?.mainBytes, packageFallback),
|
|
848
|
+
subPackageBytes: resolveBudgetValue(budgets?.subPackageBytes, packageFallback),
|
|
849
|
+
independentBytes: resolveBudgetValue(budgets?.independentBytes, packageFallback),
|
|
850
|
+
warningRatio: resolveBudgetValue(budgets?.warningRatio, defaultWarningRatio),
|
|
851
|
+
source: budgets ? "config" : "default"
|
|
852
|
+
};
|
|
748
853
|
}
|
|
749
|
-
function
|
|
750
|
-
|
|
751
|
-
if (
|
|
752
|
-
|
|
753
|
-
|
|
854
|
+
function resolveAnalyzeHistoryMetadata(configService) {
|
|
855
|
+
const history = configService.weappViteConfig.analyze?.history;
|
|
856
|
+
if (history === false) return {
|
|
857
|
+
enabled: false,
|
|
858
|
+
dir: path.resolve(configService.cwd, defaultHistoryDir),
|
|
859
|
+
limit: defaultHistoryLimit
|
|
860
|
+
};
|
|
861
|
+
const historyConfig = typeof history === "object" && history ? history : {};
|
|
862
|
+
const rawDir = typeof historyConfig.dir === "string" && historyConfig.dir.trim() ? historyConfig.dir.trim() : defaultHistoryDir;
|
|
863
|
+
return {
|
|
864
|
+
enabled: historyConfig.enabled !== false,
|
|
865
|
+
dir: path.isAbsolute(rawDir) ? rawDir : path.resolve(configService.cwd, rawDir),
|
|
866
|
+
limit: resolveHistoryLimit(historyConfig.limit)
|
|
867
|
+
};
|
|
754
868
|
}
|
|
755
|
-
function
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
869
|
+
function createAnalyzeMetadata(configService, now = /* @__PURE__ */ new Date()) {
|
|
870
|
+
const history = resolveAnalyzeHistoryMetadata(configService);
|
|
871
|
+
return {
|
|
872
|
+
generatedAt: now.toISOString(),
|
|
873
|
+
budgets: resolveAnalyzeBudgets(configService),
|
|
874
|
+
history: {
|
|
875
|
+
...history,
|
|
876
|
+
dir: configService.relativeCwd(history.dir)
|
|
877
|
+
}
|
|
878
|
+
};
|
|
760
879
|
}
|
|
761
|
-
|
|
762
|
-
|
|
880
|
+
//#endregion
|
|
881
|
+
//#region src/analyze/subpackages/classifier.ts
|
|
882
|
+
const VIRTUAL_MODULE_INDICATOR = "\0";
|
|
883
|
+
const VIRTUAL_PREFIX = `${SHARED_CHUNK_VIRTUAL_PREFIX}/`;
|
|
884
|
+
function classifyModuleSourceKind(options) {
|
|
885
|
+
if (options.isNodeModule) return "node_modules";
|
|
886
|
+
if (options.inSrc) return "src";
|
|
887
|
+
if (options.inPlugin) return "plugin";
|
|
888
|
+
return "workspace";
|
|
763
889
|
}
|
|
764
|
-
function
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
return
|
|
890
|
+
function resolvePluginAssetAbsolute(normalizedFileName, pluginRoot) {
|
|
891
|
+
if (!pluginRoot) return;
|
|
892
|
+
const pluginBase = posix.basename(pluginRoot);
|
|
893
|
+
if (normalizedFileName !== pluginBase && !normalizedFileName.startsWith(`${pluginBase}/`)) return;
|
|
894
|
+
const relative = normalizedFileName === pluginBase ? "" : normalizedFileName.slice(pluginBase.length + 1);
|
|
895
|
+
const absolute = posix.resolve(pluginRoot, relative);
|
|
896
|
+
return isPathInside(pluginRoot, absolute) ? absolute : void 0;
|
|
768
897
|
}
|
|
769
|
-
function
|
|
770
|
-
|
|
898
|
+
function resolveSubPackageRoot$1(fileName, context) {
|
|
899
|
+
const normalized = posix.normalize(fileName);
|
|
900
|
+
return Array.from(context.subPackageRoots).filter(Boolean).sort((left, right) => right.length - left.length).find((root) => normalized === root || normalized.startsWith(`${root}/`));
|
|
771
901
|
}
|
|
772
|
-
function
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
902
|
+
function classifyPackage(fileName, origin, context) {
|
|
903
|
+
if (fileName.startsWith(VIRTUAL_PREFIX)) {
|
|
904
|
+
const combination = fileName.slice(VIRTUAL_PREFIX.length).split("/")[0] || "shared";
|
|
905
|
+
return {
|
|
906
|
+
id: `virtual:${combination}`,
|
|
907
|
+
label: `共享虚拟包 ${combination}`,
|
|
908
|
+
type: "virtual"
|
|
909
|
+
};
|
|
777
910
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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
|
-
});
|
|
911
|
+
const rootCandidate = resolveSubPackageRoot$1(fileName, context);
|
|
912
|
+
if (rootCandidate) {
|
|
913
|
+
const isIndependent = context.independentRoots.has(rootCandidate);
|
|
914
|
+
return {
|
|
915
|
+
id: rootCandidate,
|
|
916
|
+
label: `${isIndependent ? "独立分包" : "分包"} ${rootCandidate}`,
|
|
917
|
+
type: isIndependent || origin === "independent" ? "independent" : "subPackage"
|
|
918
|
+
};
|
|
809
919
|
}
|
|
810
|
-
return
|
|
920
|
+
return {
|
|
921
|
+
id: "__main__",
|
|
922
|
+
label: "主包",
|
|
923
|
+
type: "main"
|
|
924
|
+
};
|
|
811
925
|
}
|
|
812
|
-
function
|
|
813
|
-
if (
|
|
814
|
-
if (
|
|
815
|
-
|
|
816
|
-
if (sourceType === "plugin") return "插件生成内容跨包重复,检查插件产物输出策略。";
|
|
817
|
-
return "检查该模块是否需要在多个包内重复存在。";
|
|
926
|
+
function normalizeModuleId(id) {
|
|
927
|
+
if (!id || id.includes(VIRTUAL_MODULE_INDICATOR)) return;
|
|
928
|
+
if (!posix.isAbsolute(id)) return;
|
|
929
|
+
return posix.normalize(id);
|
|
818
930
|
}
|
|
819
|
-
function
|
|
820
|
-
|
|
821
|
-
|
|
931
|
+
function resolveModuleSourceType(absoluteId, ctx) {
|
|
932
|
+
const { configService } = ctx;
|
|
933
|
+
const isNodeModule = absoluteId.includes("/node_modules/") || absoluteId.includes("\\node_modules\\");
|
|
934
|
+
const pluginRoot = configService.absolutePluginRoot;
|
|
935
|
+
const srcRoot = configService.absoluteSrcRoot;
|
|
936
|
+
const sourceType = classifyModuleSourceKind({
|
|
937
|
+
isNodeModule,
|
|
938
|
+
inSrc: isPathInside(srcRoot, absoluteId),
|
|
939
|
+
inPlugin: pluginRoot ? isPathInside(pluginRoot, absoluteId) : false
|
|
940
|
+
});
|
|
941
|
+
return {
|
|
942
|
+
source: configService.relativeAbsoluteSrcRoot(absoluteId),
|
|
943
|
+
sourceType
|
|
944
|
+
};
|
|
822
945
|
}
|
|
823
|
-
function
|
|
824
|
-
const
|
|
825
|
-
const
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
946
|
+
function resolveAssetSource(fileName, ctx) {
|
|
947
|
+
const { configService } = ctx;
|
|
948
|
+
const normalized = posix.normalize(fileName);
|
|
949
|
+
const srcCandidate = posix.resolve(configService.absoluteSrcRoot, normalized);
|
|
950
|
+
if (isPathInside(configService.absoluteSrcRoot, srcCandidate)) return {
|
|
951
|
+
absolute: srcCandidate,
|
|
952
|
+
source: configService.relativeAbsoluteSrcRoot(srcCandidate),
|
|
953
|
+
sourceType: "src"
|
|
954
|
+
};
|
|
955
|
+
const pluginAbsolute = resolvePluginAssetAbsolute(normalized, configService.absolutePluginRoot);
|
|
956
|
+
if (pluginAbsolute) return {
|
|
957
|
+
absolute: pluginAbsolute,
|
|
958
|
+
source: configService.relativeAbsoluteSrcRoot(pluginAbsolute),
|
|
959
|
+
sourceType: "plugin"
|
|
960
|
+
};
|
|
833
961
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
const
|
|
838
|
-
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
const status = ratio >= 1 ? "exceeded" : ratio >= warningRatio ? "warning" : "ok";
|
|
843
|
-
return {
|
|
844
|
-
...options,
|
|
845
|
-
ratio,
|
|
846
|
-
status
|
|
847
|
-
};
|
|
962
|
+
//#endregion
|
|
963
|
+
//#region src/analyze/subpackages/registry.ts
|
|
964
|
+
function ensurePackage(packages, classification) {
|
|
965
|
+
const existing = packages.get(classification.id);
|
|
966
|
+
if (existing) return existing;
|
|
967
|
+
const created = {
|
|
968
|
+
...classification,
|
|
969
|
+
files: /* @__PURE__ */ new Map()
|
|
848
970
|
};
|
|
849
|
-
|
|
850
|
-
|
|
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));
|
|
971
|
+
packages.set(classification.id, created);
|
|
972
|
+
return created;
|
|
868
973
|
}
|
|
869
|
-
function
|
|
870
|
-
const
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
974
|
+
function ensureModule(modules, id, source, sourceType) {
|
|
975
|
+
const existing = modules.get(id);
|
|
976
|
+
if (existing) return existing;
|
|
977
|
+
const created = {
|
|
978
|
+
id,
|
|
979
|
+
source,
|
|
980
|
+
sourceType,
|
|
981
|
+
packages: /* @__PURE__ */ new Map()
|
|
982
|
+
};
|
|
983
|
+
modules.set(id, created);
|
|
984
|
+
return created;
|
|
985
|
+
}
|
|
986
|
+
function registerModuleInPackage(modules, moduleId, source, sourceType, packageId, fileName) {
|
|
987
|
+
const moduleEntry = ensureModule(modules, moduleId, source, sourceType);
|
|
988
|
+
const files = moduleEntry.packages.get(packageId) ?? /* @__PURE__ */ new Set();
|
|
989
|
+
files.add(fileName);
|
|
990
|
+
moduleEntry.packages.set(packageId, files);
|
|
991
|
+
}
|
|
992
|
+
//#endregion
|
|
993
|
+
//#region src/analyze/subpackages/output.ts
|
|
994
|
+
function getAssetBuffer(asset) {
|
|
995
|
+
if (typeof asset.source === "string") return Buffer.from(asset.source, "utf8");
|
|
996
|
+
if (asset.source instanceof Uint8Array) return Buffer.from(asset.source);
|
|
997
|
+
}
|
|
998
|
+
function getCompressedSizes$1(content) {
|
|
999
|
+
if (content === void 0) return {};
|
|
1000
|
+
const buffer = typeof content === "string" ? Buffer.from(content, "utf8") : Buffer.from(content);
|
|
1001
|
+
return {
|
|
1002
|
+
gzipSize: gzipSync(buffer).byteLength,
|
|
1003
|
+
brotliSize: brotliCompressSync(buffer).byteLength
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
function processChunk(chunk, origin, ctx, classifierContext, packages, modules) {
|
|
1007
|
+
const classification = classifyPackage(chunk.fileName, origin, classifierContext);
|
|
1008
|
+
const packageEntry = ensurePackage(packages, classification);
|
|
1009
|
+
const chunkEntry = {
|
|
1010
|
+
file: chunk.fileName,
|
|
1011
|
+
type: "chunk",
|
|
1012
|
+
from: origin,
|
|
1013
|
+
size: typeof chunk.code === "string" ? Buffer.byteLength(chunk.code, "utf8") : void 0,
|
|
1014
|
+
...getCompressedSizes$1(chunk.code),
|
|
1015
|
+
isEntry: chunk.isEntry,
|
|
1016
|
+
modules: []
|
|
1017
|
+
};
|
|
1018
|
+
const moduleEntries = Object.entries(chunk.modules ?? {});
|
|
1019
|
+
for (const [rawModuleId, info] of moduleEntries) {
|
|
1020
|
+
const absoluteId = normalizeModuleId(rawModuleId);
|
|
1021
|
+
if (!absoluteId) continue;
|
|
1022
|
+
const { source, sourceType } = resolveModuleSourceType(absoluteId, ctx);
|
|
1023
|
+
const moduleEntry = {
|
|
1024
|
+
id: absoluteId,
|
|
1025
|
+
source,
|
|
1026
|
+
sourceType,
|
|
1027
|
+
bytes: info?.renderedLength
|
|
1028
|
+
};
|
|
1029
|
+
if (typeof info?.code === "string") moduleEntry.originalBytes = Buffer.byteLength(info.code, "utf8");
|
|
1030
|
+
chunkEntry.modules.push(moduleEntry);
|
|
1031
|
+
registerModuleInPackage(modules, absoluteId, source, sourceType, classification.id, chunk.fileName);
|
|
1032
|
+
}
|
|
1033
|
+
if (chunkEntry.modules) chunkEntry.modules.sort((a, b) => a.source.localeCompare(b.source));
|
|
1034
|
+
packageEntry.files.set(chunk.fileName, chunkEntry);
|
|
1035
|
+
}
|
|
1036
|
+
function processAsset(asset, origin, ctx, classifierContext, packages, modules) {
|
|
1037
|
+
const classification = classifyPackage(asset.fileName, origin, classifierContext);
|
|
1038
|
+
const packageEntry = ensurePackage(packages, classification);
|
|
1039
|
+
const assetBuffer = getAssetBuffer(asset);
|
|
1040
|
+
const entry = {
|
|
1041
|
+
file: asset.fileName,
|
|
1042
|
+
type: "asset",
|
|
1043
|
+
from: origin,
|
|
1044
|
+
size: assetBuffer?.byteLength,
|
|
1045
|
+
...getCompressedSizes$1(assetBuffer)
|
|
1046
|
+
};
|
|
1047
|
+
const assetSource = resolveAssetSource(asset.fileName, ctx);
|
|
1048
|
+
if (assetSource) {
|
|
1049
|
+
entry.source = assetSource.source;
|
|
1050
|
+
registerModuleInPackage(modules, assetSource.absolute, assetSource.source, assetSource.sourceType, classification.id, asset.fileName);
|
|
1051
|
+
}
|
|
1052
|
+
packageEntry.files.set(asset.fileName, entry);
|
|
1053
|
+
}
|
|
1054
|
+
function processOutput(output, origin, ctx, classifierContext, packages, modules) {
|
|
1055
|
+
if (!output) return;
|
|
1056
|
+
for (const item of output.output ?? []) if (item.type === "chunk") processChunk(item, origin, ctx, classifierContext, packages, modules);
|
|
1057
|
+
else if (item.type === "asset") processAsset(item, origin, ctx, classifierContext, packages, modules);
|
|
1058
|
+
}
|
|
1059
|
+
//#endregion
|
|
1060
|
+
//#region src/analyze/subpackages/summary.ts
|
|
1061
|
+
function toArray(value) {
|
|
1062
|
+
return Array.from(value);
|
|
1063
|
+
}
|
|
1064
|
+
function summarizePackages(packages) {
|
|
1065
|
+
const order = {
|
|
1066
|
+
main: 0,
|
|
1067
|
+
subPackage: 1,
|
|
1068
|
+
independent: 2,
|
|
1069
|
+
virtual: 3
|
|
1070
|
+
};
|
|
1071
|
+
const reports = toArray(packages.values()).map((pkg) => {
|
|
1072
|
+
const files = toArray(pkg.files.values());
|
|
1073
|
+
files.sort((a, b) => a.file.localeCompare(b.file));
|
|
1074
|
+
return {
|
|
1075
|
+
id: pkg.id,
|
|
1076
|
+
label: pkg.label,
|
|
1077
|
+
type: pkg.type,
|
|
1078
|
+
files
|
|
1079
|
+
};
|
|
1080
|
+
});
|
|
1081
|
+
reports.sort((a, b) => {
|
|
1082
|
+
const delta = order[a.type] - order[b.type];
|
|
1083
|
+
if (delta !== 0) return delta;
|
|
1084
|
+
if (a.id === "__main__") return -1;
|
|
1085
|
+
if (b.id === "__main__") return 1;
|
|
1086
|
+
return a.id.localeCompare(b.id);
|
|
1087
|
+
});
|
|
1088
|
+
return reports;
|
|
1089
|
+
}
|
|
1090
|
+
function summarizeModules(modules) {
|
|
1091
|
+
const usage = toArray(modules.values()).map((module) => {
|
|
1092
|
+
const packages = toArray(module.packages.entries()).map(([packageId, files]) => {
|
|
1093
|
+
return {
|
|
1094
|
+
packageId,
|
|
1095
|
+
files: toArray(files).sort((a, b) => a.localeCompare(b))
|
|
1096
|
+
};
|
|
1097
|
+
}).sort((a, b) => {
|
|
1098
|
+
if (a.packageId === b.packageId) return 0;
|
|
1099
|
+
if (a.packageId === "__main__") return -1;
|
|
1100
|
+
if (b.packageId === "__main__") return 1;
|
|
1101
|
+
return a.packageId.localeCompare(b.packageId);
|
|
1102
|
+
});
|
|
876
1103
|
return {
|
|
877
1104
|
id: module.id,
|
|
878
1105
|
source: module.source,
|
|
879
1106
|
sourceType: module.sourceType,
|
|
880
|
-
|
|
881
|
-
bytes,
|
|
882
|
-
estimatedSavingBytes,
|
|
883
|
-
packages: packageIds,
|
|
884
|
-
advice: createDuplicateAdvice(module.sourceType, packageIds, packageTypeMap, estimatedSavingBytes)
|
|
1107
|
+
packages
|
|
885
1108
|
};
|
|
886
|
-
})
|
|
1109
|
+
});
|
|
1110
|
+
usage.sort((a, b) => a.source.localeCompare(b.source));
|
|
1111
|
+
return usage;
|
|
887
1112
|
}
|
|
888
|
-
function
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
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
|
-
});
|
|
1113
|
+
function expandVirtualModulePlacements(modules, packages, context) {
|
|
1114
|
+
for (const moduleEntry of modules.values()) {
|
|
1115
|
+
const virtualEntries = Array.from(moduleEntry.packages.entries()).filter(([packageId]) => packageId.startsWith("virtual:"));
|
|
1116
|
+
if (!virtualEntries.length) continue;
|
|
1117
|
+
const virtualFileBases = /* @__PURE__ */ new Map();
|
|
1118
|
+
for (const [virtualPackageId, files] of virtualEntries) {
|
|
1119
|
+
const combination = virtualPackageId.slice(8);
|
|
1120
|
+
if (!combination) continue;
|
|
1121
|
+
const segments = combination.split(/[_+]/).map((segment) => segment.trim()).filter(Boolean);
|
|
1122
|
+
if (!segments.length) continue;
|
|
1123
|
+
let matchingBases = virtualFileBases.get(virtualPackageId);
|
|
1124
|
+
if (!matchingBases) {
|
|
1125
|
+
matchingBases = Array.from(files).map((file) => posix.basename(file));
|
|
1126
|
+
virtualFileBases.set(virtualPackageId, matchingBases);
|
|
1127
|
+
}
|
|
1128
|
+
for (const root of segments) {
|
|
1129
|
+
if (!context.subPackageRoots.has(root)) continue;
|
|
1130
|
+
const targetPackage = packages.get(root);
|
|
1131
|
+
if (!targetPackage) continue;
|
|
1132
|
+
const moduleFiles = moduleEntry.packages.get(root) ?? /* @__PURE__ */ new Set();
|
|
1133
|
+
const targetFiles = Array.from(targetPackage.files.values()).filter((fileEntry) => {
|
|
1134
|
+
if (!matchingBases?.length) return true;
|
|
1135
|
+
const base = posix.basename(fileEntry.file);
|
|
1136
|
+
return matchingBases.includes(base);
|
|
1137
|
+
}).map((fileEntry) => fileEntry.file);
|
|
1138
|
+
if (targetFiles.length === 0) {
|
|
1139
|
+
const fallback = targetPackage.files.values().next().value;
|
|
1140
|
+
if (fallback) moduleFiles.add(fallback.file);
|
|
1141
|
+
} else for (const fileName of targetFiles) moduleFiles.add(fileName);
|
|
1142
|
+
if (moduleFiles.size > 0) moduleEntry.packages.set(root, moduleFiles);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
938
1145
|
}
|
|
939
|
-
return items.sort((a, b) => b.deltaBytes - a.deltaBytes || a.category.localeCompare(b.category) || a.label.localeCompare(b.label));
|
|
940
1146
|
}
|
|
941
|
-
function
|
|
942
|
-
const
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
deltaBytes: 0
|
|
1147
|
+
function summarizeSubPackages(metas) {
|
|
1148
|
+
const descriptors = metas.map((meta) => {
|
|
1149
|
+
return {
|
|
1150
|
+
root: meta.subPackage.root ?? "",
|
|
1151
|
+
independent: Boolean(meta.subPackage.independent),
|
|
1152
|
+
name: meta.subPackage.name
|
|
948
1153
|
};
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
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");
|
|
1154
|
+
}).filter((descriptor) => descriptor.root);
|
|
1155
|
+
descriptors.sort((a, b) => a.root.localeCompare(b.root));
|
|
1156
|
+
return descriptors;
|
|
1100
1157
|
}
|
|
1101
1158
|
//#endregion
|
|
1102
|
-
//#region src/
|
|
1103
|
-
|
|
1104
|
-
const
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
const
|
|
1108
|
-
const
|
|
1109
|
-
const
|
|
1110
|
-
const
|
|
1111
|
-
const
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1159
|
+
//#region src/analyze/subpackages/index.ts
|
|
1160
|
+
async function analyzeSubpackages(ctx) {
|
|
1161
|
+
const { configService, scanService, buildService } = ctx;
|
|
1162
|
+
if (!configService || !scanService || !buildService) throw new Error("analyzeSubpackages 需要先初始化 configService、scanService 和 buildService。");
|
|
1163
|
+
await scanService.loadAppEntry();
|
|
1164
|
+
const subPackageMetas = scanService.loadSubPackages();
|
|
1165
|
+
const subPackageRoots = /* @__PURE__ */ new Set();
|
|
1166
|
+
const independentRoots = /* @__PURE__ */ new Set();
|
|
1167
|
+
for (const meta of subPackageMetas) {
|
|
1168
|
+
const root = meta.subPackage.root;
|
|
1169
|
+
if (root) {
|
|
1170
|
+
subPackageRoots.add(root);
|
|
1171
|
+
if (meta.subPackage.independent) independentRoots.add(root);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
const classifierContext = {
|
|
1175
|
+
subPackageRoots,
|
|
1176
|
+
independentRoots
|
|
1177
|
+
};
|
|
1178
|
+
const mainResult = await build(configService.merge(void 0, createSharedBuildConfig(configService, scanService), { build: {
|
|
1179
|
+
write: false,
|
|
1180
|
+
watch: null
|
|
1181
|
+
} }));
|
|
1182
|
+
const mainOutputs = Array.isArray(mainResult) ? mainResult : [mainResult];
|
|
1183
|
+
const packages = /* @__PURE__ */ new Map();
|
|
1184
|
+
const modules = /* @__PURE__ */ new Map();
|
|
1185
|
+
const componentJsonConfigs = [];
|
|
1186
|
+
for (const output of mainOutputs) {
|
|
1187
|
+
processOutput(output, "main", ctx, classifierContext, packages, modules);
|
|
1188
|
+
componentJsonConfigs.push(...collectAnalyzeComponentJsonConfigs(output));
|
|
1189
|
+
}
|
|
1190
|
+
for (const root of independentRoots) {
|
|
1191
|
+
const output = buildService.getIndependentOutput(root);
|
|
1192
|
+
processOutput(output, "independent", ctx, classifierContext, packages, modules);
|
|
1193
|
+
componentJsonConfigs.push(...collectAnalyzeComponentJsonConfigs(output));
|
|
1194
|
+
}
|
|
1195
|
+
expandVirtualModulePlacements(modules, packages, classifierContext);
|
|
1196
|
+
const subPackages = summarizeSubPackages(subPackageMetas);
|
|
1121
1197
|
return {
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
tags: input.tags
|
|
1198
|
+
metadata: createAnalyzeMetadata(ctx.configService),
|
|
1199
|
+
packages: summarizePackages(packages),
|
|
1200
|
+
modules: summarizeModules(modules),
|
|
1201
|
+
subPackages,
|
|
1202
|
+
components: analyzeComponentUsage({
|
|
1203
|
+
jsonConfigs: componentJsonConfigs,
|
|
1204
|
+
subPackages
|
|
1205
|
+
})
|
|
1131
1206
|
};
|
|
1132
1207
|
}
|
|
1133
|
-
|
|
1208
|
+
//#endregion
|
|
1209
|
+
//#region src/analyze/subpackages/history.ts
|
|
1210
|
+
const jsonExtension = ".json";
|
|
1211
|
+
function createSnapshotFileName(date = /* @__PURE__ */ new Date()) {
|
|
1212
|
+
return `${date.toISOString().replace(/[:.]/g, "-")}${jsonExtension}`;
|
|
1213
|
+
}
|
|
1214
|
+
function isAnalyzeResult(value) {
|
|
1215
|
+
return Boolean(value && typeof value === "object" && Array.isArray(value.packages) && Array.isArray(value.modules) && Array.isArray(value.subPackages));
|
|
1216
|
+
}
|
|
1217
|
+
async function listSnapshotFiles(dir) {
|
|
1134
1218
|
try {
|
|
1135
|
-
return
|
|
1219
|
+
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));
|
|
1136
1220
|
} catch {
|
|
1137
|
-
return;
|
|
1221
|
+
return [];
|
|
1138
1222
|
}
|
|
1139
1223
|
}
|
|
1140
|
-
function
|
|
1141
|
-
const
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1224
|
+
async function trimSnapshotFiles(dir, limit) {
|
|
1225
|
+
const staleFiles = (await listSnapshotFiles(dir)).slice(limit);
|
|
1226
|
+
await Promise.all(staleFiles.map(async (file) => {
|
|
1227
|
+
try {
|
|
1228
|
+
await fs$2.unlink(file);
|
|
1229
|
+
} catch {}
|
|
1230
|
+
}));
|
|
1145
1231
|
}
|
|
1146
|
-
function
|
|
1147
|
-
const
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
const
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
configFile
|
|
1156
|
-
};
|
|
1232
|
+
async function readLatestAnalyzeHistorySnapshot(configService) {
|
|
1233
|
+
const history = resolveAnalyzeHistoryMetadata(configService);
|
|
1234
|
+
if (!history.enabled) return null;
|
|
1235
|
+
const files = await listSnapshotFiles(history.dir);
|
|
1236
|
+
for (const file of files) try {
|
|
1237
|
+
const parsed = JSON.parse(await fs$2.readFile(file, "utf8"));
|
|
1238
|
+
if (isAnalyzeResult(parsed)) return parsed;
|
|
1239
|
+
} catch {}
|
|
1240
|
+
return null;
|
|
1157
1241
|
}
|
|
1158
|
-
function
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1242
|
+
async function writeAnalyzeHistorySnapshot(result, configService, now = /* @__PURE__ */ new Date()) {
|
|
1243
|
+
const history = resolveAnalyzeHistoryMetadata(configService);
|
|
1244
|
+
if (!history.enabled) return;
|
|
1245
|
+
await fs$2.mkdir(history.dir, { recursive: true });
|
|
1246
|
+
const snapshotPath = path.join(history.dir, createSnapshotFileName(now));
|
|
1247
|
+
if (result.metadata) result.metadata = {
|
|
1248
|
+
...result.metadata,
|
|
1249
|
+
history: {
|
|
1250
|
+
...history,
|
|
1251
|
+
dir: configService.relativeCwd(history.dir),
|
|
1252
|
+
latestSnapshot: configService.relativeCwd(snapshotPath)
|
|
1253
|
+
}
|
|
1254
|
+
};
|
|
1255
|
+
await fs$2.writeFile(snapshotPath, `${JSON.stringify(result, null, 2)}\n`, "utf8");
|
|
1256
|
+
await trimSnapshotFiles(history.dir, history.limit);
|
|
1257
|
+
return snapshotPath;
|
|
1258
|
+
}
|
|
1259
|
+
//#endregion
|
|
1260
|
+
//#region src/analyze/subpackages/report.ts
|
|
1261
|
+
function formatAnalyzeBytes(bytes) {
|
|
1262
|
+
if (!bytes || Number.isNaN(bytes)) return "0 B";
|
|
1263
|
+
const units = [
|
|
1264
|
+
"B",
|
|
1265
|
+
"KB",
|
|
1266
|
+
"MB",
|
|
1267
|
+
"GB"
|
|
1268
|
+
];
|
|
1269
|
+
let value = bytes;
|
|
1270
|
+
let unitIndex = 0;
|
|
1271
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
1272
|
+
value /= 1024;
|
|
1273
|
+
unitIndex++;
|
|
1175
1274
|
}
|
|
1176
|
-
|
|
1177
|
-
logger_default.info(`如需启用,请执行 ${colors.bold(colors.green(createInstallCommand(options?.packageManagerAgent)))}`);
|
|
1275
|
+
return `${value.toFixed(value >= 100 || unitIndex === 0 ? 0 : 2)} ${units[unitIndex]}`;
|
|
1178
1276
|
}
|
|
1179
|
-
function
|
|
1180
|
-
|
|
1277
|
+
function formatDelta(bytes) {
|
|
1278
|
+
if (typeof bytes !== "number" || Number.isNaN(bytes) || bytes === 0) return "无变化";
|
|
1279
|
+
return `${bytes > 0 ? "+" : "-"}${formatAnalyzeBytes(Math.abs(bytes))}`;
|
|
1181
1280
|
}
|
|
1182
|
-
function
|
|
1183
|
-
|
|
1184
|
-
return queryIndex === -1 ? value : value.slice(0, queryIndex);
|
|
1281
|
+
function getFileSize(file) {
|
|
1282
|
+
return file.size ?? 0;
|
|
1185
1283
|
}
|
|
1186
|
-
function
|
|
1187
|
-
|
|
1188
|
-
const normalizedPath = normalizeDashboardRelativePath(stripDashboardFileQuery(value));
|
|
1189
|
-
if (!normalizedPath || path.isAbsolute(normalizedPath)) return;
|
|
1190
|
-
paths.add(normalizedPath);
|
|
1284
|
+
function getCompressedSize(file) {
|
|
1285
|
+
return file.brotliSize ?? file.gzipSize ?? 0;
|
|
1191
1286
|
}
|
|
1192
|
-
function
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
addDashboardAllowedPath(sourcePaths, file.source);
|
|
1198
|
-
for (const module of file.modules ?? []) addDashboardAllowedPath(sourcePaths, module.source);
|
|
1199
|
-
}
|
|
1200
|
-
return {
|
|
1201
|
-
artifactPaths,
|
|
1202
|
-
sourcePaths
|
|
1203
|
-
};
|
|
1287
|
+
function getBudgetLimit(type, budgets) {
|
|
1288
|
+
if (!budgets) return;
|
|
1289
|
+
if (type === "main") return budgets.mainBytes;
|
|
1290
|
+
if (type === "subPackage") return budgets.subPackageBytes;
|
|
1291
|
+
if (type === "independent") return budgets.independentBytes;
|
|
1204
1292
|
}
|
|
1205
|
-
function
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
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
|
-
};
|
|
1293
|
+
function createPackageSizeMap(result) {
|
|
1294
|
+
return new Map((result?.packages ?? []).map((pkg) => {
|
|
1295
|
+
const totalBytes = pkg.files.reduce((sum, file) => sum + getFileSize(file), 0);
|
|
1296
|
+
return [pkg.id, totalBytes];
|
|
1297
|
+
}));
|
|
1219
1298
|
}
|
|
1220
|
-
function
|
|
1221
|
-
|
|
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";
|
|
1299
|
+
function createFileKey(packageId, fileName) {
|
|
1300
|
+
return `${packageId}\u0000${fileName}`;
|
|
1228
1301
|
}
|
|
1229
|
-
function
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1302
|
+
function createFileSizeMap(result) {
|
|
1303
|
+
const map = /* @__PURE__ */ new Map();
|
|
1304
|
+
for (const pkg of result?.packages ?? []) for (const file of pkg.files) map.set(createFileKey(pkg.id, file.file), getFileSize(file));
|
|
1305
|
+
return map;
|
|
1233
1306
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
message: "必须传入合法的 kind 和相对路径。"
|
|
1243
|
-
});
|
|
1244
|
-
return;
|
|
1307
|
+
function createPackageTypeMap(result) {
|
|
1308
|
+
return new Map(result.packages.map((pkg) => [pkg.id, pkg.type]));
|
|
1309
|
+
}
|
|
1310
|
+
function createModuleByteMap(result) {
|
|
1311
|
+
const map = /* @__PURE__ */ new Map();
|
|
1312
|
+
for (const pkg of result.packages) for (const file of pkg.files) for (const mod of file.modules ?? []) {
|
|
1313
|
+
const bytes = mod.bytes ?? mod.originalBytes ?? 0;
|
|
1314
|
+
map.set(mod.id, Math.max(map.get(mod.id) ?? 0, bytes));
|
|
1245
1315
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1316
|
+
return map;
|
|
1317
|
+
}
|
|
1318
|
+
function classifyIncrementCategory(source, sourceType) {
|
|
1319
|
+
if (source.includes("wevu") || source.includes("@weapp-vite/dashboard")) return "WeVu / runtime";
|
|
1320
|
+
if (sourceType === "node_modules" || source.includes("node_modules")) return "第三方依赖";
|
|
1321
|
+
if (sourceType === "workspace") return "工作区包";
|
|
1322
|
+
if (sourceType === "plugin") return "插件生成";
|
|
1323
|
+
if (source.endsWith(".wxss") || source.endsWith(".css") || source.endsWith(".scss")) return "样式资源";
|
|
1324
|
+
if (source.endsWith(".wxml") || source.endsWith(".json")) return "页面结构";
|
|
1325
|
+
return "业务源码";
|
|
1326
|
+
}
|
|
1327
|
+
function createIncrementAdvice(item) {
|
|
1328
|
+
if (item.category === "第三方依赖") return "检查依赖是否只在必要分包引用,必要时收敛到公共入口或替换轻量实现。";
|
|
1329
|
+
if (item.category === "WeVu / runtime") return "确认运行时能力是否被新页面引入,优先排查组件和 API 引用边界。";
|
|
1330
|
+
if (item.category === "样式资源") return "检查样式重复、原子类生成范围和组件样式裁剪。";
|
|
1331
|
+
if (item.type === "new-file" || item.type === "new-module") return "确认是否为本次需求必要新增,评估分包归属和懒加载边界。";
|
|
1332
|
+
return "对比本次变更,优先查看新增引用、共享模块和大对象常量。";
|
|
1333
|
+
}
|
|
1334
|
+
function createModuleSizeMap(result) {
|
|
1335
|
+
const map = /* @__PURE__ */ new Map();
|
|
1336
|
+
for (const pkg of result?.packages ?? []) for (const file of pkg.files) for (const module of file.modules ?? []) {
|
|
1337
|
+
const bytes = module.bytes ?? module.originalBytes ?? 0;
|
|
1338
|
+
const existing = map.get(module.id);
|
|
1339
|
+
if (existing && existing.bytes >= bytes) continue;
|
|
1340
|
+
map.set(module.id, {
|
|
1341
|
+
source: module.source,
|
|
1342
|
+
sourceType: module.sourceType,
|
|
1343
|
+
bytes,
|
|
1344
|
+
packageLabel: pkg.label,
|
|
1345
|
+
file: file.file
|
|
1274
1346
|
});
|
|
1275
1347
|
}
|
|
1348
|
+
return map;
|
|
1276
1349
|
}
|
|
1277
|
-
function
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
window.${ANALYZE_GLOBAL_KEY} = current
|
|
1284
|
-
window.${PREVIOUS_ANALYZE_GLOBAL_KEY} = previous
|
|
1285
|
-
window.dispatchEvent(new CustomEvent('weapp-analyze:update', { detail: { current, previous } }))
|
|
1286
|
-
}
|
|
1287
|
-
const applyDashboardEvents = (payload) => {
|
|
1288
|
-
const events = Array.isArray(payload) ? payload : [payload]
|
|
1289
|
-
const nextEvents = events.filter(Boolean)
|
|
1290
|
-
if (nextEvents.length === 0) {
|
|
1291
|
-
return
|
|
1292
|
-
}
|
|
1293
|
-
window.${DASHBOARD_EVENTS_GLOBAL_KEY} = [
|
|
1294
|
-
...(window.${DASHBOARD_EVENTS_GLOBAL_KEY} ?? []),
|
|
1295
|
-
...nextEvents,
|
|
1296
|
-
]
|
|
1297
|
-
window.dispatchEvent(new CustomEvent('${DASHBOARD_EVENT_NAME}', { detail: nextEvents }))
|
|
1298
|
-
}
|
|
1299
|
-
const source = new EventSource('${ANALYZE_SSE_PATH}')
|
|
1300
|
-
source.onmessage = (event) => {
|
|
1301
|
-
try {
|
|
1302
|
-
applyAnalyzePayload(JSON.parse(event.data))
|
|
1303
|
-
}
|
|
1304
|
-
catch {}
|
|
1305
|
-
}
|
|
1306
|
-
if (import.meta.hot) {
|
|
1307
|
-
import.meta.hot.on('weapp-analyze:update', (payload) => {
|
|
1308
|
-
applyAnalyzePayload(payload)
|
|
1309
|
-
})
|
|
1310
|
-
import.meta.hot.on('${DASHBOARD_EVENT_NAME}', (payload) => {
|
|
1311
|
-
applyDashboardEvents(payload)
|
|
1312
|
-
})
|
|
1313
|
-
}
|
|
1314
|
-
`.trim();
|
|
1315
|
-
const broadcast = (payload) => {
|
|
1316
|
-
const serialized = `data: ${JSON.stringify(payload)}\n\n`;
|
|
1317
|
-
for (const client of sseClients) client.write(serialized);
|
|
1318
|
-
};
|
|
1319
|
-
onBroadcastReady(broadcast);
|
|
1320
|
-
return {
|
|
1321
|
-
name: "weapp-vite-analyze-html",
|
|
1322
|
-
transformIndexHtml(html) {
|
|
1323
|
-
return {
|
|
1324
|
-
html,
|
|
1325
|
-
tags: [
|
|
1326
|
-
{
|
|
1327
|
-
tag: "script",
|
|
1328
|
-
children: `window.${ANALYZE_GLOBAL_KEY} = ${JSON.stringify(state.current)}`,
|
|
1329
|
-
injectTo: "head-prepend"
|
|
1330
|
-
},
|
|
1331
|
-
{
|
|
1332
|
-
tag: "script",
|
|
1333
|
-
children: `window.${PREVIOUS_ANALYZE_GLOBAL_KEY} = ${JSON.stringify(state.previous)}`,
|
|
1334
|
-
injectTo: "head-prepend"
|
|
1335
|
-
},
|
|
1336
|
-
{
|
|
1337
|
-
tag: "script",
|
|
1338
|
-
children: `window.${DASHBOARD_EVENTS_GLOBAL_KEY} = ${JSON.stringify(runtimeEvents.current)}`,
|
|
1339
|
-
injectTo: "head-prepend"
|
|
1340
|
-
},
|
|
1341
|
-
{
|
|
1342
|
-
tag: "script",
|
|
1343
|
-
attrs: {
|
|
1344
|
-
type: "module",
|
|
1345
|
-
src: "/@vite/client"
|
|
1346
|
-
},
|
|
1347
|
-
injectTo: "head"
|
|
1348
|
-
},
|
|
1349
|
-
{
|
|
1350
|
-
tag: "script",
|
|
1351
|
-
attrs: { type: "module" },
|
|
1352
|
-
children: hotBridgeScript,
|
|
1353
|
-
injectTo: "body"
|
|
1354
|
-
}
|
|
1355
|
-
]
|
|
1356
|
-
};
|
|
1357
|
-
},
|
|
1358
|
-
configureServer(server) {
|
|
1359
|
-
onServerInstance(server);
|
|
1360
|
-
server.middlewares.use((req, res, next) => {
|
|
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) {
|
|
1367
|
-
next();
|
|
1368
|
-
return;
|
|
1369
|
-
}
|
|
1370
|
-
res.statusCode = 200;
|
|
1371
|
-
res.setHeader("Content-Type", "text/event-stream");
|
|
1372
|
-
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
1373
|
-
res.setHeader("Connection", "keep-alive");
|
|
1374
|
-
res.write(`data: ${JSON.stringify(state)}\n\n`);
|
|
1375
|
-
sseClients.add(res);
|
|
1376
|
-
req.on("close", () => {
|
|
1377
|
-
sseClients.delete(res);
|
|
1378
|
-
});
|
|
1379
|
-
});
|
|
1380
|
-
}
|
|
1381
|
-
};
|
|
1382
|
-
}
|
|
1383
|
-
async function waitForServerExit(server) {
|
|
1384
|
-
let resolved = false;
|
|
1385
|
-
const cleanup = async () => {
|
|
1386
|
-
if (resolved) return;
|
|
1387
|
-
resolved = true;
|
|
1388
|
-
try {
|
|
1389
|
-
await server.close();
|
|
1390
|
-
} catch (error) {
|
|
1391
|
-
logger_default.error(error);
|
|
1392
|
-
}
|
|
1393
|
-
};
|
|
1394
|
-
const signals = ["SIGINT", "SIGTERM"];
|
|
1395
|
-
await new Promise((resolvePromise) => {
|
|
1396
|
-
const resolveOnce = async () => {
|
|
1397
|
-
await cleanup();
|
|
1398
|
-
signals.forEach((signal) => {
|
|
1399
|
-
process.removeListener(signal, resolveOnce);
|
|
1400
|
-
});
|
|
1401
|
-
resolvePromise();
|
|
1402
|
-
};
|
|
1403
|
-
signals.forEach((signal) => {
|
|
1404
|
-
process.once(signal, resolveOnce);
|
|
1405
|
-
});
|
|
1406
|
-
server.httpServer?.once("close", resolveOnce);
|
|
1407
|
-
});
|
|
1408
|
-
}
|
|
1409
|
-
async function startAnalyzeDashboard(result, options) {
|
|
1410
|
-
const resolved = resolveDashboardRoot(options);
|
|
1411
|
-
if (!resolved) return;
|
|
1412
|
-
const { root, configFile } = resolved;
|
|
1413
|
-
const state = {
|
|
1414
|
-
current: result,
|
|
1415
|
-
previous: options?.previousResult ?? null
|
|
1416
|
-
};
|
|
1417
|
-
const contentAllowlist = { current: createDashboardContentAllowlist(result) };
|
|
1418
|
-
const runtimeEvents = { current: [createDashboardRuntimeEvent({
|
|
1419
|
-
kind: "command",
|
|
1420
|
-
level: "success",
|
|
1421
|
-
title: options?.watch ? "dashboard watch session started" : "dashboard static session started",
|
|
1422
|
-
detail: options?.watch ? "weapp-vite UI 已进入实时分析模式,后续 analyze 结果会继续推送到 dashboard。" : "weapp-vite UI 已进入静态分析模式,当前页面展示的是一次性分析结果。",
|
|
1423
|
-
tags: options?.watch ? ["watch", "analyze"] : ["static", "analyze"]
|
|
1424
|
-
}), ...(options?.initialEvents ?? []).map((event) => createDashboardRuntimeEvent(event))] };
|
|
1425
|
-
let serverRef;
|
|
1426
|
-
let broadcastAnalyzeResult;
|
|
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) => {
|
|
1431
|
-
serverRef = server;
|
|
1432
|
-
}, (broadcast) => {
|
|
1433
|
-
broadcastAnalyzeResult = broadcast;
|
|
1434
|
-
})];
|
|
1435
|
-
const serverOptions = {
|
|
1436
|
-
root,
|
|
1437
|
-
configFile: configFile ?? false,
|
|
1438
|
-
clearScreen: false,
|
|
1439
|
-
appType: "spa",
|
|
1440
|
-
publicDir: false,
|
|
1441
|
-
plugins,
|
|
1442
|
-
server: {
|
|
1443
|
-
host: "127.0.0.1",
|
|
1444
|
-
port: 0,
|
|
1445
|
-
watch: { ignored: ["**/*"] }
|
|
1446
|
-
},
|
|
1447
|
-
logLevel: "error"
|
|
1448
|
-
};
|
|
1449
|
-
const server = await createServer(serverOptions);
|
|
1450
|
-
const requestedPort = typeof serverOptions.server?.port === "number" ? serverOptions.server.port : void 0;
|
|
1451
|
-
await server.listen(requestedPort);
|
|
1452
|
-
serverRef ??= server;
|
|
1453
|
-
server.printUrls();
|
|
1454
|
-
const urls = (() => {
|
|
1455
|
-
const resolved = server.resolvedUrls;
|
|
1456
|
-
if (!resolved) return [];
|
|
1457
|
-
return [...resolved.local ?? [], ...resolved.network ?? []];
|
|
1458
|
-
})();
|
|
1459
|
-
const waitPromise = waitForServerExit(server);
|
|
1460
|
-
if (serverRef?.ws) {
|
|
1461
|
-
serverRef.ws.send({
|
|
1462
|
-
type: "custom",
|
|
1463
|
-
event: "weapp-analyze:update",
|
|
1464
|
-
data: state
|
|
1465
|
-
});
|
|
1466
|
-
serverRef.ws.send({
|
|
1467
|
-
type: "custom",
|
|
1468
|
-
event: DASHBOARD_EVENT_NAME,
|
|
1469
|
-
data: runtimeEvents.current
|
|
1470
|
-
});
|
|
1471
|
-
}
|
|
1472
|
-
broadcastAnalyzeResult?.(state);
|
|
1473
|
-
const emitRuntimeEvents = (events) => {
|
|
1474
|
-
if (events.length === 0) return;
|
|
1475
|
-
const nextEvents = events.map((event) => createDashboardRuntimeEvent(event));
|
|
1476
|
-
runtimeEvents.current = [...nextEvents, ...runtimeEvents.current].slice(0, 24);
|
|
1477
|
-
if (serverRef) serverRef.ws.send({
|
|
1478
|
-
type: "custom",
|
|
1479
|
-
event: DASHBOARD_EVENT_NAME,
|
|
1480
|
-
data: nextEvents
|
|
1481
|
-
});
|
|
1482
|
-
};
|
|
1483
|
-
const handle = {
|
|
1484
|
-
async update(nextResult, previousResult) {
|
|
1485
|
-
state.previous = previousResult ?? state.current;
|
|
1486
|
-
state.current = nextResult;
|
|
1487
|
-
contentAllowlist.current = createDashboardContentAllowlist(nextResult);
|
|
1488
|
-
emitRuntimeEvents([{
|
|
1489
|
-
kind: "build",
|
|
1490
|
-
level: "info",
|
|
1491
|
-
title: "analyze payload refreshed",
|
|
1492
|
-
detail: `已推送新的 analyze 结果,当前包含 ${nextResult.packages.length} 个包与 ${nextResult.modules.length} 个模块。`,
|
|
1493
|
-
tags: ["analyze", "refresh"]
|
|
1494
|
-
}]);
|
|
1495
|
-
if (serverRef) serverRef.ws.send({
|
|
1496
|
-
type: "custom",
|
|
1497
|
-
event: "weapp-analyze:update",
|
|
1498
|
-
data: state
|
|
1499
|
-
});
|
|
1500
|
-
broadcastAnalyzeResult?.(state);
|
|
1501
|
-
},
|
|
1502
|
-
emitRuntimeEvents,
|
|
1503
|
-
waitForExit: () => waitPromise,
|
|
1504
|
-
close: async () => {
|
|
1505
|
-
await server.close();
|
|
1506
|
-
},
|
|
1507
|
-
urls
|
|
1508
|
-
};
|
|
1509
|
-
if (options?.watch) {
|
|
1510
|
-
if (!options.silentStartupLog) {
|
|
1511
|
-
logger_default.info("weapp-vite UI 已启动(分析视图,实时模式),按 Ctrl+C 退出。");
|
|
1512
|
-
for (const url of handle.urls) logger_default.info(` ➜ ${colors.bold(colors.cyan(url))}`);
|
|
1513
|
-
}
|
|
1514
|
-
return handle;
|
|
1515
|
-
}
|
|
1516
|
-
if (!options?.silentStartupLog) {
|
|
1517
|
-
logger_default.info("weapp-vite UI 已启动(分析视图,静态模式),按 Ctrl+C 退出。");
|
|
1518
|
-
for (const url of handle.urls) logger_default.info(` ➜ ${colors.bold(colors.cyan(url))}`);
|
|
1519
|
-
}
|
|
1520
|
-
await waitPromise;
|
|
1521
|
-
}
|
|
1522
|
-
//#endregion
|
|
1523
|
-
//#region src/cli/options.ts
|
|
1524
|
-
function filterDuplicateOptions(options) {
|
|
1525
|
-
for (const [key, value] of Object.entries(options)) if (Array.isArray(value)) options[key] = value.at(-1);
|
|
1526
|
-
}
|
|
1527
|
-
function resolveConfigFile(options) {
|
|
1528
|
-
if (typeof options.config === "string") return options.config;
|
|
1529
|
-
if (typeof options.c === "string") return options.c;
|
|
1530
|
-
}
|
|
1531
|
-
function convertBase(value) {
|
|
1532
|
-
if (value === 0) return "";
|
|
1533
|
-
return value;
|
|
1534
|
-
}
|
|
1535
|
-
function coerceBooleanOption(value) {
|
|
1536
|
-
if (value === void 0) return;
|
|
1537
|
-
if (typeof value === "boolean") return value;
|
|
1538
|
-
if (typeof value === "string") {
|
|
1539
|
-
const normalized = value.trim().toLowerCase();
|
|
1540
|
-
if (normalized === "") return true;
|
|
1541
|
-
if (normalized === "false" || normalized === "0" || normalized === "off" || normalized === "no") return false;
|
|
1542
|
-
if (normalized === "true" || normalized === "1" || normalized === "on" || normalized === "yes") return true;
|
|
1543
|
-
return true;
|
|
1544
|
-
}
|
|
1545
|
-
if (typeof value === "number") return value !== 0;
|
|
1546
|
-
return Boolean(value);
|
|
1547
|
-
}
|
|
1548
|
-
function isUiEnabled(options) {
|
|
1549
|
-
return Boolean(options.ui || options.analyze);
|
|
1550
|
-
}
|
|
1551
|
-
//#endregion
|
|
1552
|
-
//#region src/cli/runtime.ts
|
|
1553
|
-
function logRuntimeTarget(targets, options = {}) {
|
|
1554
|
-
if (options.silent) return;
|
|
1555
|
-
if (targets.label === "config") {
|
|
1556
|
-
const resolvedPlatform = targets.mpPlatform ?? options.resolvedConfigPlatform;
|
|
1557
|
-
if (resolvedPlatform) {
|
|
1558
|
-
logger_default.info(`目标平台:${colors.green(resolvedPlatform)}`);
|
|
1559
|
-
return;
|
|
1560
|
-
}
|
|
1561
|
-
logger_default.info(`目标平台:使用配置文件中的 ${colors.bold(colors.green("weapp.platform"))}`);
|
|
1562
|
-
return;
|
|
1563
|
-
}
|
|
1564
|
-
logger_default.info(`目标平台:${colors.green(targets.label)}`);
|
|
1565
|
-
}
|
|
1566
|
-
function resolveRuntimeTargets(options) {
|
|
1567
|
-
const rawPlatform = typeof options.platform === "string" ? options.platform : typeof options.p === "string" ? options.p : void 0;
|
|
1568
|
-
if (!rawPlatform) return {
|
|
1569
|
-
runMini: true,
|
|
1570
|
-
runWeb: false,
|
|
1571
|
-
mpPlatform: void 0,
|
|
1572
|
-
label: "config",
|
|
1573
|
-
rawPlatform
|
|
1574
|
-
};
|
|
1575
|
-
const normalized = normalizeMiniPlatform(rawPlatform);
|
|
1576
|
-
const lowerRawPlatform = rawPlatform.toLowerCase();
|
|
1577
|
-
if (lowerRawPlatform === "all" || lowerRawPlatform === "both") return {
|
|
1578
|
-
runMini: true,
|
|
1579
|
-
runWeb: true,
|
|
1580
|
-
mpPlatform: void 0,
|
|
1581
|
-
label: "weapp + web",
|
|
1582
|
-
rawPlatform
|
|
1583
|
-
};
|
|
1584
|
-
if (!normalized) return {
|
|
1585
|
-
runMini: true,
|
|
1586
|
-
runWeb: false,
|
|
1587
|
-
mpPlatform: DEFAULT_MP_PLATFORM,
|
|
1588
|
-
label: DEFAULT_MP_PLATFORM,
|
|
1589
|
-
rawPlatform
|
|
1590
|
-
};
|
|
1591
|
-
if (normalized === "h5" || normalized === "web") return {
|
|
1592
|
-
runMini: false,
|
|
1593
|
-
runWeb: true,
|
|
1594
|
-
mpPlatform: void 0,
|
|
1595
|
-
label: normalized === "h5" ? "h5" : "web",
|
|
1596
|
-
rawPlatform
|
|
1597
|
-
};
|
|
1598
|
-
const mpPlatform = resolveMiniPlatform(normalized);
|
|
1599
|
-
if (mpPlatform) return {
|
|
1600
|
-
runMini: true,
|
|
1601
|
-
runWeb: false,
|
|
1602
|
-
mpPlatform,
|
|
1603
|
-
label: mpPlatform,
|
|
1604
|
-
rawPlatform
|
|
1605
|
-
};
|
|
1606
|
-
logger_default.warn(`未识别的平台 "${colors.yellow(rawPlatform)}",已回退到 ${colors.green(DEFAULT_MP_PLATFORM)}`);
|
|
1607
|
-
return {
|
|
1608
|
-
runMini: true,
|
|
1609
|
-
runWeb: false,
|
|
1610
|
-
mpPlatform: DEFAULT_MP_PLATFORM,
|
|
1611
|
-
label: DEFAULT_MP_PLATFORM,
|
|
1612
|
-
rawPlatform
|
|
1613
|
-
};
|
|
1614
|
-
}
|
|
1615
|
-
function createInlineConfig(mpPlatform) {
|
|
1616
|
-
if (!mpPlatform) return;
|
|
1617
|
-
return { weapp: { platform: mpPlatform } };
|
|
1618
|
-
}
|
|
1619
|
-
//#endregion
|
|
1620
|
-
//#region src/cli/commands/analyze.ts
|
|
1621
|
-
function normalizeDisplayPath(value) {
|
|
1622
|
-
return value || ".";
|
|
1623
|
-
}
|
|
1624
|
-
function getDefaultWebAnalyzeScopes() {
|
|
1625
|
-
return {
|
|
1626
|
-
supported: [
|
|
1627
|
-
"weapp.web 配置解析(enable/root/srcDir/outDir)",
|
|
1628
|
-
"runtime.executionMode 静态解析(compat/safe/strict)",
|
|
1629
|
-
"JSON 报告输出(--json/--output)"
|
|
1630
|
-
],
|
|
1631
|
-
unsupported: [
|
|
1632
|
-
"分包产物体积分析(仅小程序)",
|
|
1633
|
-
"源码模块包体映射(仅小程序)",
|
|
1634
|
-
"分析仪表盘(dashboard)"
|
|
1635
|
-
]
|
|
1636
|
-
};
|
|
1637
|
-
}
|
|
1638
|
-
function createWebAnalyzeResult(configService, options) {
|
|
1639
|
-
const webConfig = configService.weappWebConfig;
|
|
1640
|
-
const executionMode = webConfig?.pluginOptions.runtime?.executionMode ?? "compat";
|
|
1641
|
-
const scope = getDefaultWebAnalyzeScopes();
|
|
1642
|
-
const limitations = ["当前仅提供静态配置分析,不执行 Web 产物扫描。"];
|
|
1643
|
-
if (!webConfig?.enabled) limitations.push("未检测到启用的 weapp.web 配置。");
|
|
1644
|
-
return {
|
|
1645
|
-
runtime: "web",
|
|
1646
|
-
platform: options.platform,
|
|
1647
|
-
mode: configService.mode,
|
|
1648
|
-
generatedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
1649
|
-
experimental: true,
|
|
1650
|
-
configFile: configService.configFilePath ? normalizeDisplayPath(configService.relativeCwd(configService.configFilePath)) : void 0,
|
|
1651
|
-
web: {
|
|
1652
|
-
enabled: Boolean(webConfig?.enabled),
|
|
1653
|
-
root: webConfig?.root ? normalizeDisplayPath(configService.relativeCwd(webConfig.root)) : void 0,
|
|
1654
|
-
srcDir: webConfig?.srcDir,
|
|
1655
|
-
outDir: webConfig?.outDir ? normalizeDisplayPath(configService.relativeCwd(webConfig.outDir)) : void 0,
|
|
1656
|
-
executionMode
|
|
1657
|
-
},
|
|
1658
|
-
supportedScopes: scope.supported,
|
|
1659
|
-
unsupportedScopes: scope.unsupported,
|
|
1660
|
-
limitations
|
|
1661
|
-
};
|
|
1662
|
-
}
|
|
1663
|
-
function printAnalysisSummary(result) {
|
|
1664
|
-
const packageLabelMap = /* @__PURE__ */ new Map();
|
|
1665
|
-
const packageModuleSet = /* @__PURE__ */ new Map();
|
|
1666
|
-
for (const pkg of result.packages) packageLabelMap.set(pkg.id, pkg.label);
|
|
1667
|
-
for (const module of result.modules) for (const pkgRef of module.packages) {
|
|
1668
|
-
const set = packageModuleSet.get(pkgRef.packageId) ?? /* @__PURE__ */ new Set();
|
|
1669
|
-
set.add(module.id);
|
|
1670
|
-
packageModuleSet.set(pkgRef.packageId, set);
|
|
1671
|
-
}
|
|
1672
|
-
logger_default.success("分包分析完成");
|
|
1673
|
-
for (const pkg of result.packages) {
|
|
1674
|
-
const chunkCount = pkg.files.filter((file) => file.type === "chunk").length;
|
|
1675
|
-
const assetCount = pkg.files.length - chunkCount;
|
|
1676
|
-
const moduleCount = packageModuleSet.get(pkg.id)?.size ?? 0;
|
|
1677
|
-
logger_default.info(`- ${pkg.label}:${chunkCount} 个模块产物,${assetCount} 个资源,覆盖 ${moduleCount} 个源码模块`);
|
|
1678
|
-
}
|
|
1679
|
-
if (result.subPackages.length > 0) {
|
|
1680
|
-
logger_default.info("分包配置:");
|
|
1681
|
-
for (const descriptor of result.subPackages) {
|
|
1682
|
-
const segments = [descriptor.root];
|
|
1683
|
-
if (descriptor.name) segments.push(`别名:${descriptor.name}`);
|
|
1684
|
-
if (descriptor.independent) segments.push("独立构建");
|
|
1685
|
-
logger_default.info(`- ${segments.join(",")}`);
|
|
1686
|
-
}
|
|
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
|
-
}
|
|
1695
|
-
const duplicates = result.modules.filter((module) => module.packages.length > 1);
|
|
1696
|
-
if (duplicates.length === 0) {
|
|
1697
|
-
logger_default.info("未检测到跨包复用的源码模块。");
|
|
1698
|
-
return;
|
|
1699
|
-
}
|
|
1700
|
-
logger_default.info(`跨包复用/复制源码共 ${duplicates.length} 项:`);
|
|
1701
|
-
const limit = 10;
|
|
1702
|
-
const entries = duplicates.slice(0, limit);
|
|
1703
|
-
for (const module of entries) {
|
|
1704
|
-
const placements = module.packages.map((pkgRef) => {
|
|
1705
|
-
return `${packageLabelMap.get(pkgRef.packageId) ?? pkgRef.packageId} → ${pkgRef.files.join(", ")}`;
|
|
1706
|
-
}).join(";");
|
|
1707
|
-
logger_default.info(`- ${module.source} (${module.sourceType}):${placements}`);
|
|
1708
|
-
}
|
|
1709
|
-
if (duplicates.length > limit) logger_default.info(`- …其余 ${duplicates.length - limit} 项请使用 ${colors.bold(colors.green("weapp-vite analyze --json"))} 查看`);
|
|
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;
|
|
1350
|
+
function createDuplicateAdvice(sourceType, packageIds, packageTypeMap, estimatedSavingBytes) {
|
|
1351
|
+
if (packageIds.some((packageId) => packageTypeMap.get(packageId) === "independent")) return estimatedSavingBytes > 0 ? "含独立分包,先确认隔离要求,再评估是否抽公共入口。" : "含独立分包,重复可能来自隔离边界。";
|
|
1352
|
+
if (sourceType === "node_modules") return "依赖被多个包带入,检查引用边界或考虑主包公共入口。";
|
|
1353
|
+
if (sourceType === "src" || sourceType === "workspace") return "共享源码跨包重复,优先抽公共模块或调整分包归属。";
|
|
1354
|
+
if (sourceType === "plugin") return "插件生成内容跨包重复,检查插件产物输出策略。";
|
|
1355
|
+
return "检查该模块是否需要在多个包内重复存在。";
|
|
1720
1356
|
}
|
|
1721
|
-
function
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
if (result.web.enabled) {
|
|
1725
|
-
logger_default.info(`- root:${result.web.root ?? "."}`);
|
|
1726
|
-
logger_default.info(`- srcDir:${result.web.srcDir ?? "."}`);
|
|
1727
|
-
logger_default.info(`- outDir:${result.web.outDir ?? "dist/web"}`);
|
|
1728
|
-
}
|
|
1729
|
-
logger_default.info(`- executionMode:${result.web.executionMode}`);
|
|
1730
|
-
logger_default.info(`- 支持范围:${result.supportedScopes.join(";")}`);
|
|
1731
|
-
logger_default.warn(`- 未支持范围:${result.unsupportedScopes.join(";")}`);
|
|
1732
|
-
for (const limitation of result.limitations) logger_default.warn(`- 限制:${limitation}`);
|
|
1357
|
+
function formatBudgetStatus(item) {
|
|
1358
|
+
if (item.status === "ok") return "正常";
|
|
1359
|
+
return `${item.status === "exceeded" ? "超预算" : "接近预算"} ${(item.ratio * 100).toFixed(1)}%`;
|
|
1733
1360
|
}
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
const
|
|
1737
|
-
const
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
return
|
|
1361
|
+
function createActionItems(options) {
|
|
1362
|
+
const actions = [];
|
|
1363
|
+
const exceededItems = options.budgetItems.filter((item) => item.status === "exceeded");
|
|
1364
|
+
const warningItems = options.budgetItems.filter((item) => item.status === "warning");
|
|
1365
|
+
const topDuplicate = options.duplicateInsights.find((item) => item.estimatedSavingBytes > 0);
|
|
1366
|
+
if (exceededItems.length > 0) actions.push(`处理 ${exceededItems[0].label} 预算超限:当前 ${formatAnalyzeBytes(exceededItems[0].currentBytes)},限制 ${formatAnalyzeBytes(exceededItems[0].limitBytes)}。`);
|
|
1367
|
+
else if (warningItems.length > 0) actions.push(`关注 ${warningItems[0].label} 预算接近阈值:当前 ${(warningItems[0].ratio * 100).toFixed(1)}%。`);
|
|
1368
|
+
if (topDuplicate) actions.push(`优先处理重复模块 ${topDuplicate.source},估算可节省 ${formatAnalyzeBytes(topDuplicate.estimatedSavingBytes)}。`);
|
|
1369
|
+
if (actions.length === 0) actions.push("当前没有预算超限或高收益重复模块,保持观察即可。");
|
|
1370
|
+
return actions;
|
|
1744
1371
|
}
|
|
1745
|
-
function
|
|
1746
|
-
|
|
1747
|
-
|
|
1372
|
+
function createAnalyzeBudgetCheck(result) {
|
|
1373
|
+
const budgets = result.metadata?.budgets;
|
|
1374
|
+
if (!budgets) return [];
|
|
1375
|
+
const items = [];
|
|
1376
|
+
const totalBytes = result.packages.flatMap((pkg) => pkg.files).reduce((sum, file) => sum + getFileSize(file), 0);
|
|
1377
|
+
const warningRatio = budgets.warningRatio;
|
|
1378
|
+
const createItem = (options) => {
|
|
1379
|
+
const ratio = options.limitBytes > 0 ? options.currentBytes / options.limitBytes : 0;
|
|
1380
|
+
const status = ratio >= 1 ? "exceeded" : ratio >= warningRatio ? "warning" : "ok";
|
|
1381
|
+
return {
|
|
1382
|
+
...options,
|
|
1383
|
+
ratio,
|
|
1384
|
+
status
|
|
1385
|
+
};
|
|
1386
|
+
};
|
|
1387
|
+
items.push(createItem({
|
|
1388
|
+
id: "__total__",
|
|
1389
|
+
label: "总包",
|
|
1390
|
+
scope: "total",
|
|
1391
|
+
currentBytes: totalBytes,
|
|
1392
|
+
limitBytes: budgets.totalBytes
|
|
1393
|
+
}));
|
|
1394
|
+
for (const pkg of result.packages) {
|
|
1395
|
+
const limitBytes = getBudgetLimit(pkg.type, budgets);
|
|
1396
|
+
if (!limitBytes) continue;
|
|
1397
|
+
items.push(createItem({
|
|
1398
|
+
id: pkg.id,
|
|
1399
|
+
label: pkg.label,
|
|
1400
|
+
scope: pkg.type,
|
|
1401
|
+
currentBytes: pkg.files.reduce((sum, file) => sum + getFileSize(file), 0),
|
|
1402
|
+
limitBytes
|
|
1403
|
+
}));
|
|
1404
|
+
}
|
|
1405
|
+
return items.sort((a, b) => b.ratio - a.ratio || a.label.localeCompare(b.label));
|
|
1748
1406
|
}
|
|
1749
|
-
function
|
|
1750
|
-
|
|
1407
|
+
function createDuplicateModuleInsights(result) {
|
|
1408
|
+
const moduleByteMap = createModuleByteMap(result);
|
|
1409
|
+
const packageTypeMap = createPackageTypeMap(result);
|
|
1410
|
+
return result.modules.filter((module) => module.packages.length > 1).map((module) => {
|
|
1411
|
+
const bytes = moduleByteMap.get(module.id) ?? 0;
|
|
1412
|
+
const packageIds = module.packages.map((pkg) => pkg.packageId);
|
|
1413
|
+
const estimatedSavingBytes = bytes * Math.max(module.packages.length - 1, 0);
|
|
1414
|
+
return {
|
|
1415
|
+
id: module.id,
|
|
1416
|
+
source: module.source,
|
|
1417
|
+
sourceType: module.sourceType,
|
|
1418
|
+
packageCount: module.packages.length,
|
|
1419
|
+
bytes,
|
|
1420
|
+
estimatedSavingBytes,
|
|
1421
|
+
packages: packageIds,
|
|
1422
|
+
advice: createDuplicateAdvice(module.sourceType, packageIds, packageTypeMap, estimatedSavingBytes)
|
|
1423
|
+
};
|
|
1424
|
+
}).sort((a, b) => b.estimatedSavingBytes - a.estimatedSavingBytes || b.packageCount - a.packageCount || a.source.localeCompare(b.source));
|
|
1751
1425
|
}
|
|
1752
|
-
function
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
const
|
|
1758
|
-
const
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1426
|
+
function createAnalyzeIncrementAttribution(result, previousResult) {
|
|
1427
|
+
if (!previousResult) return [];
|
|
1428
|
+
const previousFiles = createFileSizeMap(previousResult);
|
|
1429
|
+
const previousModules = createModuleSizeMap(previousResult);
|
|
1430
|
+
const currentModules = createModuleSizeMap(result);
|
|
1431
|
+
const items = [];
|
|
1432
|
+
for (const pkg of result.packages) for (const file of pkg.files) {
|
|
1433
|
+
const currentBytes = getFileSize(file);
|
|
1434
|
+
const previousBytes = previousFiles.get(createFileKey(pkg.id, file.file)) ?? 0;
|
|
1435
|
+
const deltaBytes = currentBytes - previousBytes;
|
|
1436
|
+
if (deltaBytes <= 0) continue;
|
|
1437
|
+
const type = previousBytes > 0 ? "increased-file" : "new-file";
|
|
1438
|
+
const item = {
|
|
1439
|
+
key: `file:${pkg.id}:${file.file}`,
|
|
1440
|
+
type,
|
|
1441
|
+
label: file.file,
|
|
1442
|
+
category: classifyIncrementCategory(file.source ?? file.file),
|
|
1443
|
+
packageLabel: pkg.label,
|
|
1444
|
+
file: file.file,
|
|
1445
|
+
currentBytes,
|
|
1446
|
+
previousBytes,
|
|
1447
|
+
deltaBytes,
|
|
1448
|
+
advice: ""
|
|
1449
|
+
};
|
|
1450
|
+
items.push({
|
|
1451
|
+
...item,
|
|
1452
|
+
advice: createIncrementAdvice(item)
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
for (const [id, module] of currentModules) {
|
|
1456
|
+
const previousBytes = previousModules.get(id)?.bytes ?? 0;
|
|
1457
|
+
const deltaBytes = module.bytes - previousBytes;
|
|
1458
|
+
if (deltaBytes <= 0) continue;
|
|
1459
|
+
const type = previousBytes > 0 ? "increased-module" : "new-module";
|
|
1460
|
+
const item = {
|
|
1461
|
+
key: `module:${id}`,
|
|
1462
|
+
type,
|
|
1463
|
+
label: module.source,
|
|
1464
|
+
category: classifyIncrementCategory(module.source, module.sourceType),
|
|
1465
|
+
packageLabel: module.packageLabel,
|
|
1466
|
+
file: module.file,
|
|
1467
|
+
currentBytes: module.bytes,
|
|
1468
|
+
previousBytes,
|
|
1469
|
+
deltaBytes,
|
|
1470
|
+
advice: ""
|
|
1471
|
+
};
|
|
1472
|
+
items.push({
|
|
1473
|
+
...item,
|
|
1474
|
+
advice: createIncrementAdvice(item)
|
|
1475
|
+
});
|
|
1777
1476
|
}
|
|
1477
|
+
return items.sort((a, b) => b.deltaBytes - a.deltaBytes || a.category.localeCompare(b.category) || a.label.localeCompare(b.label));
|
|
1778
1478
|
}
|
|
1779
|
-
function
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
const
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1479
|
+
function createAnalyzeIncrementCategorySummary(items) {
|
|
1480
|
+
const map = /* @__PURE__ */ new Map();
|
|
1481
|
+
for (const item of items) {
|
|
1482
|
+
const entry = map.get(item.category) ?? {
|
|
1483
|
+
category: item.category,
|
|
1484
|
+
count: 0,
|
|
1485
|
+
deltaBytes: 0
|
|
1486
|
+
};
|
|
1487
|
+
entry.count += 1;
|
|
1488
|
+
entry.deltaBytes += item.deltaBytes;
|
|
1489
|
+
map.set(item.category, entry);
|
|
1490
|
+
}
|
|
1491
|
+
return [...map.values()].sort((a, b) => b.deltaBytes - a.deltaBytes || b.count - a.count || a.category.localeCompare(b.category));
|
|
1492
|
+
}
|
|
1493
|
+
function createAnalyzePrMarkdownReport(result, previousResult) {
|
|
1494
|
+
const files = result.packages.flatMap((pkg) => pkg.files.map((file) => ({
|
|
1495
|
+
pkg,
|
|
1496
|
+
file
|
|
1497
|
+
})));
|
|
1498
|
+
const totalBytes = files.reduce((sum, item) => sum + getFileSize(item.file), 0);
|
|
1499
|
+
const compressedBytes = files.reduce((sum, item) => sum + getCompressedSize(item.file), 0);
|
|
1500
|
+
const previousTotalBytes = previousResult?.packages.flatMap((pkg) => pkg.files).reduce((sum, file) => sum + getFileSize(file), 0);
|
|
1501
|
+
const incrementItems = createAnalyzeIncrementAttribution(result, previousResult);
|
|
1502
|
+
const incrementSummary = createAnalyzeIncrementCategorySummary(incrementItems);
|
|
1503
|
+
const duplicateInsights = createDuplicateModuleInsights(result);
|
|
1504
|
+
const budgetItems = createAnalyzeBudgetCheck(result);
|
|
1505
|
+
const budgetIssues = budgetItems.filter((item) => item.status !== "ok");
|
|
1506
|
+
const actionItems = createActionItems({
|
|
1507
|
+
budgetItems,
|
|
1508
|
+
duplicateInsights
|
|
1509
|
+
}).slice(0, 3);
|
|
1510
|
+
const budgetRows = budgetIssues.slice(0, 5).map((item) => `| ${item.label} | ${formatAnalyzeBytes(item.currentBytes)} | ${formatAnalyzeBytes(item.limitBytes)} | ${formatBudgetStatus(item)} |`).join("\n");
|
|
1511
|
+
const incrementRows = incrementItems.slice(0, 8).map((item) => `| ${item.label} | ${item.category} | ${item.packageLabel} | ${formatAnalyzeBytes(item.deltaBytes)} | ${item.advice} |`).join("\n");
|
|
1512
|
+
const sourceRows = incrementSummary.slice(0, 6).map((item) => `| ${item.category} | ${item.count} | ${formatAnalyzeBytes(item.deltaBytes)} |`).join("\n");
|
|
1513
|
+
const duplicateRows = duplicateInsights.slice(0, 5).map((module) => `| ${module.source} | ${module.packageCount} | ${formatAnalyzeBytes(module.estimatedSavingBytes)} | ${module.advice} |`).join("\n");
|
|
1514
|
+
return [
|
|
1515
|
+
"## weapp-vite analyze PR 摘要",
|
|
1516
|
+
"",
|
|
1517
|
+
`- 总产物体积:${formatAnalyzeBytes(totalBytes)}(较上次 ${formatDelta(typeof previousTotalBytes === "number" ? totalBytes - previousTotalBytes : void 0)})`,
|
|
1518
|
+
`- 压缩后体积:${formatAnalyzeBytes(compressedBytes)}`,
|
|
1519
|
+
`- 预算告警:${budgetIssues.length}`,
|
|
1520
|
+
`- 增量归因:${incrementItems.length > 0 ? `${incrementItems.length} 项正向增长` : "无正向增长"}`,
|
|
1521
|
+
`- 跨包复用:${duplicateInsights.length}`,
|
|
1522
|
+
"",
|
|
1523
|
+
"### 建议动作",
|
|
1524
|
+
"",
|
|
1525
|
+
...actionItems.map((item) => `- ${item}`),
|
|
1526
|
+
"",
|
|
1527
|
+
"### 预算状态",
|
|
1528
|
+
"",
|
|
1529
|
+
"| 对象 | 当前体积 | 预算 | 状态 |",
|
|
1530
|
+
"| --- | ---: | ---: | --- |",
|
|
1531
|
+
budgetRows || "| - | 0 B | 0 B | 正常 |",
|
|
1532
|
+
"",
|
|
1533
|
+
"### 增量来源",
|
|
1534
|
+
"",
|
|
1535
|
+
"| 来源 | 项数 | 增量 |",
|
|
1536
|
+
"| --- | ---: | ---: |",
|
|
1537
|
+
sourceRows || "| - | 0 | 0 B |",
|
|
1538
|
+
"",
|
|
1539
|
+
"### Top 增量",
|
|
1540
|
+
"",
|
|
1541
|
+
"| 文件/模块 | 来源 | 包 | 增量 | 建议 |",
|
|
1542
|
+
"| --- | --- | --- | ---: | --- |",
|
|
1543
|
+
incrementRows || "| - | - | - | 0 B | - |",
|
|
1544
|
+
"",
|
|
1545
|
+
"### 重复模块",
|
|
1546
|
+
"",
|
|
1547
|
+
"| 模块 | 包数量 | 估算可节省 | 建议 |",
|
|
1548
|
+
"| --- | ---: | ---: | --- |",
|
|
1549
|
+
duplicateRows || "| - | 0 | 0 B | - |",
|
|
1550
|
+
""
|
|
1551
|
+
].join("\n");
|
|
1552
|
+
}
|
|
1553
|
+
function createAnalyzeMarkdownReport(result, previousResult) {
|
|
1554
|
+
const files = result.packages.flatMap((pkg) => pkg.files.map((file) => ({
|
|
1555
|
+
pkg,
|
|
1556
|
+
file
|
|
1557
|
+
})));
|
|
1558
|
+
const totalBytes = files.reduce((sum, item) => sum + getFileSize(item.file), 0);
|
|
1559
|
+
const compressedBytes = files.reduce((sum, item) => sum + getCompressedSize(item.file), 0);
|
|
1560
|
+
const duplicateInsights = createDuplicateModuleInsights(result);
|
|
1561
|
+
const incrementItems = createAnalyzeIncrementAttribution(result, previousResult);
|
|
1562
|
+
const incrementSummary = createAnalyzeIncrementCategorySummary(incrementItems);
|
|
1563
|
+
const budgetItems = createAnalyzeBudgetCheck(result);
|
|
1564
|
+
const previousTotalBytes = previousResult?.packages.flatMap((pkg) => pkg.files).reduce((sum, file) => sum + getFileSize(file), 0);
|
|
1565
|
+
const previousPackageSizes = createPackageSizeMap(previousResult);
|
|
1566
|
+
const budgets = result.metadata?.budgets;
|
|
1567
|
+
const budgetIssues = budgetItems.filter((item) => item.status !== "ok");
|
|
1568
|
+
const actionItems = createActionItems({
|
|
1569
|
+
budgetItems,
|
|
1570
|
+
duplicateInsights
|
|
1858
1571
|
});
|
|
1572
|
+
const packageRows = result.packages.map((pkg) => {
|
|
1573
|
+
const size = pkg.files.reduce((sum, file) => sum + getFileSize(file), 0);
|
|
1574
|
+
const compressed = pkg.files.reduce((sum, file) => sum + getCompressedSize(file), 0);
|
|
1575
|
+
const previousSize = previousPackageSizes.get(pkg.id);
|
|
1576
|
+
const budgetStatus = budgetItems.find((item) => item.id === pkg.id);
|
|
1577
|
+
return `| ${pkg.label} | ${pkg.type} | ${formatAnalyzeBytes(size)} | ${formatAnalyzeBytes(compressed)} | ${formatDelta(typeof previousSize === "number" ? size - previousSize : void 0)} | ${budgetStatus ? formatBudgetStatus(budgetStatus) : "正常"} |`;
|
|
1578
|
+
}).join("\n");
|
|
1579
|
+
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");
|
|
1580
|
+
const duplicateRows = duplicateInsights.slice(0, 10).map((module) => `| ${module.source} | ${module.sourceType} | ${module.packageCount} | ${formatAnalyzeBytes(module.estimatedSavingBytes)} | ${module.advice} |`).join("\n");
|
|
1581
|
+
const budgetRows = budgetIssues.map((item) => `| ${item.label} | ${item.scope} | ${formatAnalyzeBytes(item.currentBytes)} | ${formatAnalyzeBytes(item.limitBytes)} | ${formatBudgetStatus(item)} |`).join("\n");
|
|
1582
|
+
const incrementRows = incrementItems.slice(0, 10).map((item) => `| ${item.label} | ${item.category} | ${item.packageLabel} | ${formatAnalyzeBytes(item.deltaBytes)} | ${item.advice} |`).join("\n");
|
|
1583
|
+
const incrementSummaryRows = incrementSummary.slice(0, 8).map((item) => `| ${item.category} | ${item.count} | ${formatAnalyzeBytes(item.deltaBytes)} |`).join("\n");
|
|
1584
|
+
return [
|
|
1585
|
+
"# weapp-vite analyze 报告",
|
|
1586
|
+
"",
|
|
1587
|
+
`生成时间:${result.metadata?.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString()}`,
|
|
1588
|
+
"",
|
|
1589
|
+
"## 本次变化摘要",
|
|
1590
|
+
"",
|
|
1591
|
+
`- 总产物体积:${formatAnalyzeBytes(totalBytes)}`,
|
|
1592
|
+
`- 压缩后体积:${formatAnalyzeBytes(compressedBytes)}`,
|
|
1593
|
+
`- 较上次:${formatDelta(typeof previousTotalBytes === "number" ? totalBytes - previousTotalBytes : void 0)}`,
|
|
1594
|
+
`- 包体数量:${result.packages.length}`,
|
|
1595
|
+
`- 源码模块:${result.modules.length}`,
|
|
1596
|
+
`- 跨包复用:${duplicateInsights.length}`,
|
|
1597
|
+
`- 预算来源:${budgets?.source === "config" ? "配置" : "默认"}`,
|
|
1598
|
+
"",
|
|
1599
|
+
"## 预算告警",
|
|
1600
|
+
"",
|
|
1601
|
+
"| 对象 | 范围 | 当前体积 | 预算 | 状态 |",
|
|
1602
|
+
"| --- | --- | ---: | ---: | --- |",
|
|
1603
|
+
budgetRows || "| - | - | 0 B | 0 B | 正常 |",
|
|
1604
|
+
"",
|
|
1605
|
+
"## 建议动作",
|
|
1606
|
+
"",
|
|
1607
|
+
...actionItems.map((item) => `- ${item}`),
|
|
1608
|
+
"",
|
|
1609
|
+
"## 增量归因",
|
|
1610
|
+
"",
|
|
1611
|
+
"| 来源 | 项数 | 增量 |",
|
|
1612
|
+
"| --- | ---: | ---: |",
|
|
1613
|
+
incrementSummaryRows || "| - | 0 | 0 B |",
|
|
1614
|
+
"",
|
|
1615
|
+
"| 文件/模块 | 来源 | 包 | 增量 | 建议 |",
|
|
1616
|
+
"| --- | --- | --- | ---: | --- |",
|
|
1617
|
+
incrementRows || "| - | - | - | 0 B | - |",
|
|
1618
|
+
"",
|
|
1619
|
+
"## 包体预算",
|
|
1620
|
+
"",
|
|
1621
|
+
"| 包 | 类型 | 体积 | 压缩后 | 较上次 | 预算 |",
|
|
1622
|
+
"| --- | --- | ---: | ---: | ---: | --- |",
|
|
1623
|
+
packageRows || "| - | - | 0 B | 0 B | 无变化 | 正常 |",
|
|
1624
|
+
"",
|
|
1625
|
+
"## Top 文件",
|
|
1626
|
+
"",
|
|
1627
|
+
"| 文件 | 包 | 类型 | 体积 | 压缩后 |",
|
|
1628
|
+
"| --- | --- | --- | ---: | ---: |",
|
|
1629
|
+
topFileRows || "| - | - | - | 0 B | 0 B |",
|
|
1630
|
+
"",
|
|
1631
|
+
"## 重复模块",
|
|
1632
|
+
"",
|
|
1633
|
+
"| 模块 | 来源 | 包数量 | 估算可节省 | 建议 |",
|
|
1634
|
+
"| --- | --- | ---: | ---: | --- |",
|
|
1635
|
+
duplicateRows || "| - | - | 0 | 0 B | - |",
|
|
1636
|
+
""
|
|
1637
|
+
].join("\n");
|
|
1859
1638
|
}
|
|
1860
1639
|
//#endregion
|
|
1861
|
-
//#region src/cli/
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1640
|
+
//#region src/cli/analyze/dashboard.ts
|
|
1641
|
+
const ANALYZE_GLOBAL_KEY = "__WEAPP_VITE_ANALYZE_RESULT__";
|
|
1642
|
+
const PREVIOUS_ANALYZE_GLOBAL_KEY = "__WEAPP_VITE_PREVIOUS_ANALYZE_RESULT__";
|
|
1643
|
+
const DASHBOARD_EVENTS_GLOBAL_KEY = "__WEAPP_VITE_DASHBOARD_EVENTS__";
|
|
1644
|
+
const ANALYZE_DASHBOARD_PACKAGE_NAME = "@weapp-vite/dashboard";
|
|
1645
|
+
const ANALYZE_SSE_PATH = "/__weapp_vite_analyze";
|
|
1646
|
+
const FILE_CONTENT_PATH = "/__weapp_vite_file_content";
|
|
1647
|
+
const DASHBOARD_EVENT_NAME = "weapp-dashboard:event";
|
|
1648
|
+
const MAX_FILE_CONTENT_BYTES = 2 * 1024 * 1024;
|
|
1649
|
+
const require = createRequire(import.meta.url);
|
|
1650
|
+
function createInstallCommand(agent) {
|
|
1651
|
+
const resolved = resolveCommand(agent ?? "npm", "install", [ANALYZE_DASHBOARD_PACKAGE_NAME]);
|
|
1652
|
+
if (!resolved) return `npm install ${ANALYZE_DASHBOARD_PACKAGE_NAME}`;
|
|
1653
|
+
return `${resolved.command} ${resolved.args.join(" ")}`;
|
|
1867
1654
|
}
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
let logBuildAppFinishOnlyShowOnce = false;
|
|
1871
|
-
function collectServerUrls(webServer) {
|
|
1872
|
-
const urls = webServer?.resolvedUrls;
|
|
1873
|
-
if (!urls) return [];
|
|
1874
|
-
return [...urls.local ?? [], ...urls.network ?? []];
|
|
1655
|
+
function formatEventTimestamp(date = /* @__PURE__ */ new Date()) {
|
|
1656
|
+
return date.toLocaleTimeString("zh-CN", { hour12: false });
|
|
1875
1657
|
}
|
|
1876
|
-
function
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1658
|
+
function createDashboardRuntimeEvent(input) {
|
|
1659
|
+
return {
|
|
1660
|
+
id: `dashboard:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`,
|
|
1661
|
+
kind: input.kind,
|
|
1662
|
+
level: input.level,
|
|
1663
|
+
title: input.title,
|
|
1664
|
+
detail: input.detail,
|
|
1665
|
+
timestamp: formatEventTimestamp(),
|
|
1666
|
+
source: input.source ?? "weapp-vite",
|
|
1667
|
+
durationMs: input.durationMs,
|
|
1668
|
+
tags: input.tags,
|
|
1669
|
+
profile: input.profile
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
function readDashboardManifest(packageJsonPath) {
|
|
1673
|
+
try {
|
|
1674
|
+
return parseCommentJson(fs$1.readFileSync(packageJsonPath, "utf8"));
|
|
1675
|
+
} catch {
|
|
1885
1676
|
return;
|
|
1886
1677
|
}
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1678
|
+
}
|
|
1679
|
+
function resolveDashboardDistRoot(packageRoot, manifest) {
|
|
1680
|
+
const distDir = manifest?.weappViteDashboard?.distDir ?? "dist";
|
|
1681
|
+
const distRoot = path.resolve(packageRoot, distDir);
|
|
1682
|
+
if (!fs$1.existsSync(distRoot)) return;
|
|
1683
|
+
return { root: distRoot };
|
|
1684
|
+
}
|
|
1685
|
+
function resolveDashboardDevRoot(packageRoot, manifest) {
|
|
1686
|
+
const devRoot = manifest?.weappViteDashboard?.devRoot;
|
|
1687
|
+
const devConfigFile = manifest?.weappViteDashboard?.devConfigFile;
|
|
1688
|
+
if (!devRoot || !devConfigFile) return;
|
|
1689
|
+
const root = path.resolve(packageRoot, devRoot);
|
|
1690
|
+
const configFile = path.resolve(root, devConfigFile);
|
|
1691
|
+
if (!fs$1.existsSync(root) || !fs$1.existsSync(configFile)) return;
|
|
1692
|
+
return {
|
|
1693
|
+
root,
|
|
1694
|
+
configFile
|
|
1890
1695
|
};
|
|
1891
|
-
const devCommand = `${command} ${args.join(" ")}`;
|
|
1892
|
-
logger_default.success("开发服务已就绪:");
|
|
1893
|
-
logger_default.info(`小程序:执行 ${colors.bold(colors.green(devCommand))},或手动导入 ${colors.green(getProjectConfigFileName(configService.platform))}`);
|
|
1894
|
-
if (uiUrls.length > 0) logger_default.info(`UI:${colors.cyan(uiUrls[0])}`);
|
|
1895
|
-
else if (!skipMini) logger_default.info("UI:未启用");
|
|
1896
|
-
if (webUrls.length > 0) logger_default.info(`Web:${colors.cyan(webUrls[0])}`);
|
|
1897
|
-
const projectConfigFileName = getProjectConfigFileName(configService.platform);
|
|
1898
|
-
if (!uiUrls.length && !webUrls.length) logger_default.info(`提示:手动打开对应平台开发者工具,导入根目录(${colors.green(projectConfigFileName)} 文件所在目录)`);
|
|
1899
|
-
logBuildAppFinishOnlyShowOnce = true;
|
|
1900
1696
|
}
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1697
|
+
function resolveDashboardRoot(options) {
|
|
1698
|
+
const resolvePaths = options?.cwd && options.cwd !== process.cwd() ? [options.cwd, process.cwd()] : options?.cwd ? [options.cwd] : void 0;
|
|
1699
|
+
let dashboardPackageRoot;
|
|
1700
|
+
let dashboardManifest;
|
|
1701
|
+
try {
|
|
1702
|
+
const dashboardPackageJsonPath = require.resolve(`${ANALYZE_DASHBOARD_PACKAGE_NAME}/package.json`, { paths: resolvePaths });
|
|
1703
|
+
dashboardPackageRoot = path.dirname(dashboardPackageJsonPath);
|
|
1704
|
+
dashboardManifest = readDashboardManifest(dashboardPackageJsonPath);
|
|
1705
|
+
} catch {
|
|
1706
|
+
dashboardPackageRoot = void 0;
|
|
1707
|
+
dashboardManifest = void 0;
|
|
1708
|
+
}
|
|
1709
|
+
if (dashboardPackageRoot) {
|
|
1710
|
+
const devResolved = resolveDashboardDevRoot(dashboardPackageRoot, dashboardManifest);
|
|
1711
|
+
if (devResolved) return devResolved;
|
|
1712
|
+
const distResolved = resolveDashboardDistRoot(dashboardPackageRoot, dashboardManifest);
|
|
1713
|
+
if (distResolved) return distResolved;
|
|
1714
|
+
}
|
|
1715
|
+
logger_default.warn(`[weapp-vite ui] 未安装可选仪表盘包 ${colors.bold(colors.green(ANALYZE_DASHBOARD_PACKAGE_NAME))},已自动降级关闭 dashboard 能力。`);
|
|
1716
|
+
logger_default.info(`如需启用,请执行 ${colors.bold(colors.green(createInstallCommand(options?.packageManagerAgent)))}`);
|
|
1904
1717
|
}
|
|
1905
|
-
function
|
|
1906
|
-
|
|
1907
|
-
if (typeof item.source === "string") return Buffer.byteLength(item.source, "utf8");
|
|
1908
|
-
if (item.source instanceof Uint8Array) return item.source.byteLength;
|
|
1909
|
-
return 0;
|
|
1718
|
+
function normalizeDashboardRelativePath(value) {
|
|
1719
|
+
return value.replaceAll("\\", "/");
|
|
1910
1720
|
}
|
|
1911
|
-
function
|
|
1912
|
-
const
|
|
1913
|
-
return
|
|
1721
|
+
function stripDashboardFileQuery(value) {
|
|
1722
|
+
const queryIndex = value.indexOf("?");
|
|
1723
|
+
return queryIndex === -1 ? value : value.slice(0, queryIndex);
|
|
1914
1724
|
}
|
|
1915
|
-
function
|
|
1916
|
-
|
|
1917
|
-
const
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
for (const current of outputs) for (const item of current.output ?? []) {
|
|
1921
|
-
const root = resolveSubPackageRoot(item.fileName, roots) ?? "__main__";
|
|
1922
|
-
packageBytes.set(root, (packageBytes.get(root) ?? 0) + getOutputItemBytes(item));
|
|
1923
|
-
}
|
|
1924
|
-
const reports = [{
|
|
1925
|
-
root: "__main__",
|
|
1926
|
-
label: "主包",
|
|
1927
|
-
bytes: packageBytes.get("__main__") ?? 0
|
|
1928
|
-
}];
|
|
1929
|
-
for (const root of roots) {
|
|
1930
|
-
const meta = subPackageMap?.get(root);
|
|
1931
|
-
const isIndependent = Boolean(meta?.subPackage.independent);
|
|
1932
|
-
reports.push({
|
|
1933
|
-
root,
|
|
1934
|
-
label: `${isIndependent ? "独立分包" : "分包"} ${root}`,
|
|
1935
|
-
bytes: packageBytes.get(root) ?? 0
|
|
1936
|
-
});
|
|
1937
|
-
}
|
|
1938
|
-
return reports;
|
|
1725
|
+
function addDashboardAllowedPath(paths, value) {
|
|
1726
|
+
if (!value || value.includes("\0")) return;
|
|
1727
|
+
const normalizedPath = normalizeDashboardRelativePath(stripDashboardFileQuery(value));
|
|
1728
|
+
if (!normalizedPath || path.isAbsolute(normalizedPath)) return;
|
|
1729
|
+
paths.add(normalizedPath);
|
|
1939
1730
|
}
|
|
1940
|
-
function
|
|
1941
|
-
const
|
|
1942
|
-
const
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
if (report.bytes <= warningBytes) continue;
|
|
1948
|
-
logger_default.warn(`[包体积] ${colors.yellow(report.label)} 体积 ${colors.yellow(formatBytes(report.bytes))},已超过阈值 ${colors.yellow(formatBytes(warningBytes))}。`);
|
|
1731
|
+
function createDashboardContentAllowlist(result) {
|
|
1732
|
+
const artifactPaths = /* @__PURE__ */ new Set();
|
|
1733
|
+
const sourcePaths = /* @__PURE__ */ new Set();
|
|
1734
|
+
for (const packageReport of result.packages) for (const file of packageReport.files) {
|
|
1735
|
+
addDashboardAllowedPath(artifactPaths, file.file);
|
|
1736
|
+
addDashboardAllowedPath(sourcePaths, file.source);
|
|
1737
|
+
for (const module of file.modules ?? []) addDashboardAllowedPath(sourcePaths, module.source);
|
|
1949
1738
|
}
|
|
1739
|
+
return {
|
|
1740
|
+
artifactPaths,
|
|
1741
|
+
sourcePaths
|
|
1742
|
+
};
|
|
1950
1743
|
}
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1744
|
+
function resolveDashboardContentPath(root, requestPath, options) {
|
|
1745
|
+
if (!root || !requestPath || requestPath.includes("\0")) return;
|
|
1746
|
+
const normalizedRequestPath = normalizeDashboardRelativePath(stripDashboardFileQuery(requestPath));
|
|
1747
|
+
if (path.isAbsolute(normalizedRequestPath)) return;
|
|
1748
|
+
if (!options.allowedPaths.has(normalizedRequestPath)) return;
|
|
1749
|
+
const resolvedRoot = path.resolve(root);
|
|
1750
|
+
const absolutePath = path.resolve(resolvedRoot, normalizedRequestPath);
|
|
1751
|
+
const relativePath = path.relative(resolvedRoot, absolutePath);
|
|
1752
|
+
if (!relativePath) return;
|
|
1753
|
+
if (!options.allowParent && (relativePath.startsWith("..") || path.isAbsolute(relativePath))) return;
|
|
1754
|
+
return {
|
|
1755
|
+
absolutePath,
|
|
1756
|
+
relativePath: options.allowParent ? normalizedRequestPath : normalizeDashboardRelativePath(relativePath)
|
|
1757
|
+
};
|
|
1960
1758
|
}
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
if (
|
|
1965
|
-
if (
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
if (command === "cache") {
|
|
1970
|
-
const cleanType = readArgOption(argv, "--clean", "-c");
|
|
1971
|
-
if (cleanType !== "compile" && cleanType !== "all") return false;
|
|
1972
|
-
await clearWechatIdeCacheByAutomator({
|
|
1973
|
-
clean: cleanType,
|
|
1974
|
-
projectPath
|
|
1975
|
-
});
|
|
1976
|
-
return true;
|
|
1977
|
-
}
|
|
1978
|
-
return false;
|
|
1759
|
+
function resolveDashboardFileLanguage(filePath) {
|
|
1760
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
1761
|
+
if (extension === ".js" || extension === ".mjs" || extension === ".cjs" || extension === ".wxs" || extension === ".sjs") return "javascript";
|
|
1762
|
+
if (extension === ".ts" || extension === ".mts" || extension === ".cts") return "typescript";
|
|
1763
|
+
if (extension === ".json" || extension === ".map") return "json";
|
|
1764
|
+
if (extension === ".css" || extension === ".wxss" || extension === ".scss" || extension === ".sass" || extension === ".less") return "css";
|
|
1765
|
+
if (extension === ".vue" || extension === ".wxml" || extension === ".html") return "html";
|
|
1766
|
+
return "plaintext";
|
|
1979
1767
|
}
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
if (!projectPath) return false;
|
|
1985
|
-
await openWechatIdeProjectByHttp(projectPath);
|
|
1986
|
-
return true;
|
|
1987
|
-
}
|
|
1988
|
-
if (command === "reset-fileutils") {
|
|
1989
|
-
if (!projectPath) return false;
|
|
1990
|
-
await resetWechatIdeFileUtilsByHttp(projectPath);
|
|
1991
|
-
return true;
|
|
1992
|
-
}
|
|
1993
|
-
if (command === "engine" && argv[1] === "build") {
|
|
1994
|
-
const engineProjectPath = argv[2] || projectPath;
|
|
1995
|
-
if (!engineProjectPath) return false;
|
|
1996
|
-
await runWechatIdeEngineBuild(engineProjectPath, { logPath: readArgOption(argv, "--logPath", "-l") });
|
|
1997
|
-
return true;
|
|
1998
|
-
}
|
|
1999
|
-
return false;
|
|
1768
|
+
function sendDashboardJson(res, statusCode, payload) {
|
|
1769
|
+
res.statusCode = statusCode;
|
|
1770
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
1771
|
+
res.end(JSON.stringify(payload));
|
|
2000
1772
|
}
|
|
2001
|
-
async function
|
|
2002
|
-
const
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
1773
|
+
async function sendDashboardFileContent(res, roots, allowlist, kind, requestPath) {
|
|
1774
|
+
const resolved = resolveDashboardContentPath(kind === "artifact" ? roots.artifactRoot : kind === "source" ? roots.sourceRoot : void 0, requestPath, {
|
|
1775
|
+
allowParent: kind === "source",
|
|
1776
|
+
allowedPaths: kind === "artifact" ? allowlist.artifactPaths : allowlist.sourcePaths
|
|
1777
|
+
});
|
|
1778
|
+
if (!resolved || kind !== "source" && kind !== "artifact") {
|
|
1779
|
+
sendDashboardJson(res, 400, {
|
|
1780
|
+
error: "invalid_request",
|
|
1781
|
+
message: "必须传入合法的 kind 和相对路径。"
|
|
1782
|
+
});
|
|
1783
|
+
return;
|
|
2011
1784
|
}
|
|
2012
|
-
|
|
2013
|
-
const
|
|
2014
|
-
if (!
|
|
2015
|
-
|
|
2016
|
-
|
|
1785
|
+
try {
|
|
1786
|
+
const stat = await fs$1.promises.stat(resolved.absolutePath);
|
|
1787
|
+
if (!stat.isFile()) {
|
|
1788
|
+
sendDashboardJson(res, 400, {
|
|
1789
|
+
error: "not_file",
|
|
1790
|
+
message: "目标路径不是文件。"
|
|
1791
|
+
});
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
if (stat.size > MAX_FILE_CONTENT_BYTES) {
|
|
1795
|
+
sendDashboardJson(res, 413, {
|
|
1796
|
+
error: "file_too_large",
|
|
1797
|
+
message: `文件超过 ${MAX_FILE_CONTENT_BYTES} 字节,已拒绝读取。`
|
|
1798
|
+
});
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
sendDashboardJson(res, 200, {
|
|
1802
|
+
kind,
|
|
1803
|
+
path: resolved.relativePath,
|
|
1804
|
+
language: resolveDashboardFileLanguage(resolved.relativePath),
|
|
1805
|
+
size: stat.size,
|
|
1806
|
+
content: await fs$1.promises.readFile(resolved.absolutePath, "utf8")
|
|
1807
|
+
});
|
|
1808
|
+
} catch (error) {
|
|
1809
|
+
const code = typeof error === "object" && error && "code" in error ? String(error.code) : "";
|
|
1810
|
+
sendDashboardJson(res, code === "ENOENT" ? 404 : 500, {
|
|
1811
|
+
error: code === "ENOENT" ? "not_found" : "read_failed",
|
|
1812
|
+
message: code === "ENOENT" ? "文件不存在。" : "读取文件失败。"
|
|
1813
|
+
});
|
|
2017
1814
|
}
|
|
2018
|
-
return false;
|
|
2019
1815
|
}
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
1816
|
+
function createAnalyzeHtmlPlugin(state, runtimeEvents, contentRoots, contentAllowlist, onServerInstance, onBroadcastReady) {
|
|
1817
|
+
const sseClients = /* @__PURE__ */ new Set();
|
|
1818
|
+
const hotBridgeScript = `
|
|
1819
|
+
const applyAnalyzePayload = (payload) => {
|
|
1820
|
+
const current = payload?.current ?? payload
|
|
1821
|
+
const previous = payload?.previous ?? window.${PREVIOUS_ANALYZE_GLOBAL_KEY} ?? null
|
|
1822
|
+
window.${ANALYZE_GLOBAL_KEY} = current
|
|
1823
|
+
window.${PREVIOUS_ANALYZE_GLOBAL_KEY} = previous
|
|
1824
|
+
window.dispatchEvent(new CustomEvent('weapp-analyze:update', { detail: { current, previous } }))
|
|
1825
|
+
}
|
|
1826
|
+
const applyDashboardEvents = (payload) => {
|
|
1827
|
+
const events = Array.isArray(payload) ? payload : [payload]
|
|
1828
|
+
const nextEvents = events.filter(Boolean)
|
|
1829
|
+
if (nextEvents.length === 0) {
|
|
1830
|
+
return
|
|
1831
|
+
}
|
|
1832
|
+
window.${DASHBOARD_EVENTS_GLOBAL_KEY} = [
|
|
1833
|
+
...(window.${DASHBOARD_EVENTS_GLOBAL_KEY} ?? []),
|
|
1834
|
+
...nextEvents,
|
|
1835
|
+
]
|
|
1836
|
+
window.dispatchEvent(new CustomEvent('${DASHBOARD_EVENT_NAME}', { detail: nextEvents }))
|
|
1837
|
+
}
|
|
1838
|
+
const source = new EventSource('${ANALYZE_SSE_PATH}')
|
|
1839
|
+
source.onmessage = (event) => {
|
|
1840
|
+
try {
|
|
1841
|
+
applyAnalyzePayload(JSON.parse(event.data))
|
|
1842
|
+
}
|
|
1843
|
+
catch {}
|
|
1844
|
+
}
|
|
1845
|
+
if (import.meta.hot) {
|
|
1846
|
+
import.meta.hot.on('weapp-analyze:update', (payload) => {
|
|
1847
|
+
applyAnalyzePayload(payload)
|
|
1848
|
+
})
|
|
1849
|
+
import.meta.hot.on('${DASHBOARD_EVENT_NAME}', (payload) => {
|
|
1850
|
+
applyDashboardEvents(payload)
|
|
1851
|
+
})
|
|
1852
|
+
}
|
|
1853
|
+
`.trim();
|
|
1854
|
+
const broadcast = (payload) => {
|
|
1855
|
+
const serialized = `data: ${JSON.stringify(payload)}\n\n`;
|
|
1856
|
+
for (const client of sseClients) client.write(serialized);
|
|
1857
|
+
};
|
|
1858
|
+
onBroadcastReady(broadcast);
|
|
1859
|
+
return {
|
|
1860
|
+
name: "weapp-vite-analyze-html",
|
|
1861
|
+
transformIndexHtml(html) {
|
|
1862
|
+
return {
|
|
1863
|
+
html,
|
|
1864
|
+
tags: [
|
|
1865
|
+
{
|
|
1866
|
+
tag: "script",
|
|
1867
|
+
children: `window.${ANALYZE_GLOBAL_KEY} = ${JSON.stringify(state.current)}`,
|
|
1868
|
+
injectTo: "head-prepend"
|
|
1869
|
+
},
|
|
1870
|
+
{
|
|
1871
|
+
tag: "script",
|
|
1872
|
+
children: `window.${PREVIOUS_ANALYZE_GLOBAL_KEY} = ${JSON.stringify(state.previous)}`,
|
|
1873
|
+
injectTo: "head-prepend"
|
|
1874
|
+
},
|
|
1875
|
+
{
|
|
1876
|
+
tag: "script",
|
|
1877
|
+
children: `window.${DASHBOARD_EVENTS_GLOBAL_KEY} = ${JSON.stringify(runtimeEvents.current)}`,
|
|
1878
|
+
injectTo: "head-prepend"
|
|
1879
|
+
},
|
|
1880
|
+
{
|
|
1881
|
+
tag: "script",
|
|
1882
|
+
attrs: {
|
|
1883
|
+
type: "module",
|
|
1884
|
+
src: "/@vite/client"
|
|
1885
|
+
},
|
|
1886
|
+
injectTo: "head"
|
|
1887
|
+
},
|
|
1888
|
+
{
|
|
1889
|
+
tag: "script",
|
|
1890
|
+
attrs: { type: "module" },
|
|
1891
|
+
children: hotBridgeScript,
|
|
1892
|
+
injectTo: "body"
|
|
1893
|
+
}
|
|
1894
|
+
]
|
|
1895
|
+
};
|
|
1896
|
+
},
|
|
1897
|
+
configureServer(server) {
|
|
1898
|
+
onServerInstance(server);
|
|
1899
|
+
server.middlewares.use((req, res, next) => {
|
|
1900
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
1901
|
+
if (url.pathname === FILE_CONTENT_PATH) {
|
|
1902
|
+
sendDashboardFileContent(res, contentRoots, contentAllowlist.current, url.searchParams.get("kind"), url.searchParams.get("path"));
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
if (url.pathname !== ANALYZE_SSE_PATH) {
|
|
1906
|
+
next();
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
res.statusCode = 200;
|
|
1910
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
1911
|
+
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
1912
|
+
res.setHeader("Connection", "keep-alive");
|
|
1913
|
+
res.write(`data: ${JSON.stringify(state)}\n\n`);
|
|
1914
|
+
sseClients.add(res);
|
|
1915
|
+
req.on("close", () => {
|
|
1916
|
+
sseClients.delete(res);
|
|
1917
|
+
});
|
|
1918
|
+
});
|
|
2035
1919
|
}
|
|
1920
|
+
};
|
|
1921
|
+
}
|
|
1922
|
+
async function waitForServerExit(server) {
|
|
1923
|
+
let resolved = false;
|
|
1924
|
+
const cleanup = async () => {
|
|
1925
|
+
if (resolved) return;
|
|
1926
|
+
resolved = true;
|
|
2036
1927
|
try {
|
|
2037
|
-
|
|
1928
|
+
await server.close();
|
|
2038
1929
|
} catch (error) {
|
|
2039
|
-
|
|
2040
|
-
onNonLoginError(error);
|
|
2041
|
-
return;
|
|
2042
|
-
}
|
|
2043
|
-
throw error;
|
|
1930
|
+
logger_default.error(error);
|
|
2044
1931
|
}
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
throw error;
|
|
2058
|
-
}
|
|
2059
|
-
return error;
|
|
2060
|
-
}
|
|
2061
|
-
},
|
|
2062
|
-
isRetryableResult: (result) => result !== null,
|
|
2063
|
-
onCancel: () => {},
|
|
2064
|
-
onRetry: () => {
|
|
2065
|
-
onRetry?.();
|
|
2066
|
-
},
|
|
2067
|
-
promptRetry: async (error) => await promptWechatIdeLoginRetry({
|
|
2068
|
-
cancelLevel,
|
|
2069
|
-
error,
|
|
2070
|
-
logger: logger_default
|
|
2071
|
-
}),
|
|
2072
|
-
shouldRetry: (action) => action === "retry"
|
|
1932
|
+
};
|
|
1933
|
+
const signals = ["SIGINT", "SIGTERM"];
|
|
1934
|
+
await new Promise((resolvePromise) => {
|
|
1935
|
+
const resolveOnce = async () => {
|
|
1936
|
+
await cleanup();
|
|
1937
|
+
signals.forEach((signal) => {
|
|
1938
|
+
process.removeListener(signal, resolveOnce);
|
|
1939
|
+
});
|
|
1940
|
+
resolvePromise();
|
|
1941
|
+
};
|
|
1942
|
+
signals.forEach((signal) => {
|
|
1943
|
+
process.once(signal, resolveOnce);
|
|
2073
1944
|
});
|
|
1945
|
+
server.httpServer?.once("close", resolveOnce);
|
|
2074
1946
|
});
|
|
2075
1947
|
}
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
1948
|
+
async function startAnalyzeDashboard(result, options) {
|
|
1949
|
+
const resolved = resolveDashboardRoot(options);
|
|
1950
|
+
if (!resolved) return;
|
|
1951
|
+
const { root, configFile } = resolved;
|
|
1952
|
+
const state = {
|
|
1953
|
+
current: result,
|
|
1954
|
+
previous: options?.previousResult ?? null
|
|
1955
|
+
};
|
|
1956
|
+
const contentAllowlist = { current: createDashboardContentAllowlist(result) };
|
|
1957
|
+
const runtimeEvents = { current: [createDashboardRuntimeEvent({
|
|
1958
|
+
kind: "command",
|
|
1959
|
+
level: "success",
|
|
1960
|
+
title: options?.watch ? "dashboard watch session started" : "dashboard static session started",
|
|
1961
|
+
detail: options?.watch ? "weapp-vite UI 已进入实时分析模式,后续 analyze 结果会继续推送到 dashboard。" : "weapp-vite UI 已进入静态分析模式,当前页面展示的是一次性分析结果。",
|
|
1962
|
+
tags: options?.watch ? ["watch", "analyze"] : ["static", "analyze"]
|
|
1963
|
+
}), ...(options?.initialEvents ?? []).map((event) => createDashboardRuntimeEvent(event))] };
|
|
1964
|
+
let serverRef;
|
|
1965
|
+
let broadcastAnalyzeResult;
|
|
1966
|
+
const plugins = [createAnalyzeHtmlPlugin(state, runtimeEvents, {
|
|
1967
|
+
artifactRoot: options?.artifactRoot ?? (options?.cwd ? path.resolve(options.cwd, "dist") : void 0),
|
|
1968
|
+
sourceRoot: options?.cwd
|
|
1969
|
+
}, contentAllowlist, (server) => {
|
|
1970
|
+
serverRef = server;
|
|
1971
|
+
}, (broadcast) => {
|
|
1972
|
+
broadcastAnalyzeResult = broadcast;
|
|
1973
|
+
})];
|
|
1974
|
+
const serverOptions = {
|
|
1975
|
+
root,
|
|
1976
|
+
configFile: configFile ?? false,
|
|
1977
|
+
clearScreen: false,
|
|
1978
|
+
appType: "spa",
|
|
1979
|
+
publicDir: false,
|
|
1980
|
+
plugins,
|
|
1981
|
+
server: {
|
|
1982
|
+
host: "127.0.0.1",
|
|
1983
|
+
port: 0,
|
|
1984
|
+
watch: { ignored: ["**/*"] }
|
|
1985
|
+
},
|
|
1986
|
+
logLevel: "error"
|
|
1987
|
+
};
|
|
1988
|
+
const server = await createServer(serverOptions);
|
|
1989
|
+
const requestedPort = typeof serverOptions.server?.port === "number" ? serverOptions.server.port : void 0;
|
|
1990
|
+
await server.listen(requestedPort);
|
|
1991
|
+
serverRef ??= server;
|
|
1992
|
+
server.printUrls();
|
|
1993
|
+
const urls = (() => {
|
|
1994
|
+
const resolved = server.resolvedUrls;
|
|
1995
|
+
if (!resolved) return [];
|
|
1996
|
+
return [...resolved.local ?? [], ...resolved.network ?? []];
|
|
1997
|
+
})();
|
|
1998
|
+
const waitPromise = waitForServerExit(server);
|
|
1999
|
+
if (serverRef?.ws) {
|
|
2000
|
+
serverRef.ws.send({
|
|
2001
|
+
type: "custom",
|
|
2002
|
+
event: "weapp-analyze:update",
|
|
2003
|
+
data: state
|
|
2004
|
+
});
|
|
2005
|
+
serverRef.ws.send({
|
|
2006
|
+
type: "custom",
|
|
2007
|
+
event: DASHBOARD_EVENT_NAME,
|
|
2008
|
+
data: runtimeEvents.current
|
|
2009
|
+
});
|
|
2097
2010
|
}
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
}
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2011
|
+
broadcastAnalyzeResult?.(state);
|
|
2012
|
+
const emitRuntimeEvents = (events) => {
|
|
2013
|
+
if (events.length === 0) return;
|
|
2014
|
+
const nextEvents = events.map((event) => createDashboardRuntimeEvent(event));
|
|
2015
|
+
runtimeEvents.current = [...nextEvents, ...runtimeEvents.current].slice(0, 24);
|
|
2016
|
+
if (serverRef) serverRef.ws.send({
|
|
2017
|
+
type: "custom",
|
|
2018
|
+
event: DASHBOARD_EVENT_NAME,
|
|
2019
|
+
data: nextEvents
|
|
2020
|
+
});
|
|
2021
|
+
};
|
|
2022
|
+
const handle = {
|
|
2023
|
+
async update(nextResult, previousResult) {
|
|
2024
|
+
state.previous = previousResult ?? state.current;
|
|
2025
|
+
state.current = nextResult;
|
|
2026
|
+
contentAllowlist.current = createDashboardContentAllowlist(nextResult);
|
|
2027
|
+
emitRuntimeEvents([{
|
|
2028
|
+
kind: "build",
|
|
2029
|
+
level: "info",
|
|
2030
|
+
title: "analyze payload refreshed",
|
|
2031
|
+
detail: `已推送新的 analyze 结果,当前包含 ${nextResult.packages.length} 个包与 ${nextResult.modules.length} 个模块。`,
|
|
2032
|
+
tags: ["analyze", "refresh"]
|
|
2033
|
+
}]);
|
|
2034
|
+
if (serverRef) serverRef.ws.send({
|
|
2035
|
+
type: "custom",
|
|
2036
|
+
event: "weapp-analyze:update",
|
|
2037
|
+
data: state
|
|
2114
2038
|
});
|
|
2115
|
-
|
|
2116
|
-
}
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
logger_default.info("已回退为进程级关闭微信开发者工具。");
|
|
2129
|
-
return true;
|
|
2039
|
+
broadcastAnalyzeResult?.(state);
|
|
2040
|
+
},
|
|
2041
|
+
emitRuntimeEvents,
|
|
2042
|
+
waitForExit: () => waitPromise,
|
|
2043
|
+
close: async () => {
|
|
2044
|
+
await server.close();
|
|
2045
|
+
},
|
|
2046
|
+
urls
|
|
2047
|
+
};
|
|
2048
|
+
if (options?.watch) {
|
|
2049
|
+
if (!options.silentStartupLog) {
|
|
2050
|
+
logger_default.info("weapp-vite UI 已启动(分析视图,实时模式),按 Ctrl+C 退出。");
|
|
2051
|
+
for (const url of handle.urls) logger_default.info(` ➜ ${colors.bold(colors.cyan(url))}`);
|
|
2130
2052
|
}
|
|
2131
|
-
return
|
|
2053
|
+
return handle;
|
|
2054
|
+
}
|
|
2055
|
+
if (!options?.silentStartupLog) {
|
|
2056
|
+
logger_default.info("weapp-vite UI 已启动(分析视图,静态模式),按 Ctrl+C 退出。");
|
|
2057
|
+
for (const url of handle.urls) logger_default.info(` ➜ ${colors.bold(colors.cyan(url))}`);
|
|
2132
2058
|
}
|
|
2059
|
+
await waitPromise;
|
|
2133
2060
|
}
|
|
2134
2061
|
//#endregion
|
|
2135
|
-
//#region src/cli/
|
|
2136
|
-
function
|
|
2137
|
-
return
|
|
2138
|
-
}
|
|
2139
|
-
async function openWechatIdeByAutomator(projectPath) {
|
|
2140
|
-
(await launchAutomator({
|
|
2141
|
-
projectPath,
|
|
2142
|
-
trustProject: true
|
|
2143
|
-
})).disconnect();
|
|
2144
|
-
}
|
|
2145
|
-
async function connectOpenedProject(projectPath) {
|
|
2146
|
-
try {
|
|
2147
|
-
return await connectOpenedAutomator({
|
|
2148
|
-
projectPath,
|
|
2149
|
-
timeout: 3e3
|
|
2150
|
-
});
|
|
2151
|
-
} catch {
|
|
2152
|
-
return null;
|
|
2153
|
-
}
|
|
2062
|
+
//#region src/cli/commands/analyze.ts
|
|
2063
|
+
function normalizeDisplayPath(value) {
|
|
2064
|
+
return value || ".";
|
|
2154
2065
|
}
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2066
|
+
function getDefaultWebAnalyzeScopes() {
|
|
2067
|
+
return {
|
|
2068
|
+
supported: [
|
|
2069
|
+
"weapp.web 配置解析(enable/root/srcDir/outDir)",
|
|
2070
|
+
"runtime.executionMode 静态解析(compat/safe/strict)",
|
|
2071
|
+
"JSON 报告输出(--json/--output)"
|
|
2072
|
+
],
|
|
2073
|
+
unsupported: [
|
|
2074
|
+
"分包产物体积分析(仅小程序)",
|
|
2075
|
+
"源码模块包体映射(仅小程序)",
|
|
2076
|
+
"分析仪表盘(dashboard)"
|
|
2077
|
+
]
|
|
2166
2078
|
};
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2079
|
+
}
|
|
2080
|
+
function createWebAnalyzeResult(configService, options) {
|
|
2081
|
+
const webConfig = configService.weappWebConfig;
|
|
2082
|
+
const executionMode = webConfig?.pluginOptions.runtime?.executionMode ?? "compat";
|
|
2083
|
+
const scope = getDefaultWebAnalyzeScopes();
|
|
2084
|
+
const limitations = ["当前仅提供静态配置分析,不执行 Web 产物扫描。"];
|
|
2085
|
+
if (!webConfig?.enabled) limitations.push("未检测到启用的 weapp.web 配置。");
|
|
2170
2086
|
return {
|
|
2171
|
-
|
|
2172
|
-
|
|
2087
|
+
runtime: "web",
|
|
2088
|
+
platform: options.platform,
|
|
2089
|
+
mode: configService.mode,
|
|
2090
|
+
generatedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
2091
|
+
experimental: true,
|
|
2092
|
+
configFile: configService.configFilePath ? normalizeDisplayPath(configService.relativeCwd(configService.configFilePath)) : void 0,
|
|
2093
|
+
web: {
|
|
2094
|
+
enabled: Boolean(webConfig?.enabled),
|
|
2095
|
+
root: webConfig?.root ? normalizeDisplayPath(configService.relativeCwd(webConfig.root)) : void 0,
|
|
2096
|
+
srcDir: webConfig?.srcDir,
|
|
2097
|
+
outDir: webConfig?.outDir ? normalizeDisplayPath(configService.relativeCwd(webConfig.outDir)) : void 0,
|
|
2098
|
+
executionMode
|
|
2099
|
+
},
|
|
2100
|
+
supportedScopes: scope.supported,
|
|
2101
|
+
unsupportedScopes: scope.unsupported,
|
|
2102
|
+
limitations
|
|
2173
2103
|
};
|
|
2174
2104
|
}
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
*/
|
|
2178
|
-
|
|
2179
|
-
const
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2105
|
+
function printAnalysisSummary(result) {
|
|
2106
|
+
const packageLabelMap = /* @__PURE__ */ new Map();
|
|
2107
|
+
const packageModuleSet = /* @__PURE__ */ new Map();
|
|
2108
|
+
for (const pkg of result.packages) packageLabelMap.set(pkg.id, pkg.label);
|
|
2109
|
+
for (const module of result.modules) for (const pkgRef of module.packages) {
|
|
2110
|
+
const set = packageModuleSet.get(pkgRef.packageId) ?? /* @__PURE__ */ new Set();
|
|
2111
|
+
set.add(module.id);
|
|
2112
|
+
packageModuleSet.set(pkgRef.packageId, set);
|
|
2113
|
+
}
|
|
2114
|
+
logger_default.success("分包分析完成");
|
|
2115
|
+
for (const pkg of result.packages) {
|
|
2116
|
+
const chunkCount = pkg.files.filter((file) => file.type === "chunk").length;
|
|
2117
|
+
const assetCount = pkg.files.length - chunkCount;
|
|
2118
|
+
const moduleCount = packageModuleSet.get(pkg.id)?.size ?? 0;
|
|
2119
|
+
logger_default.info(`- ${pkg.label}:${chunkCount} 个模块产物,${assetCount} 个资源,覆盖 ${moduleCount} 个源码模块`);
|
|
2120
|
+
}
|
|
2121
|
+
if (result.subPackages.length > 0) {
|
|
2122
|
+
logger_default.info("分包配置:");
|
|
2123
|
+
for (const descriptor of result.subPackages) {
|
|
2124
|
+
const segments = [descriptor.root];
|
|
2125
|
+
if (descriptor.name) segments.push(`别名:${descriptor.name}`);
|
|
2126
|
+
if (descriptor.independent) segments.push("独立构建");
|
|
2127
|
+
logger_default.info(`- ${segments.join(",")}`);
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
const componentUsages = result.components ?? [];
|
|
2131
|
+
if (componentUsages.length > 0) {
|
|
2132
|
+
const suggestions = componentUsages.flatMap((component) => component.suggestions);
|
|
2133
|
+
logger_default.info(`组件依赖:${componentUsages.length} 个组件,${suggestions.length} 条分包优化建议`);
|
|
2134
|
+
for (const suggestion of suggestions.slice(0, 5)) logger_default.info(`- ${suggestion.message}`);
|
|
2135
|
+
if (suggestions.length > 5) logger_default.info(`- …其余 ${suggestions.length - 5} 条组件建议请使用 ${colors.bold(colors.green("weapp-vite analyze --json"))} 查看`);
|
|
2136
|
+
}
|
|
2137
|
+
const duplicates = result.modules.filter((module) => module.packages.length > 1);
|
|
2138
|
+
if (duplicates.length === 0) {
|
|
2139
|
+
logger_default.info("未检测到跨包复用的源码模块。");
|
|
2140
|
+
return;
|
|
2141
|
+
}
|
|
2142
|
+
logger_default.info(`跨包复用/复制源码共 ${duplicates.length} 项:`);
|
|
2143
|
+
const limit = 10;
|
|
2144
|
+
const entries = duplicates.slice(0, limit);
|
|
2145
|
+
for (const module of entries) {
|
|
2146
|
+
const placements = module.packages.map((pkgRef) => {
|
|
2147
|
+
return `${packageLabelMap.get(pkgRef.packageId) ?? pkgRef.packageId} → ${pkgRef.files.join(", ")}`;
|
|
2148
|
+
}).join(";");
|
|
2149
|
+
logger_default.info(`- ${module.source} (${module.sourceType}):${placements}`);
|
|
2150
|
+
}
|
|
2151
|
+
if (duplicates.length > limit) logger_default.info(`- …其余 ${duplicates.length - limit} 项请使用 ${colors.bold(colors.green("weapp-vite analyze --json"))} 查看`);
|
|
2152
|
+
}
|
|
2153
|
+
function printBudgetCheckSummary(result) {
|
|
2154
|
+
const exceededItems = createAnalyzeBudgetCheck(result).filter((item) => item.status === "exceeded");
|
|
2155
|
+
if (exceededItems.length === 0) {
|
|
2156
|
+
logger_default.success("包体预算检查通过");
|
|
2157
|
+
return false;
|
|
2158
|
+
}
|
|
2159
|
+
logger_default.error(`包体预算检查失败:${exceededItems.length} 项超限`);
|
|
2160
|
+
for (const item of exceededItems) logger_default.error(`- ${item.label}:${formatAnalyzeBytes(item.currentBytes)} / ${formatAnalyzeBytes(item.limitBytes)} (${(item.ratio * 100).toFixed(1)}%)`);
|
|
2185
2161
|
return true;
|
|
2186
2162
|
}
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2163
|
+
function printWebAnalysisSummary(result) {
|
|
2164
|
+
logger_default.success("Web 静态分析完成");
|
|
2165
|
+
logger_default.info(`- 配置状态:${result.web.enabled ? "已启用 weapp.web" : "未启用 weapp.web"}`);
|
|
2166
|
+
if (result.web.enabled) {
|
|
2167
|
+
logger_default.info(`- root:${result.web.root ?? "."}`);
|
|
2168
|
+
logger_default.info(`- srcDir:${result.web.srcDir ?? "."}`);
|
|
2169
|
+
logger_default.info(`- outDir:${result.web.outDir ?? "dist/web"}`);
|
|
2170
|
+
}
|
|
2171
|
+
logger_default.info(`- executionMode:${result.web.executionMode}`);
|
|
2172
|
+
logger_default.info(`- 支持范围:${result.supportedScopes.join(";")}`);
|
|
2173
|
+
logger_default.warn(`- 未支持范围:${result.unsupportedScopes.join(";")}`);
|
|
2174
|
+
for (const limitation of result.limitations) logger_default.warn(`- 限制:${limitation}`);
|
|
2192
2175
|
}
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
await
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2176
|
+
async function writeAnalyzeResult(result, outputOption, configService, format = "json", previousResult) {
|
|
2177
|
+
if (!outputOption) return;
|
|
2178
|
+
const baseDir = configService.cwd;
|
|
2179
|
+
const resolvedOutputPath = path.isAbsolute(outputOption) ? outputOption : path.resolve(baseDir, outputOption);
|
|
2180
|
+
await fs.ensureDir(path.dirname(resolvedOutputPath));
|
|
2181
|
+
const content = format === "markdown" && "packages" in result ? createAnalyzeMarkdownReport(result, previousResult) : format === "pr" && "packages" in result ? createAnalyzePrMarkdownReport(result, previousResult) : JSON.stringify(result, null, 2);
|
|
2182
|
+
await fs.writeFile(resolvedOutputPath, `${content}\n`, "utf8");
|
|
2183
|
+
const relativeOutput = configService.relativeCwd(resolvedOutputPath);
|
|
2184
|
+
logger_default.success(`分析结果已写入 ${colors.green(relativeOutput)}`);
|
|
2185
|
+
return resolvedOutputPath;
|
|
2186
|
+
}
|
|
2187
|
+
function formatMetricSummary(label, metric) {
|
|
2188
|
+
if (!metric.count || metric.averageMs === void 0 || metric.maxMs === void 0) return;
|
|
2189
|
+
return `${label} avg ${metric.averageMs.toFixed(2)} ms,max ${metric.maxMs.toFixed(2)} ms`;
|
|
2190
|
+
}
|
|
2191
|
+
function formatCountItems(items, limit = 5) {
|
|
2192
|
+
return items.slice(0, limit).map((item) => `${item.name} x${item.count}`).join(",");
|
|
2193
|
+
}
|
|
2194
|
+
function printHmrProfileAnalysisSummary(result, configService) {
|
|
2195
|
+
logger_default.success("HMR profile 分析完成");
|
|
2196
|
+
logger_default.info(`- profile:${colors.green(configService.relativeCwd(result.profilePath))}`);
|
|
2197
|
+
logger_default.info(`- 样本:${result.sampleCount} 条`);
|
|
2198
|
+
if (result.firstTimestamp && result.lastTimestamp) logger_default.info(`- 时间范围:${result.firstTimestamp} -> ${result.lastTimestamp}`);
|
|
2199
|
+
const totalSummary = formatMetricSummary("total", result.metrics.totalMs);
|
|
2200
|
+
const watchSummary = formatMetricSummary("watch->dirty", result.metrics.watchToDirtyMs);
|
|
2201
|
+
const emitSummary = formatMetricSummary("emit", result.metrics.emitMs);
|
|
2202
|
+
const sharedSummary = formatMetricSummary("shared", result.metrics.sharedChunkResolveMs);
|
|
2203
|
+
for (const summary of [
|
|
2204
|
+
totalSummary,
|
|
2205
|
+
watchSummary,
|
|
2206
|
+
emitSummary,
|
|
2207
|
+
sharedSummary
|
|
2208
|
+
]) if (summary) logger_default.info(`- ${summary}`);
|
|
2209
|
+
if (result.events.length) logger_default.info(`- 事件分布:${formatCountItems(result.events)}`);
|
|
2210
|
+
if (result.dirtyReasons.length) logger_default.info(`- 主要 dirty 原因:${formatCountItems(result.dirtyReasons)}`);
|
|
2211
|
+
if (result.pendingReasons.length) logger_default.info(`- 主要 pending 原因:${formatCountItems(result.pendingReasons)}`);
|
|
2212
|
+
if (result.skippedLineCount > 0) logger_default.warn(`- 跳过 ${result.skippedLineCount} 条无法解析的 profile 记录`);
|
|
2213
|
+
if (result.slowestSamples.length) {
|
|
2214
|
+
logger_default.info("- 最慢样本:");
|
|
2215
|
+
for (const sample of result.slowestSamples.slice(0, 3)) {
|
|
2216
|
+
const fileLabel = sample.file ? configService.relativeCwd(sample.file) : "(unknown)";
|
|
2217
|
+
logger_default.info(` - ${sample.totalMs?.toFixed(2) ?? "0.00"} ms,${sample.event ?? "unknown"},${fileLabel}`);
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
function registerAnalyzeCommand(cli) {
|
|
2222
|
+
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 | web)`).option("--project-config <path>", `[string] project config path (miniprogram only)`).action(async (root, options) => {
|
|
2223
|
+
filterDuplicateOptions(options);
|
|
2224
|
+
const configFile = resolveConfigFile(options);
|
|
2225
|
+
const outputJson = coerceBooleanOption(options.json);
|
|
2226
|
+
const outputMarkdown = coerceBooleanOption(options.markdown);
|
|
2227
|
+
const reportType = typeof options.report === "string" ? options.report.trim() : "";
|
|
2228
|
+
const outputPrReport = reportType === "pr";
|
|
2229
|
+
if (reportType && !outputPrReport) throw new Error(`不支持的 analyze report 类型:${reportType}`);
|
|
2230
|
+
const budgetCheck = coerceBooleanOption(options.budgetCheck);
|
|
2231
|
+
const targets = resolveRuntimeTargets(options);
|
|
2232
|
+
const inlineConfig = createInlineConfig(targets.platform);
|
|
2233
|
+
try {
|
|
2234
|
+
const ctx = await createCompilerContext({
|
|
2235
|
+
cwd: root,
|
|
2236
|
+
mode: options.mode ?? "production",
|
|
2237
|
+
configFile,
|
|
2238
|
+
inlineConfig,
|
|
2239
|
+
cliPlatform: targets.rawPlatform,
|
|
2240
|
+
projectConfigPath: options.projectConfig
|
|
2241
|
+
});
|
|
2242
|
+
logRuntimeTarget(targets, {
|
|
2243
|
+
silent: outputJson || outputMarkdown,
|
|
2244
|
+
resolvedConfigPlatform: ctx.configService.platform
|
|
2245
|
+
});
|
|
2246
|
+
const outputOption = typeof options.output === "string" ? options.output.trim() : "";
|
|
2247
|
+
if (options.hmrProfile !== void 0 && options.hmrProfile !== false) {
|
|
2248
|
+
const profileOption = typeof options.hmrProfile === "string" && options.hmrProfile.trim() ? options.hmrProfile.trim() : ctx.configService.weappViteConfig.hmr?.profileJson;
|
|
2249
|
+
const profilePath = resolveHmrProfileJsonPath({
|
|
2250
|
+
cwd: ctx.configService.cwd,
|
|
2251
|
+
option: profileOption,
|
|
2252
|
+
fallbackToDefault: true
|
|
2253
|
+
});
|
|
2254
|
+
if (!profilePath) throw new Error("未找到可用的 HMR profile 文件路径");
|
|
2255
|
+
const hmrProfileResult = await analyzeHmrProfile({ profilePath });
|
|
2256
|
+
const writtenPath = await writeAnalyzeResult(hmrProfileResult, outputOption, ctx.configService);
|
|
2257
|
+
if (outputJson) {
|
|
2258
|
+
if (!writtenPath) process.stdout.write(`${JSON.stringify(hmrProfileResult, null, 2)}\n`);
|
|
2259
|
+
} else printHmrProfileAnalysisSummary(hmrProfileResult, ctx.configService);
|
|
2260
|
+
return;
|
|
2261
|
+
}
|
|
2262
|
+
if (targets.runWeb) {
|
|
2263
|
+
const webResult = createWebAnalyzeResult(ctx.configService, { platform: "web" });
|
|
2264
|
+
const writtenPath = await writeAnalyzeResult(webResult, outputOption, ctx.configService);
|
|
2265
|
+
if (outputJson) {
|
|
2266
|
+
if (!writtenPath) process.stdout.write(`${JSON.stringify(webResult, null, 2)}\n`);
|
|
2267
|
+
} else printWebAnalysisSummary(webResult);
|
|
2268
|
+
return;
|
|
2269
|
+
}
|
|
2270
|
+
if (!targets.runMini) {
|
|
2271
|
+
logger_default.warn("当前命令不支持该平台,请通过 --platform weapp 或 --platform web 指定目标。");
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
const previousResult = await readLatestAnalyzeHistorySnapshot(ctx.configService);
|
|
2275
|
+
const result = await analyzeSubpackages(ctx);
|
|
2276
|
+
await writeAnalyzeHistorySnapshot(result, ctx.configService);
|
|
2277
|
+
const writtenPath = await writeAnalyzeResult(result, outputOption, ctx.configService, outputPrReport ? "pr" : outputMarkdown ? "markdown" : "json", previousResult);
|
|
2278
|
+
if (outputPrReport) {
|
|
2279
|
+
if (!writtenPath) process.stdout.write(`${createAnalyzePrMarkdownReport(result, previousResult)}\n`);
|
|
2280
|
+
} else if (outputMarkdown) {
|
|
2281
|
+
if (!writtenPath) process.stdout.write(`${createAnalyzeMarkdownReport(result, previousResult)}\n`);
|
|
2282
|
+
} else if (outputJson) {
|
|
2283
|
+
if (!writtenPath) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
2284
|
+
}
|
|
2285
|
+
if (budgetCheck ? printBudgetCheckSummary(result) : false) process.exitCode = 1;
|
|
2286
|
+
if (budgetCheck) return;
|
|
2287
|
+
if (!outputPrReport && !outputMarkdown && !outputJson) {
|
|
2288
|
+
printAnalysisSummary(result);
|
|
2289
|
+
await startAnalyzeDashboard(result, {
|
|
2290
|
+
artifactRoot: ctx.configService.outDir,
|
|
2291
|
+
cwd: ctx.configService.cwd,
|
|
2292
|
+
packageManagerAgent: ctx.configService.packageManager.agent,
|
|
2293
|
+
previousResult
|
|
2294
|
+
});
|
|
2295
|
+
}
|
|
2296
|
+
} catch (error) {
|
|
2297
|
+
logger_default.error(error);
|
|
2298
|
+
process.exitCode = 1;
|
|
2202
2299
|
}
|
|
2203
2300
|
});
|
|
2204
2301
|
}
|
|
2302
|
+
//#endregion
|
|
2303
|
+
//#region src/cli/formatDuration.ts
|
|
2205
2304
|
/**
|
|
2206
|
-
*
|
|
2207
|
-
*/
|
|
2208
|
-
function resolveIdeProjectPath(mpDistRoot) {
|
|
2209
|
-
if (!mpDistRoot || !mpDistRoot.trim()) return;
|
|
2210
|
-
const parent = path.dirname(mpDistRoot);
|
|
2211
|
-
if (!parent || parent === "." || parent === "/") return;
|
|
2212
|
-
return parent;
|
|
2213
|
-
}
|
|
2214
|
-
/**
|
|
2215
|
-
* @description 结合 mpDistRoot 与配置根目录解析最终 IDE 项目目录。
|
|
2305
|
+
* 将毫秒耗时格式化为适合 CLI 展示的文本。
|
|
2216
2306
|
*/
|
|
2217
|
-
function
|
|
2218
|
-
return
|
|
2307
|
+
function formatDuration(durationMs) {
|
|
2308
|
+
return `${durationMs}ms`;
|
|
2219
2309
|
}
|
|
2220
|
-
|
|
2221
|
-
|
|
2310
|
+
//#endregion
|
|
2311
|
+
//#region src/cli/logBuildAppFinish.ts
|
|
2312
|
+
let logBuildAppFinishOnlyShowOnce = false;
|
|
2313
|
+
function collectServerUrls(webServer) {
|
|
2314
|
+
const urls = webServer?.resolvedUrls;
|
|
2315
|
+
if (!urls) return [];
|
|
2316
|
+
return [...urls.local ?? [], ...urls.network ?? []];
|
|
2222
2317
|
}
|
|
2223
|
-
|
|
2224
|
-
if (
|
|
2225
|
-
|
|
2318
|
+
function logBuildAppFinish(configService, webServer, options = {}) {
|
|
2319
|
+
if (logBuildAppFinishOnlyShowOnce) return;
|
|
2320
|
+
const { skipMini = false, skipWeb = false, uiUrls = [] } = options;
|
|
2321
|
+
const webUrls = skipWeb ? [] : collectServerUrls(webServer);
|
|
2322
|
+
if (skipMini) {
|
|
2323
|
+
logger_default.success("开发服务已就绪:");
|
|
2324
|
+
if (webUrls.length > 0) logger_default.info(`Web:${colors.cyan(webUrls[0])}`);
|
|
2325
|
+
else logger_default.info("Web:已启动");
|
|
2326
|
+
logBuildAppFinishOnlyShowOnce = true;
|
|
2327
|
+
return;
|
|
2226
2328
|
}
|
|
2227
|
-
const
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2329
|
+
const { command, args } = resolveCommand(configService.packageManager.agent, "run", ["open"]) ?? {
|
|
2330
|
+
command: "npm",
|
|
2331
|
+
args: ["run", "open"]
|
|
2332
|
+
};
|
|
2333
|
+
const devCommand = `${command} ${args.join(" ")}`;
|
|
2334
|
+
logger_default.success("开发服务已就绪:");
|
|
2335
|
+
logger_default.info(`小程序:执行 ${colors.bold(colors.green(devCommand))},或手动导入 ${colors.green(getProjectConfigFileName(configService.platform))}`);
|
|
2336
|
+
if (uiUrls.length > 0) logger_default.info(`UI:${colors.cyan(uiUrls[0])}`);
|
|
2337
|
+
else if (!skipMini) logger_default.info("UI:未启用");
|
|
2338
|
+
if (webUrls.length > 0) logger_default.info(`Web:${colors.cyan(webUrls[0])}`);
|
|
2339
|
+
const projectConfigFileName = getProjectConfigFileName(configService.platform);
|
|
2340
|
+
if (!uiUrls.length && !webUrls.length) logger_default.info(`提示:手动打开对应平台开发者工具,导入根目录(${colors.green(projectConfigFileName)} 文件所在目录)`);
|
|
2341
|
+
logBuildAppFinishOnlyShowOnce = true;
|
|
2231
2342
|
}
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
async function stabilizeOpenedWechatIdeProject(projectPath, servicePortEnabled) {
|
|
2236
|
-
if (servicePortEnabled === false) return;
|
|
2237
|
-
try {
|
|
2238
|
-
await executeWechatIdeCliCommand(["compile"], {
|
|
2239
|
-
httpMode: "prefer",
|
|
2240
|
-
onNonLoginError: (error) => logger_default.error(error),
|
|
2241
|
-
projectPath
|
|
2242
|
-
});
|
|
2243
|
-
await executeWechatIdeCliCommand([
|
|
2244
|
-
"reset-fileutils",
|
|
2245
|
-
"-p",
|
|
2246
|
-
projectPath
|
|
2247
|
-
], {
|
|
2248
|
-
httpMode: "prefer",
|
|
2249
|
-
onNonLoginError: (error) => logger_default.error(error),
|
|
2250
|
-
projectPath
|
|
2251
|
-
});
|
|
2252
|
-
await executeWechatIdeCliCommand([
|
|
2253
|
-
"engine",
|
|
2254
|
-
"build",
|
|
2255
|
-
projectPath
|
|
2256
|
-
], {
|
|
2257
|
-
httpMode: "prefer",
|
|
2258
|
-
onNonLoginError: (error) => logger_default.error(error),
|
|
2259
|
-
projectPath
|
|
2260
|
-
});
|
|
2261
|
-
try {
|
|
2262
|
-
await executeWechatIdeCliCommand(["compile"], {
|
|
2263
|
-
automatorMode: "require",
|
|
2264
|
-
httpMode: "skip",
|
|
2265
|
-
projectPath
|
|
2266
|
-
});
|
|
2267
|
-
} catch (error) {
|
|
2268
|
-
if (shouldLogAutomatorFallbackError()) logger_default.error(error);
|
|
2269
|
-
}
|
|
2270
|
-
} catch (error) {
|
|
2271
|
-
logger_default.warn("刷新微信开发者工具项目索引失败,已保留当前打开状态;如模拟器仍显示旧状态,可手动刷新一次。");
|
|
2272
|
-
if (shouldLogAutomatorFallbackError()) logger_default.error(error);
|
|
2273
|
-
}
|
|
2343
|
+
const WINDOWS_SEPARATOR_RE = /\\/g;
|
|
2344
|
+
function normalizeFileName(fileName) {
|
|
2345
|
+
return fileName.replace(WINDOWS_SEPARATOR_RE, "/");
|
|
2274
2346
|
}
|
|
2275
|
-
function
|
|
2276
|
-
|
|
2277
|
-
if (
|
|
2278
|
-
if (
|
|
2279
|
-
|
|
2280
|
-
return argv;
|
|
2347
|
+
function getOutputItemBytes(item) {
|
|
2348
|
+
if (item.type === "chunk") return typeof item.code === "string" ? Buffer.byteLength(item.code, "utf8") : 0;
|
|
2349
|
+
if (typeof item.source === "string") return Buffer.byteLength(item.source, "utf8");
|
|
2350
|
+
if (item.source instanceof Uint8Array) return item.source.byteLength;
|
|
2351
|
+
return 0;
|
|
2281
2352
|
}
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2353
|
+
function resolveSubPackageRoot(fileName, roots) {
|
|
2354
|
+
const normalized = normalizeFileName(fileName);
|
|
2355
|
+
return roots.find((root) => normalized === root || normalized.startsWith(`${root}/`));
|
|
2356
|
+
}
|
|
2357
|
+
function collectPackageSizeReports(output, subPackageMap) {
|
|
2358
|
+
const outputs = Array.isArray(output) ? output : [output];
|
|
2359
|
+
const roots = [...subPackageMap?.keys() ?? []].filter(Boolean).sort((a, b) => b.length - a.length || a.localeCompare(b));
|
|
2360
|
+
const packageBytes = new Map([["__main__", 0]]);
|
|
2361
|
+
for (const root of roots) packageBytes.set(root, 0);
|
|
2362
|
+
for (const current of outputs) for (const item of current.output ?? []) {
|
|
2363
|
+
const root = resolveSubPackageRoot(item.fileName, roots) ?? "__main__";
|
|
2364
|
+
packageBytes.set(root, (packageBytes.get(root) ?? 0) + getOutputItemBytes(item));
|
|
2292
2365
|
}
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2366
|
+
const reports = [{
|
|
2367
|
+
root: "__main__",
|
|
2368
|
+
label: "主包",
|
|
2369
|
+
bytes: packageBytes.get("__main__") ?? 0
|
|
2370
|
+
}];
|
|
2371
|
+
for (const root of roots) {
|
|
2372
|
+
const meta = subPackageMap?.get(root);
|
|
2373
|
+
const isIndependent = Boolean(meta?.subPackage.independent);
|
|
2374
|
+
reports.push({
|
|
2375
|
+
root,
|
|
2376
|
+
label: `${isIndependent ? "独立分包" : "分包"} ${root}`,
|
|
2377
|
+
bytes: packageBytes.get(root) ?? 0
|
|
2378
|
+
});
|
|
2306
2379
|
}
|
|
2307
|
-
|
|
2308
|
-
if (platform === "weapp" && projectPath) await stabilizeOpenedWechatIdeProject(projectPath, bootstrapResult?.servicePortEnabled);
|
|
2380
|
+
return reports;
|
|
2309
2381
|
}
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
const
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
cwd,
|
|
2320
|
-
mode: options.mode ?? "development",
|
|
2321
|
-
configFile: options.configFile,
|
|
2322
|
-
inlineConfig: createInlineConfig(platform),
|
|
2323
|
-
cliPlatform: options.cliPlatform
|
|
2324
|
-
});
|
|
2325
|
-
platform ??= ctx.configService.platform;
|
|
2326
|
-
if (!projectPath) projectPath = resolveIdeProjectRoot(ctx.configService.mpDistRoot, ctx.configService.cwd);
|
|
2327
|
-
return {
|
|
2328
|
-
cwd: ctx.configService.cwd,
|
|
2329
|
-
platform,
|
|
2330
|
-
projectPath,
|
|
2331
|
-
weappViteConfig: ctx.configService.weappViteConfig,
|
|
2332
|
-
mpDistRoot: ctx.configService.mpDistRoot
|
|
2333
|
-
};
|
|
2334
|
-
} catch {}
|
|
2335
|
-
if (!projectPath) {
|
|
2336
|
-
const defaultProjectRoot = getDefaultIdeProjectRoot(platform);
|
|
2337
|
-
if (defaultProjectRoot) projectPath = resolveIdeProjectRoot(defaultProjectRoot, cwd);
|
|
2382
|
+
function logBuildPackageSizeReport(options) {
|
|
2383
|
+
const warningBytes = Number(options.warningBytes ?? 2097152);
|
|
2384
|
+
const reports = collectPackageSizeReports(options.output, options.subPackageMap);
|
|
2385
|
+
logger_default.success("主包/分包体积报告:");
|
|
2386
|
+
for (const report of reports) logger_default.info(`${report.label}:${formatBytes(report.bytes)}`);
|
|
2387
|
+
if (!(Number.isFinite(warningBytes) && warningBytes > 0)) return;
|
|
2388
|
+
for (const report of reports) {
|
|
2389
|
+
if (report.bytes <= warningBytes) continue;
|
|
2390
|
+
logger_default.warn(`[包体积] ${colors.yellow(report.label)} 体积 ${colors.yellow(formatBytes(report.bytes))},已超过阈值 ${colors.yellow(formatBytes(warningBytes))}。`);
|
|
2338
2391
|
}
|
|
2339
|
-
return {
|
|
2340
|
-
cwd,
|
|
2341
|
-
platform,
|
|
2342
|
-
projectPath
|
|
2343
|
-
};
|
|
2344
2392
|
}
|
|
2345
2393
|
//#endregion
|
|
2346
2394
|
//#region src/cli/commands/build.ts
|
|
@@ -2359,11 +2407,11 @@ function emitDashboardEvents$1(handle, events) {
|
|
|
2359
2407
|
handle?.emitRuntimeEvents(events);
|
|
2360
2408
|
}
|
|
2361
2409
|
function registerBuildCommand(cli) {
|
|
2362
|
-
cli.command("build [root]", "build for production").option("--target <target>", `[string] transpile target (default: 'modules')`).option("--outDir <dir>", `[string] output directory (default: dist)`).option("-p, --platform <platform>", `[string] target platform (weapp |
|
|
2410
|
+
cli.command("build [root]", "build for production").option("--target <target>", `[string] transpile target (default: 'modules')`).option("--outDir <dir>", `[string] output directory (default: dist)`).option("-p, --platform <platform>", `[string] target platform (weapp | web | all)`).option("--project-config <path>", `[string] project config path (miniprogram only)`).option("--sourcemap [output]", `[boolean | "inline" | "hidden"] output source maps for build (default: false)`).option("--minify [minifier]", "[boolean | \"terser\" | \"esbuild\"] enable/disable minification, or specify minifier to use (default: esbuild)").option("--emptyOutDir", `[boolean] force empty outDir when it's outside of root`).option("-w, --watch", `[boolean] rebuilds when modules have changed on disk`).option("--skipNpm", `[boolean] if skip npm build`).option("-o, --open", `[boolean] open ide`).option("--trust-project", "[boolean] auto trust Wechat DevTools project on open", { default: true }).option("--ui", `[boolean] 启动调试 UI(当前提供分析视图)`, { default: false }).option("--analyze", `[boolean] 输出分包分析仪表盘`, { default: false }).action(async (root, options) => {
|
|
2363
2411
|
filterDuplicateOptions(options);
|
|
2364
2412
|
const configFile = resolveConfigFile(options);
|
|
2365
2413
|
const targets = resolveRuntimeTargets(options);
|
|
2366
|
-
const inlineConfig = createInlineConfig(targets.
|
|
2414
|
+
const inlineConfig = createInlineConfig(targets.platform);
|
|
2367
2415
|
const ctx = await createCompilerContext({
|
|
2368
2416
|
cwd: root,
|
|
2369
2417
|
mode: options.mode ?? "production",
|
|
@@ -2887,7 +2935,7 @@ async function runIdeCommand(action, root, options) {
|
|
|
2887
2935
|
const resolved = await resolveIdeCommandContext({
|
|
2888
2936
|
configFile,
|
|
2889
2937
|
mode: options.mode ?? "development",
|
|
2890
|
-
platform: targets.
|
|
2938
|
+
platform: targets.platform,
|
|
2891
2939
|
projectPath: root,
|
|
2892
2940
|
cliPlatform: targets.rawPlatform
|
|
2893
2941
|
});
|
|
@@ -2960,7 +3008,7 @@ async function runIdeCommand(action, root, options) {
|
|
|
2960
3008
|
* @description 注册 IDE 相关子命令。
|
|
2961
3009
|
*/
|
|
2962
3010
|
function registerIdeCommand(cli) {
|
|
2963
|
-
cli.command("ide [action] [root]", "run Wechat DevTools utility actions and log bridge commands").option("-o, --open", "[boolean] open ide before attaching log bridge").option("-p, --platform <platform>", "[string] target platform (weapp |
|
|
3011
|
+
cli.command("ide [action] [root]", "run Wechat DevTools utility actions and log bridge commands").option("-o, --open", "[boolean] open ide before attaching log bridge").option("-p, --platform <platform>", "[string] target platform (weapp | web)").option("--project-config <path>", "[string] project config path (miniprogram only)").option("--ticket <value>", "[string] ticket used by `ide ticket:set`").option("--trust-project", "[boolean] auto trust Wechat DevTools project on open", { default: true }).action(async (action, root, options) => {
|
|
2964
3012
|
await runIdeCommand(action, root, options);
|
|
2965
3013
|
});
|
|
2966
3014
|
}
|
|
@@ -3387,14 +3435,14 @@ function registerNpmCommand(cli) {
|
|
|
3387
3435
|
//#endregion
|
|
3388
3436
|
//#region src/cli/commands/open.ts
|
|
3389
3437
|
function registerOpenCommand(cli) {
|
|
3390
|
-
cli.command("open [root]").option("-p, --platform <platform>", `[string] target platform (weapp |
|
|
3438
|
+
cli.command("open [root]").option("-p, --platform <platform>", `[string] target platform (weapp | web)`).option("--trust-project", "[boolean] auto trust Wechat DevTools project on open", { default: true }).action(async (root, options) => {
|
|
3391
3439
|
filterDuplicateOptions(options);
|
|
3392
3440
|
const configFile = resolveConfigFile(options);
|
|
3393
3441
|
const targets = resolveRuntimeTargets(options);
|
|
3394
3442
|
const { cwd, platform, projectPath, mpDistRoot, weappViteConfig } = await resolveIdeCommandContext({
|
|
3395
3443
|
configFile,
|
|
3396
3444
|
mode: options.mode ?? "development",
|
|
3397
|
-
platform: targets.
|
|
3445
|
+
platform: targets.platform,
|
|
3398
3446
|
projectPath: root,
|
|
3399
3447
|
cliPlatform: targets.rawPlatform
|
|
3400
3448
|
});
|
|
@@ -3421,7 +3469,7 @@ function resolvePreparePlatform(options) {
|
|
|
3421
3469
|
return typeof options.platform === "string" ? options.platform : typeof options.p === "string" ? options.p : void 0;
|
|
3422
3470
|
}
|
|
3423
3471
|
function registerPrepareCommand(cli) {
|
|
3424
|
-
cli.command("prepare [...input]", "generate .weapp-vite support files").option("-p, --platform <platform>", `[string] target platform (weapp |
|
|
3472
|
+
cli.command("prepare [...input]", "generate .weapp-vite support files").option("-p, --platform <platform>", `[string] target platform (weapp | web)`).action(async (input, options) => {
|
|
3425
3473
|
try {
|
|
3426
3474
|
filterDuplicateOptions(options);
|
|
3427
3475
|
const cwd = path.resolve(resolvePrepareRoot(input));
|
|
@@ -3653,7 +3701,7 @@ function resolveRunnableHotkeyDefinition(input) {
|
|
|
3653
3701
|
}
|
|
3654
3702
|
//#endregion
|
|
3655
3703
|
//#region package.json
|
|
3656
|
-
var version = "6.16.
|
|
3704
|
+
var version = "6.16.5";
|
|
3657
3705
|
//#endregion
|
|
3658
3706
|
//#region src/cli/devHotkeys/format.ts
|
|
3659
3707
|
const FULLWIDTH_ASCII_START = 65281;
|
|
@@ -3953,6 +4001,27 @@ const REG_DIST_POSIX_SEP = /\\/g;
|
|
|
3953
4001
|
function emitDashboardEvents(handle, events) {
|
|
3954
4002
|
handle?.emitRuntimeEvents(events);
|
|
3955
4003
|
}
|
|
4004
|
+
function formatHmrProfileFile(profile) {
|
|
4005
|
+
return profile.sourceRootFile ?? profile.relativeFile ?? profile.file ?? "未知文件";
|
|
4006
|
+
}
|
|
4007
|
+
function createHmrProfileEvent(profile) {
|
|
4008
|
+
if (!profile || typeof profile.totalMs !== "number") return;
|
|
4009
|
+
const file = formatHmrProfileFile(profile);
|
|
4010
|
+
const counts = [
|
|
4011
|
+
typeof profile.dirtyCount === "number" ? `dirty ${profile.dirtyCount}` : void 0,
|
|
4012
|
+
typeof profile.pendingCount === "number" ? `pending ${profile.pendingCount}` : void 0,
|
|
4013
|
+
typeof profile.emittedCount === "number" ? `emitted ${profile.emittedCount}` : void 0
|
|
4014
|
+
].filter(Boolean).join(" / ");
|
|
4015
|
+
return {
|
|
4016
|
+
kind: "hmr",
|
|
4017
|
+
level: profile.totalMs >= 1e3 ? "warning" : "success",
|
|
4018
|
+
title: "mini hmr rebuild completed",
|
|
4019
|
+
detail: counts ? `${file} 已完成热更新重建(${counts})。` : `${file} 已完成热更新重建。`,
|
|
4020
|
+
durationMs: profile.totalMs,
|
|
4021
|
+
tags: ["hmr", "rebuild"],
|
|
4022
|
+
profile
|
|
4023
|
+
};
|
|
4024
|
+
}
|
|
3956
4025
|
function hasAnalyzeData(result) {
|
|
3957
4026
|
return result.packages.length > 0 || result.modules.length > 0;
|
|
3958
4027
|
}
|
|
@@ -4053,7 +4122,7 @@ function createAnalyzeController(options) {
|
|
|
4053
4122
|
mode: configService.mode,
|
|
4054
4123
|
isDev: false,
|
|
4055
4124
|
configFile,
|
|
4056
|
-
inlineConfig: createInlineConfig(targets.
|
|
4125
|
+
inlineConfig: createInlineConfig(targets.platform),
|
|
4057
4126
|
cliPlatform: targets.rawPlatform,
|
|
4058
4127
|
projectConfigPath: cliOptions.projectConfig,
|
|
4059
4128
|
syncSupportFiles: false
|
|
@@ -4122,6 +4191,8 @@ function createAnalyzeController(options) {
|
|
|
4122
4191
|
let updating = false;
|
|
4123
4192
|
if (analyzeHandle && buildResult && typeof buildResult.on === "function") buildResult.on("event", (event) => {
|
|
4124
4193
|
if (event.code !== "END" || updating) return;
|
|
4194
|
+
const hmrEvent = createHmrProfileEvent(ctx.runtimeState.build.hmr.recentProfiles.at(-1));
|
|
4195
|
+
if (hmrEvent) emitDashboardEvents(analyzeHandle, [hmrEvent]);
|
|
4125
4196
|
updating = true;
|
|
4126
4197
|
triggerAnalyzeUpdate("watch").finally(() => {
|
|
4127
4198
|
updating = false;
|
|
@@ -4229,11 +4300,11 @@ function waitForServeShutdownSignal() {
|
|
|
4229
4300
|
//#endregion
|
|
4230
4301
|
//#region src/cli/commands/serve/index.ts
|
|
4231
4302
|
function registerServeCommand(cli) {
|
|
4232
|
-
cli.command("[root]", "start dev server").alias("serve").alias("dev").option("--skipNpm", `[boolean] if skip npm build`).option("-o, --open", `[boolean] open ide`).option("-p, --platform <platform>", `[string] target platform (weapp |
|
|
4303
|
+
cli.command("[root]", "start dev server").alias("serve").alias("dev").option("--skipNpm", `[boolean] if skip npm build`).option("-o, --open", `[boolean] open ide`).option("-p, --platform <platform>", `[string] target platform (weapp | web | all)`).option("--project-config <path>", `[string] project config path (miniprogram only)`).option("--trust-project", "[boolean] auto trust Wechat DevTools project on open", { default: true }).option("--host [host]", `[string] web dev server host`).option("--ui", `[boolean] 启动调试 UI(当前提供分析视图)`, { default: false }).option("--analyze", `[boolean] 启动分包分析仪表盘 (实验特性)`, { default: false }).action(async (root, options) => {
|
|
4233
4304
|
filterDuplicateOptions(options);
|
|
4234
4305
|
const configFile = resolveConfigFile(options);
|
|
4235
4306
|
const targets = resolveRuntimeTargets(options);
|
|
4236
|
-
let inlineConfig = createInlineConfig(targets.
|
|
4307
|
+
let inlineConfig = createInlineConfig(targets.platform);
|
|
4237
4308
|
if (targets.runWeb) {
|
|
4238
4309
|
const host = resolveWebHost(options.host);
|
|
4239
4310
|
if (host !== void 0) inlineConfig = {
|
|
@@ -4434,6 +4505,7 @@ const WEAPP_VITE_NATIVE_COMMANDS = new Set([
|
|
|
4434
4505
|
"build",
|
|
4435
4506
|
"close",
|
|
4436
4507
|
"analyze",
|
|
4508
|
+
"alipay",
|
|
4437
4509
|
"init",
|
|
4438
4510
|
"open",
|
|
4439
4511
|
"npm",
|
|
@@ -4551,6 +4623,7 @@ try {
|
|
|
4551
4623
|
} catch {}
|
|
4552
4624
|
cli.option("-c, --config <file>", `[string] use specified config file`).option("--base <path>", `[string] public base path (default: /)`, { type: [convertBase] }).option("-l, --logLevel <level>", `[string] info | warn | error | silent`).option("--clearScreen", `[boolean] allow/disable clear screen when logging`).option("-d, --debug [feat]", `[string | boolean] show debug logs`).option("-f, --filter <filter>", `[string] filter debug logs`).option("-m, --mode <mode>", `[string] set env mode`);
|
|
4553
4625
|
registerIdeCommand(cli);
|
|
4626
|
+
registerAlipayCommand(cli);
|
|
4554
4627
|
registerBuildCommand(cli);
|
|
4555
4628
|
registerCloseCommand(cli);
|
|
4556
4629
|
registerAnalyzeCommand(cli);
|
|
@@ -4569,7 +4642,8 @@ const skipManagedTsconfigBootstrapCommands = new Set([
|
|
|
4569
4642
|
"ide",
|
|
4570
4643
|
"init",
|
|
4571
4644
|
"mcp",
|
|
4572
|
-
"npm"
|
|
4645
|
+
"npm",
|
|
4646
|
+
"alipay"
|
|
4573
4647
|
]);
|
|
4574
4648
|
function resolveManagedTsconfigBootstrapRoot(args) {
|
|
4575
4649
|
const [firstArg, secondArg] = args;
|