weapp-vite 6.15.14 → 6.15.16

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/bin/bootstrap.js CHANGED
@@ -6,6 +6,38 @@ function getGlobalProcess() {
6
6
  return Reflect.get(globalThis, 'process')
7
7
  }
8
8
 
9
+ function isKnownLocalPkgResolveNoise(args) {
10
+ const message = args
11
+ .map((value) => {
12
+ if (value instanceof Error) {
13
+ return `${value.message}\n${value.stack ?? ''}`
14
+ }
15
+ return String(value)
16
+ })
17
+ .join('\n')
18
+
19
+ return message.includes('ERR_INVALID_FILE_URL_HOST')
20
+ && (message.includes('local-pkg') || message.includes('mlly'))
21
+ }
22
+
23
+ export function guardKnownLocalPkgResolveNoise() {
24
+ // eslint-disable-next-line no-console -- CLI 启动阶段需要定向过滤已知三方解析噪音
25
+ const originalConsoleError = console.error
26
+
27
+ // eslint-disable-next-line no-console -- CLI 启动阶段需要定向过滤已知三方解析噪音
28
+ console.error = (...args) => {
29
+ if (isKnownLocalPkgResolveNoise(args)) {
30
+ return
31
+ }
32
+ originalConsoleError(...args)
33
+ }
34
+
35
+ return () => {
36
+ // eslint-disable-next-line no-console -- 恢复原始 console.error
37
+ console.error = originalConsoleError
38
+ }
39
+ }
40
+
9
41
  export function formatPrepareSkipMessage(error) {
10
42
  const message = error instanceof Error ? error.message : String(error)
11
43
  return `[prepare] 跳过 .weapp-vite 支持文件预生成:${message}`
@@ -77,6 +109,7 @@ export async function runWeappViteCLI(options = {}) {
77
109
  write = message => getGlobalProcess().stderr.write(`\n WARN ${message}\n\n`),
78
110
  } = options
79
111
  const restorePrepareGuard = guardPrepareProcessExit(argv)
112
+ const restoreKnownNoiseGuard = guardKnownLocalPkgResolveNoise()
80
113
 
81
114
  try {
82
115
  await importer()
@@ -90,6 +123,7 @@ export async function runWeappViteCLI(options = {}) {
90
123
  throw error
91
124
  }
92
125
  finally {
126
+ restoreKnownNoiseGuard()
93
127
  if (!isPrepareCommand(argv)) {
94
128
  restorePrepareGuard()
95
129
  }
@@ -1,7 +1,11 @@
1
- import { describe, expect, it, vi } from 'vitest'
2
- import { formatPrepareSkipMessage, guardPrepareProcessExit, runWeappViteCLI } from './bootstrap.js'
1
+ import { afterEach, describe, expect, it, vi } from 'vitest'
2
+ import { formatPrepareSkipMessage, guardKnownLocalPkgResolveNoise, guardPrepareProcessExit, runWeappViteCLI } from './bootstrap.js'
3
3
 
4
4
  describe('bin bootstrap', () => {
5
+ afterEach(() => {
6
+ vi.restoreAllMocks()
7
+ })
8
+
5
9
  it('guards process exit state for prepare at bin level', () => {
6
10
  process.exitCode = 1
7
11
  const restore = guardPrepareProcessExit(['prepare'])
@@ -60,4 +64,29 @@ describe('bin bootstrap', () => {
60
64
 
61
65
  expect(process.exitCode).toBe(0)
62
66
  })
67
+
68
+ it('suppresses known local-pkg resolve noise during bootstrap', () => {
69
+ const consoleErrorSpy = vi.fn()
70
+ // eslint-disable-next-line no-console -- 测试里需要替换 console.error
71
+ const originalConsoleError = console.error
72
+ // eslint-disable-next-line no-console -- 测试里需要观察过滤后的 console.error
73
+ console.error = consoleErrorSpy
74
+
75
+ const restore = guardKnownLocalPkgResolveNoise()
76
+ try {
77
+ // eslint-disable-next-line no-console -- 触发已知噪音分支
78
+ console.error(new Error('TypeError [ERR_INVALID_FILE_URL_HOST]: File URL host must be "localhost" or empty on darwin\n at _resolve (/tmp/local-pkg/dist/index.mjs:1:1)\n at resolveSync (/tmp/mlly/dist/index.mjs:1:1)'))
79
+ // eslint-disable-next-line no-console -- 触发普通错误透传分支
80
+ console.error(new Error('boom'))
81
+ }
82
+ finally {
83
+ restore()
84
+ // eslint-disable-next-line no-console -- 恢复测试前的 console.error
85
+ console.error = originalConsoleError
86
+ }
87
+
88
+ expect(consoleErrorSpy).toHaveBeenCalledTimes(1)
89
+ expect(consoleErrorSpy.mock.calls[0]?.[0]).toBeInstanceOf(Error)
90
+ expect(consoleErrorSpy.mock.calls[0]?.[0]?.message).toBe('boom')
91
+ })
63
92
  })
@@ -1,4 +1,4 @@
1
- import { i as getCompilerContext, u as getRouteRuntimeGlobalKeys } from "./createContext-BcX4FNt9.mjs";
1
+ import { d as getRouteRuntimeGlobalKeys, i as getCompilerContext } from "./createContext-xOVHRF1m.mjs";
2
2
  //#region src/auto-routes.ts
3
3
  const ROUTE_RUNTIME_OVERRIDE_KEY = Symbol.for("weapp-vite.route-runtime");
4
4
  function createGetter(resolver) {
package/dist/cli.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { S as isPathInside, _ as DEFAULT_MP_PLATFORM, b as resolveMiniPlatform, c as createSharedBuildConfig, d as resolveWeappConfigFile, f as checkRuntime, g as createCjsConfigLoadError, h as parseCommentJson, l as SHARED_CHUNK_VIRTUAL_PREFIX, m as loadViteConfigFile, n as syncProjectSupportFiles, p as getProjectConfigFileName, r as syncManagedTsconfigBootstrapFiles, s as formatBytes, t as createCompilerContext, v as getDefaultIdeProjectRoot, x as shouldPassPlatformArgToIdeOpen, y as normalizeMiniPlatform } from "./createContext-BcX4FNt9.mjs";
1
+ import { C as isPathInside, S as shouldPassPlatformArgToIdeOpen, _ as createCjsConfigLoadError, b as normalizeMiniPlatform, c as createSharedBuildConfig, f as resolveWeappConfigFile, g as parseCommentJson, h as loadViteConfigFile, l as SHARED_CHUNK_VIRTUAL_PREFIX, m as getProjectConfigFileName, n as syncProjectSupportFiles, p as checkRuntime, r as syncManagedTsconfigBootstrapFiles, s as formatBytes, t as createCompilerContext, u as resolveHmrProfileJsonPath, v as DEFAULT_MP_PLATFORM, x as resolveMiniPlatform, y as getDefaultIdeProjectRoot } from "./createContext-xOVHRF1m.mjs";
2
2
  import { r as logger_default, t as colors } from "./logger-CgxdNjvb.mjs";
3
- import { h as VERSION } from "./file-BR4Z0ErL.mjs";
3
+ import { h as VERSION } from "./file-DEHK6p9s.mjs";
4
4
  import { a as resolveWeappMcpConfig, o as startWeappViteMcpServer } from "./mcp-DRlj32v4.mjs";
5
5
  import { createRequire } from "node:module";
6
6
  import path, { posix } from "pathe";
@@ -22,6 +22,106 @@ import { determineAgent } from "@vercel/detect-agent";
22
22
  import { initConfig } from "@weapp-core/init";
23
23
  import { createInterface } from "node:readline/promises";
24
24
  import { clearTimeout, setTimeout as setTimeout$1 } from "node:timers";
25
+ //#region src/analyze/hmr.ts
26
+ function createMetricSummary(values) {
27
+ if (!values.length) return { count: 0 };
28
+ const total = values.reduce((sum, value) => sum + value, 0);
29
+ return {
30
+ count: values.length,
31
+ averageMs: total / values.length,
32
+ maxMs: Math.max(...values)
33
+ };
34
+ }
35
+ function sortCountEntries(map) {
36
+ return [...map.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0])).map(([name, count]) => ({
37
+ name,
38
+ count
39
+ }));
40
+ }
41
+ function collectCounts(target, values) {
42
+ for (const value of values ?? []) {
43
+ if (!value) continue;
44
+ target.set(value, (target.get(value) ?? 0) + 1);
45
+ }
46
+ }
47
+ function isFiniteNumber$1(value) {
48
+ return typeof value === "number" && Number.isFinite(value);
49
+ }
50
+ /**
51
+ * @description 聚合 HMR JSONL profile,为命令行与后续仪表盘复用。
52
+ */
53
+ async function analyzeHmrProfile(options) {
54
+ const lines = (await fs.readFile(options.profilePath, "utf8")).split(/\r?\n/);
55
+ const samples = [];
56
+ let skippedLineCount = 0;
57
+ for (const line of lines) {
58
+ const trimmed = line.trim();
59
+ if (!trimmed) continue;
60
+ try {
61
+ const parsed = JSON.parse(trimmed);
62
+ if (!isFiniteNumber$1(parsed.totalMs)) {
63
+ skippedLineCount += 1;
64
+ continue;
65
+ }
66
+ samples.push(parsed);
67
+ } catch {
68
+ skippedLineCount += 1;
69
+ }
70
+ }
71
+ const eventCounts = /* @__PURE__ */ new Map();
72
+ const dirtyReasonCounts = /* @__PURE__ */ new Map();
73
+ const pendingReasonCounts = /* @__PURE__ */ new Map();
74
+ const totalValues = [];
75
+ const buildCoreValues = [];
76
+ const transformValues = [];
77
+ const writeValues = [];
78
+ const watchToDirtyValues = [];
79
+ const emitValues = [];
80
+ const sharedChunkValues = [];
81
+ for (const sample of samples) {
82
+ totalValues.push(sample.totalMs);
83
+ if (sample.event) eventCounts.set(sample.event, (eventCounts.get(sample.event) ?? 0) + 1);
84
+ if (isFiniteNumber$1(sample.buildCoreMs)) buildCoreValues.push(sample.buildCoreMs);
85
+ if (isFiniteNumber$1(sample.transformMs)) transformValues.push(sample.transformMs);
86
+ if (isFiniteNumber$1(sample.writeMs)) writeValues.push(sample.writeMs);
87
+ if (isFiniteNumber$1(sample.watchToDirtyMs)) watchToDirtyValues.push(sample.watchToDirtyMs);
88
+ if (isFiniteNumber$1(sample.emitMs)) emitValues.push(sample.emitMs);
89
+ if (isFiniteNumber$1(sample.sharedChunkResolveMs)) sharedChunkValues.push(sample.sharedChunkResolveMs);
90
+ collectCounts(dirtyReasonCounts, sample.dirtyReasonSummary);
91
+ collectCounts(pendingReasonCounts, sample.pendingReasonSummary);
92
+ }
93
+ const orderedByTime = [...samples].sort((left, right) => {
94
+ const leftTime = typeof left.timestamp === "string" ? Date.parse(left.timestamp) : NaN;
95
+ const rightTime = typeof right.timestamp === "string" ? Date.parse(right.timestamp) : NaN;
96
+ if (Number.isFinite(leftTime) && Number.isFinite(rightTime)) return leftTime - rightTime;
97
+ return 0;
98
+ });
99
+ const slowestSamples = [...samples].sort((left, right) => (right.totalMs ?? 0) - (left.totalMs ?? 0)).slice(0, options.topSlowest ?? 5);
100
+ return {
101
+ runtime: "mini",
102
+ kind: "hmr-profile",
103
+ generatedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
104
+ profilePath: options.profilePath,
105
+ sampleCount: samples.length,
106
+ skippedLineCount,
107
+ firstTimestamp: orderedByTime[0]?.timestamp,
108
+ lastTimestamp: orderedByTime.at(-1)?.timestamp,
109
+ metrics: {
110
+ totalMs: createMetricSummary(totalValues),
111
+ buildCoreMs: createMetricSummary(buildCoreValues),
112
+ transformMs: createMetricSummary(transformValues),
113
+ writeMs: createMetricSummary(writeValues),
114
+ watchToDirtyMs: createMetricSummary(watchToDirtyValues),
115
+ emitMs: createMetricSummary(emitValues),
116
+ sharedChunkResolveMs: createMetricSummary(sharedChunkValues)
117
+ },
118
+ events: sortCountEntries(eventCounts),
119
+ dirtyReasons: sortCountEntries(dirtyReasonCounts),
120
+ pendingReasons: sortCountEntries(pendingReasonCounts),
121
+ slowestSamples
122
+ };
123
+ }
124
+ //#endregion
25
125
  //#region src/analyze/subpackages/classifier.ts
26
126
  const VIRTUAL_MODULE_INDICATOR = "\0";
27
127
  const VIRTUAL_PREFIX = `${SHARED_CHUNK_VIRTUAL_PREFIX}/`;
@@ -809,8 +909,42 @@ async function writeAnalyzeResult(result, outputOption, configService) {
809
909
  logger_default.success(`分析结果已写入 ${colors.green(relativeOutput)}`);
810
910
  return resolvedOutputPath;
811
911
  }
912
+ function formatMetricSummary(label, metric) {
913
+ if (!metric.count || metric.averageMs === void 0 || metric.maxMs === void 0) return;
914
+ return `${label} avg ${metric.averageMs.toFixed(2)} ms,max ${metric.maxMs.toFixed(2)} ms`;
915
+ }
916
+ function formatCountItems(items, limit = 5) {
917
+ return items.slice(0, limit).map((item) => `${item.name} x${item.count}`).join(",");
918
+ }
919
+ function printHmrProfileAnalysisSummary(result, configService) {
920
+ logger_default.success("HMR profile 分析完成");
921
+ logger_default.info(`- profile:${colors.green(configService.relativeCwd(result.profilePath))}`);
922
+ logger_default.info(`- 样本:${result.sampleCount} 条`);
923
+ if (result.firstTimestamp && result.lastTimestamp) logger_default.info(`- 时间范围:${result.firstTimestamp} -> ${result.lastTimestamp}`);
924
+ const totalSummary = formatMetricSummary("total", result.metrics.totalMs);
925
+ const watchSummary = formatMetricSummary("watch->dirty", result.metrics.watchToDirtyMs);
926
+ const emitSummary = formatMetricSummary("emit", result.metrics.emitMs);
927
+ const sharedSummary = formatMetricSummary("shared", result.metrics.sharedChunkResolveMs);
928
+ for (const summary of [
929
+ totalSummary,
930
+ watchSummary,
931
+ emitSummary,
932
+ sharedSummary
933
+ ]) if (summary) logger_default.info(`- ${summary}`);
934
+ if (result.events.length) logger_default.info(`- 事件分布:${formatCountItems(result.events)}`);
935
+ if (result.dirtyReasons.length) logger_default.info(`- 主要 dirty 原因:${formatCountItems(result.dirtyReasons)}`);
936
+ if (result.pendingReasons.length) logger_default.info(`- 主要 pending 原因:${formatCountItems(result.pendingReasons)}`);
937
+ if (result.skippedLineCount > 0) logger_default.warn(`- 跳过 ${result.skippedLineCount} 条无法解析的 profile 记录`);
938
+ if (result.slowestSamples.length) {
939
+ logger_default.info("- 最慢样本:");
940
+ for (const sample of result.slowestSamples.slice(0, 3)) {
941
+ const fileLabel = sample.file ? configService.relativeCwd(sample.file) : "(unknown)";
942
+ logger_default.info(` - ${sample.totalMs?.toFixed(2) ?? "0.00"} ms,${sample.event ?? "unknown"},${fileLabel}`);
943
+ }
944
+ }
945
+ }
812
946
  function registerAnalyzeCommand(cli) {
813
- cli.command("analyze [root]", "analyze 两端包体与源码映射").option("--json", `[boolean] 输出 JSON 结果`).option("--output <file>", `[string] 将分析结果写入指定文件(JSON)`).option("-p, --platform <platform>", `[string] target platform (weapp | h5)`).option("--project-config <path>", `[string] project config path (miniprogram only)`).action(async (root, options) => {
947
+ cli.command("analyze [root]", "analyze 两端包体与源码映射").option("--hmr-profile [file]", `[string | boolean] 分析 HMR JSONL profile,省略值时优先读取配置,否则回退到默认路径`).option("--json", `[boolean] 输出 JSON 结果`).option("--output <file>", `[string] 将分析结果写入指定文件(JSON)`).option("-p, --platform <platform>", `[string] target platform (weapp | h5)`).option("--project-config <path>", `[string] project config path (miniprogram only)`).action(async (root, options) => {
814
948
  filterDuplicateOptions(options);
815
949
  const configFile = resolveConfigFile(options);
816
950
  const outputJson = coerceBooleanOption(options.json);
@@ -830,6 +964,21 @@ function registerAnalyzeCommand(cli) {
830
964
  resolvedConfigPlatform: ctx.configService.platform
831
965
  });
832
966
  const outputOption = typeof options.output === "string" ? options.output.trim() : "";
967
+ if (options.hmrProfile !== void 0 && options.hmrProfile !== false) {
968
+ const profileOption = typeof options.hmrProfile === "string" && options.hmrProfile.trim() ? options.hmrProfile.trim() : ctx.configService.weappViteConfig.hmr?.profileJson;
969
+ const profilePath = resolveHmrProfileJsonPath({
970
+ cwd: ctx.configService.cwd,
971
+ option: profileOption,
972
+ fallbackToDefault: true
973
+ });
974
+ if (!profilePath) throw new Error("未找到可用的 HMR profile 文件路径");
975
+ const hmrProfileResult = await analyzeHmrProfile({ profilePath });
976
+ const writtenPath = await writeAnalyzeResult(hmrProfileResult, outputOption, ctx.configService);
977
+ if (outputJson) {
978
+ if (!writtenPath) process.stdout.write(`${JSON.stringify(hmrProfileResult, null, 2)}\n`);
979
+ } else printHmrProfileAnalysisSummary(hmrProfileResult, ctx.configService);
980
+ return;
981
+ }
833
982
  if (targets.runWeb) {
834
983
  const webResult = createWebAnalyzeResult(ctx.configService, { platform: targets.label === "web" ? "web" : "h5" });
835
984
  const writtenPath = await writeAnalyzeResult(webResult, outputOption, ctx.configService);
@@ -1025,7 +1174,7 @@ async function tryExecuteWechatIdeCliCommandByHelper(argv) {
1025
1174
  async function executeWechatIdeCliCommand(argv, options = {}) {
1026
1175
  const { automatorMode = "prefer", cancelLevel = "warn", httpMode = "prefer", onNonLoginError, onRetry, projectPath } = options;
1027
1176
  await runWithSuspendedSharedInput(async () => {
1028
- try {
1177
+ if (httpMode !== "skip") try {
1029
1178
  if (await tryExecuteWechatIdeCliCommandByHttp(argv, projectPath)) return;
1030
1179
  } catch (error) {
1031
1180
  if (httpMode === "require") throw error;
@@ -1231,6 +1380,49 @@ async function tryOpenWechatIdeByAutomator(projectPath, options) {
1231
1380
  await openWechatIdeByAutomator(projectPath);
1232
1381
  return true;
1233
1382
  }
1383
+ /**
1384
+ * @description 打开后主动刷新微信开发者工具的项目索引,避免模拟器沿用过期 app 配置。
1385
+ */
1386
+ async function stabilizeOpenedWechatIdeProject(projectPath, servicePortEnabled) {
1387
+ if (servicePortEnabled === false) return;
1388
+ try {
1389
+ await executeWechatIdeCliCommand(["compile"], {
1390
+ httpMode: "prefer",
1391
+ onNonLoginError: (error) => logger_default.error(error),
1392
+ projectPath
1393
+ });
1394
+ await executeWechatIdeCliCommand([
1395
+ "reset-fileutils",
1396
+ "-p",
1397
+ projectPath
1398
+ ], {
1399
+ httpMode: "prefer",
1400
+ onNonLoginError: (error) => logger_default.error(error),
1401
+ projectPath
1402
+ });
1403
+ await executeWechatIdeCliCommand([
1404
+ "engine",
1405
+ "build",
1406
+ projectPath
1407
+ ], {
1408
+ httpMode: "prefer",
1409
+ onNonLoginError: (error) => logger_default.error(error),
1410
+ projectPath
1411
+ });
1412
+ try {
1413
+ await executeWechatIdeCliCommand(["compile"], {
1414
+ automatorMode: "require",
1415
+ httpMode: "skip",
1416
+ projectPath
1417
+ });
1418
+ } catch (error) {
1419
+ if (shouldLogAutomatorFallbackError()) logger_default.error(error);
1420
+ }
1421
+ } catch (error) {
1422
+ logger_default.warn("刷新微信开发者工具项目索引失败,已保留当前打开状态;如模拟器仍显示旧状态,可手动刷新一次。");
1423
+ if (shouldLogAutomatorFallbackError()) logger_default.error(error);
1424
+ }
1425
+ }
1234
1426
  function createIdeOpenArgv(platform, projectPath, options = {}) {
1235
1427
  const argv = ["open", "-p"];
1236
1428
  if (projectPath) argv.push(projectPath);
@@ -1251,7 +1443,10 @@ async function openIde(platform, projectPath, options = {}) {
1251
1443
  }
1252
1444
  if (platform === "weapp" && projectPath && bootstrapResult?.servicePortEnabled === false) logger_default.warn("检测到微信开发者工具服务端口当前处于关闭状态,已保留用户设置并回退到普通 open 流程。");
1253
1445
  if (platform === "weapp" && projectPath && options.trustProject !== false && bootstrapResult?.servicePortEnabled !== false) try {
1254
- if (await tryOpenWechatIdeByAutomator(projectPath, options)) return;
1446
+ if (await tryOpenWechatIdeByAutomator(projectPath, options)) {
1447
+ await stabilizeOpenedWechatIdeProject(projectPath, bootstrapResult?.servicePortEnabled);
1448
+ return;
1449
+ }
1255
1450
  } catch (error) {
1256
1451
  if (isAutomatorLoginError(error)) {
1257
1452
  logger_default.error("检测到微信开发者工具登录状态失效,请先登录后重试。");
@@ -1261,6 +1456,7 @@ async function openIde(platform, projectPath, options = {}) {
1261
1456
  if (shouldLogAutomatorFallbackError()) logger_default.error(error);
1262
1457
  }
1263
1458
  await runWechatIdeOpenWithRetry(createIdeOpenArgv(platform, projectPath, options));
1459
+ if (platform === "weapp" && projectPath) await stabilizeOpenedWechatIdeProject(projectPath, bootstrapResult?.servicePortEnabled);
1264
1460
  }
1265
1461
  /**
1266
1462
  * @description 解析 IDE 相关命令所需的平台、项目目录与配置上下文。
@@ -1280,6 +1476,7 @@ async function resolveIdeCommandContext(options) {
1280
1476
  platform ??= ctx.configService.platform;
1281
1477
  if (!projectPath) projectPath = resolveIdeProjectRoot(ctx.configService.mpDistRoot, ctx.configService.cwd);
1282
1478
  return {
1479
+ cwd: ctx.configService.cwd,
1283
1480
  platform,
1284
1481
  projectPath,
1285
1482
  weappViteConfig: ctx.configService.weappViteConfig,
@@ -1291,6 +1488,7 @@ async function resolveIdeCommandContext(options) {
1291
1488
  if (defaultProjectRoot) projectPath = resolveIdeProjectRoot(defaultProjectRoot, cwd);
1292
1489
  }
1293
1490
  return {
1491
+ cwd,
1294
1492
  platform,
1295
1493
  projectPath
1296
1494
  };
@@ -1717,6 +1915,79 @@ function sleep(ms) {
1717
1915
  return new Promise((resolve) => setTimeout(resolve, ms));
1718
1916
  }
1719
1917
  //#endregion
1918
+ //#region src/cli/hmrProfileSummary.ts
1919
+ function isFiniteNumber(value) {
1920
+ return typeof value === "number" && Number.isFinite(value);
1921
+ }
1922
+ function parseLatestHmrProfileSample(content) {
1923
+ const lines = content.split(/\r?\n/);
1924
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
1925
+ const trimmed = lines[index]?.trim();
1926
+ if (!trimmed) continue;
1927
+ try {
1928
+ const parsed = JSON.parse(trimmed);
1929
+ if (isFiniteNumber(parsed.totalMs)) return parsed;
1930
+ } catch {
1931
+ continue;
1932
+ }
1933
+ }
1934
+ }
1935
+ function formatPhaseHint(sample) {
1936
+ const topPhase = [
1937
+ {
1938
+ label: "build-core",
1939
+ value: sample.buildCoreMs
1940
+ },
1941
+ {
1942
+ label: "transform",
1943
+ value: sample.transformMs
1944
+ },
1945
+ {
1946
+ label: "watch->dirty",
1947
+ value: sample.watchToDirtyMs
1948
+ },
1949
+ {
1950
+ label: "emit",
1951
+ value: sample.emitMs
1952
+ },
1953
+ {
1954
+ label: "shared",
1955
+ value: sample.sharedChunkResolveMs
1956
+ },
1957
+ {
1958
+ label: "write",
1959
+ value: sample.writeMs
1960
+ }
1961
+ ].filter((phase) => isFiniteNumber(phase.value)).sort((left, right) => (right.value ?? 0) - (left.value ?? 0))[0];
1962
+ if (!topPhase || (topPhase.value ?? 0) < 5) return;
1963
+ return `${topPhase.label} ${topPhase.value.toFixed(2)} ms`;
1964
+ }
1965
+ /**
1966
+ * @description 读取最近一次 HMR profile,并格式化为 IDE 日志启动前的单行摘要。
1967
+ */
1968
+ async function readLatestHmrProfileSummary(options) {
1969
+ const profilePath = resolveHmrProfileJsonPath({
1970
+ cwd: options.cwd,
1971
+ option: options.weappViteConfig?.hmr?.profileJson
1972
+ });
1973
+ if (!profilePath) return;
1974
+ const content = await fs.readFile(profilePath, "utf8").catch(() => void 0);
1975
+ if (!content) return;
1976
+ const sample = parseLatestHmrProfileSample(content);
1977
+ if (!sample || !isFiniteNumber(sample.totalMs)) return;
1978
+ const relativeCwd = options.relativeCwd ?? ((value) => value);
1979
+ const segments = [`[hmr] 最近一次热更新 ${sample.totalMs.toFixed(2)} ms`];
1980
+ if (sample.event) segments.push(sample.event);
1981
+ if (sample.file) segments.push(relativeCwd(sample.file));
1982
+ const phaseHint = formatPhaseHint(sample);
1983
+ if (phaseHint) segments.push(`主耗时 ${phaseHint}`);
1984
+ return {
1985
+ file: sample.file,
1986
+ profilePath,
1987
+ line: segments.join(",")
1988
+ };
1989
+ }
1990
+ //#endregion
1720
1991
  //#region src/cli/commands/ide.ts
1721
1992
  async function waitForTermination(cleanup) {
1722
1993
  await new Promise((resolve) => {
@@ -1805,6 +2076,12 @@ async function runIdeCommand(action, root, options) {
1805
2076
  enabled: true
1806
2077
  }
1807
2078
  });
2079
+ const latestHmrSummary = await readLatestHmrProfileSummary({
2080
+ cwd: resolved.cwd ?? process.cwd(),
2081
+ relativeCwd: (value) => resolved.cwd ? value.replace(`${resolved.cwd}/`, "") : value,
2082
+ weappViteConfig: resolved.weappViteConfig
2083
+ });
2084
+ if (latestHmrSummary) logger_default.info(latestHmrSummary.line);
1808
2085
  const session = await startForwardConsoleBridge({
1809
2086
  projectPath: resolved.projectPath,
1810
2087
  agentName: void 0,
@@ -2251,13 +2528,19 @@ function registerOpenCommand(cli) {
2251
2528
  filterDuplicateOptions(options);
2252
2529
  const configFile = resolveConfigFile(options);
2253
2530
  const targets = resolveRuntimeTargets(options);
2254
- const { platform, projectPath, mpDistRoot } = await resolveIdeCommandContext({
2531
+ const { cwd, platform, projectPath, mpDistRoot, weappViteConfig } = await resolveIdeCommandContext({
2255
2532
  configFile,
2256
2533
  mode: options.mode ?? "development",
2257
2534
  platform: targets.mpPlatform,
2258
2535
  projectPath: root,
2259
2536
  cliPlatform: targets.rawPlatform
2260
2537
  });
2538
+ const latestHmrSummary = await readLatestHmrProfileSummary({
2539
+ cwd: cwd ?? process.cwd(),
2540
+ relativeCwd: (value) => cwd ? value.replace(`${cwd}/`, "") : value,
2541
+ weappViteConfig
2542
+ });
2543
+ if (latestHmrSummary) logger_default.info(latestHmrSummary.line);
2261
2544
  await openIde(platform, projectPath ?? resolveIdeProjectRoot(mpDistRoot, process.cwd()), { trustProject: options.trustProject });
2262
2545
  });
2263
2546
  }
@@ -2292,6 +2575,15 @@ function registerPrepareCommand(cli) {
2292
2575
  }
2293
2576
  //#endregion
2294
2577
  //#region src/cli/devHotkeys/devtools.ts
2578
+ async function appendLatestHmrSummary(baseSummary, options) {
2579
+ const summary = await readLatestHmrProfileSummary({
2580
+ cwd: options.cwd,
2581
+ relativeCwd: (value) => value.replace(`${options.cwd}/`, ""),
2582
+ weappViteConfig: options.weappViteConfig
2583
+ });
2584
+ if (!summary) return baseSummary;
2585
+ return `${baseSummary};${summary.line}`;
2586
+ }
2295
2587
  /**
2296
2588
  * @description 重置当前 DevTools automator 共享会话。
2297
2589
  */
@@ -2313,7 +2605,7 @@ async function runResetAndReopenDevtoolsAction(options) {
2313
2605
  await closeSharedMiniProgram(options.projectPath);
2314
2606
  const summary = await options.openIde();
2315
2607
  logger_default.success("[dev action] 当前 DevTools 会话已重置,并已重新打开项目。");
2316
- return summary ?? "已重置当前 DevTools 会话并重新打开项目";
2608
+ return await appendLatestHmrSummary(summary ?? "已重置当前 DevTools 会话并重新打开项目", options);
2317
2609
  }
2318
2610
  /**
2319
2611
  * @description 手动触发一次当前小程序 dev 重新构建。
@@ -2340,7 +2632,7 @@ async function runOpenIdeAction(options) {
2340
2632
  await closeSharedMiniProgram(options.projectPath);
2341
2633
  const summary = await options.openIde();
2342
2634
  logger_default.success("[dev action] 微信开发者工具项目已重新打开。");
2343
- return summary ?? "已重新打开微信开发者工具项目";
2635
+ return await appendLatestHmrSummary(summary ?? "已重新打开微信开发者工具项目", options);
2344
2636
  }
2345
2637
  //#endregion
2346
2638
  //#region src/cli/devHotkeys/screenshot.ts
@@ -2492,7 +2784,7 @@ function resolveRunnableHotkeyDefinition(input) {
2492
2784
  }
2493
2785
  //#endregion
2494
2786
  //#region package.json
2495
- var version = "6.15.14";
2787
+ var version = "6.15.16";
2496
2788
  //#endregion
2497
2789
  //#region src/cli/devHotkeys/format.ts
2498
2790
  const FULLWIDTH_ASCII_START = 65281;
@@ -2735,8 +3027,9 @@ function startDevHotkeys(options) {
2735
3027
  return;
2736
3028
  }
2737
3029
  const action = resolveRunnableHotkeyDefinition(normalizedInput) ?? resolveRunnableHotkeyDefinition(normalized);
2738
- if (action?.run) runAction(action.label ?? action.description, resolvePendingLabel(normalized) ?? action.pendingLabel ?? `正在执行 ${action.description}`, async () => {
2739
- return await action.run({
3030
+ const run = action?.run;
3031
+ if (run) runAction(action.label ?? action.description, resolvePendingLabel(normalized) ?? action.pendingLabel ?? `正在执行 ${action.description}`, async () => {
3032
+ return await run({
2740
3033
  options,
2741
3034
  toggleMcp
2742
3035
  });
@@ -3107,7 +3400,8 @@ function registerServeCommand(cli) {
3107
3400
  platform: configService.platform,
3108
3401
  projectPath: miniProgramDevActions.projectPath ?? configService.cwd,
3109
3402
  rebuild: miniProgramDevActions.rebuild,
3110
- silentStartupHint: true
3403
+ silentStartupHint: true,
3404
+ weappViteConfig: configService.weappViteConfig
3111
3405
  }) : void 0;
3112
3406
  try {
3113
3407
  const analyzeController = createAnalyzeController({
@@ -6,7 +6,7 @@ import { LoggerConfig } from "@weapp-core/logger";
6
6
  import { ConfigEnv, ConfigEnv as ConfigEnv$1, InlineConfig, InlineConfig as InlineConfig$1, Plugin as Plugin$1, PluginOption, ResolvedConfig, UserConfig, ViteDevServer, ViteDevServer as ViteDevServer$1, build } from "vite";
7
7
  import { LRUCache } from "lru-cache";
8
8
  import { fdir } from "fdir";
9
- import { InputOption, RolldownBuild, RolldownOptions, RolldownOutput, RolldownOutput as RolldownOutput$1, RolldownPlugin, RolldownPluginOption, RolldownWatcher, RolldownWatcher as RolldownWatcher$1, WatchOptions as RolldownWatchOptions } from "rolldown";
9
+ import { InputOption, ResolvedId, RolldownBuild, RolldownOptions, RolldownOutput, RolldownOutput as RolldownOutput$1, RolldownPlugin, RolldownPluginOption, RolldownWatcher, RolldownWatcher as RolldownWatcher$1, WatchOptions as RolldownWatchOptions } from "rolldown";
10
10
  import { Options } from "rolldown-plugin-dts";
11
11
  import { Buffer } from "node:buffer";
12
12
  import { PluginOptions } from "vite-tsconfig-paths";
@@ -504,6 +504,14 @@ interface WeappSubPackageConfig {
504
504
  interface WeappHmrConfig {
505
505
  sharedChunks?: 'full' | 'auto' | 'off';
506
506
  touchAppWxss?: boolean | 'auto';
507
+ /**
508
+ * @description HMR 终端日志档位:默认仅输出总耗时,显式开启 concise/verbose 后再展示阶段诊断。
509
+ */
510
+ logLevel?: 'default' | 'concise' | 'verbose';
511
+ /**
512
+ * @description 是否输出 HMR 结构化 profile,或指定自定义 JSONL 输出路径
513
+ */
514
+ profileJson?: boolean | string;
507
515
  }
508
516
  /**
509
517
  * @description worker 构建配置
@@ -1369,6 +1377,42 @@ interface RuntimeState {
1369
1377
  independent: {
1370
1378
  outputs: Map<string, RolldownOutput>;
1371
1379
  };
1380
+ hmr: {
1381
+ loadedEntrySet: Set<string>;
1382
+ dirtyEntrySet: Set<string>;
1383
+ dirtyEntryReasons: Map<string, 'direct' | 'dependency'>;
1384
+ resolvedEntryMap: Map<string, ResolvedId>;
1385
+ entriesMap: Map<string, Entry | undefined>;
1386
+ layoutEntryDependents: Map<string, Set<string>>;
1387
+ entryLayoutDependencies: Map<string, Set<string>>;
1388
+ recentProfiles: Array<{
1389
+ totalMs: number;
1390
+ buildCoreMs?: number;
1391
+ transformMs?: number;
1392
+ writeMs?: number;
1393
+ watchToDirtyMs?: number;
1394
+ emitMs?: number;
1395
+ sharedChunkResolveMs?: number;
1396
+ dirtyCount?: number;
1397
+ pendingCount?: number;
1398
+ emittedCount?: number;
1399
+ }>;
1400
+ profile: {
1401
+ event?: ChangeEvent;
1402
+ file?: string;
1403
+ buildCoreMs?: number;
1404
+ transformMs?: number;
1405
+ writeMs?: number;
1406
+ watchToDirtyMs?: number;
1407
+ emitMs?: number;
1408
+ sharedChunkResolveMs?: number;
1409
+ dirtyCount?: number;
1410
+ pendingCount?: number;
1411
+ emittedCount?: number;
1412
+ dirtyReasonSummary?: string[];
1413
+ pendingReasonSummary?: string[];
1414
+ };
1415
+ };
1372
1416
  };
1373
1417
  json: {
1374
1418
  cache: FileCache<any>;
@@ -1521,7 +1565,7 @@ interface AutoRoutesService {
1521
1565
  getWatchFiles: () => Iterable<string>;
1522
1566
  getWatchDirectories: () => Iterable<string>;
1523
1567
  isRouteFile: (filePath: string) => boolean;
1524
- handleFileChange: (filePath: string, event?: AutoRoutesFileEvent) => Promise<void>;
1568
+ handleFileChange: (filePath: string, event?: AutoRoutesFileEvent) => Promise<boolean>;
1525
1569
  isInitialized: () => boolean;
1526
1570
  isEnabled: () => boolean;
1527
1571
  }
package/dist/config.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { Bn as resolveWeappViteHostMeta, Fn as WeappViteHostMeta, In as WeappViteRuntime, Ln as applyWeappViteHostMeta, Pn as WEAPP_VITE_HOST_NAME, Rn as createWeappViteHostMeta, _ as definePageJson, a as UserConfigFnNoEnvPlain, c as UserConfigFnPromise, d as Component, f as Page, g as defineComponentJson, h as defineAppJson, i as UserConfigFnNoEnv, l as defineConfig, m as Theme, n as UserConfigExport, nt as WeappViteConfig, o as UserConfigFnObject, p as Sitemap, r as UserConfigFn, s as UserConfigFnObjectPlain, t as UserConfig, u as App, v as defineSitemapJson, y as defineThemeJson, zn as isWeappViteHost } from "./config-DdiGMnVs.mjs";
1
+ import { Bn as resolveWeappViteHostMeta, Fn as WeappViteHostMeta, In as WeappViteRuntime, Ln as applyWeappViteHostMeta, Pn as WEAPP_VITE_HOST_NAME, Rn as createWeappViteHostMeta, _ as definePageJson, a as UserConfigFnNoEnvPlain, c as UserConfigFnPromise, d as Component, f as Page, g as defineComponentJson, h as defineAppJson, i as UserConfigFnNoEnv, l as defineConfig, m as Theme, n as UserConfigExport, nt as WeappViteConfig, o as UserConfigFnObject, p as Sitemap, r as UserConfigFn, s as UserConfigFnObjectPlain, t as UserConfig, u as App, v as defineSitemapJson, y as defineThemeJson, zn as isWeappViteHost } from "./config-BEJTp6sp.mjs";
2
2
  export { App, Component, Page, Sitemap, Theme, UserConfig, UserConfigExport, UserConfigFn, UserConfigFnNoEnv, UserConfigFnNoEnvPlain, UserConfigFnObject, UserConfigFnObjectPlain, UserConfigFnPromise, WEAPP_VITE_HOST_NAME, WeappViteConfig, WeappViteHostMeta, WeappViteRuntime, applyWeappViteHostMeta, createWeappViteHostMeta, defineAppJson, defineComponentJson, defineConfig, definePageJson, defineSitemapJson, defineThemeJson, isWeappViteHost, resolveWeappViteHostMeta };