weapp-vite 7.4.0 → 7.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +51 -1
  2. package/bin/bootstrap.js +14 -1
  3. package/dist/adapters-CcHrsYaj.mjs +1 -0
  4. package/dist/analyze-ZJUvEdaY.mjs +248 -0
  5. package/dist/auto-import-components/resolvers.mjs +1 -2
  6. package/dist/auto-import-presets/wevu-router.mjs +1 -2
  7. package/dist/auto-import-presets/wevu.mjs +1 -2
  8. package/dist/auto-routes.mjs +2 -1
  9. package/dist/budget-BD7VqLCl.d.mts +178 -0
  10. package/dist/chunkStrategy-DRA5WpB-.mjs +1436 -0
  11. package/dist/cli-acceptance.d.mts +1 -0
  12. package/dist/cli-acceptance.mjs +21 -0
  13. package/dist/cli.mjs +1420 -1554
  14. package/dist/compatibility.mjs +1 -2
  15. package/dist/{config-Dbvj5yK7.d.mts → config-V0L-IAJf.d.mts} +87 -30
  16. package/dist/config.d.mts +2 -2
  17. package/dist/config.mjs +1 -1
  18. package/dist/{constants-WOsG3Eqd.mjs → constants-DEZdcsMn.mjs} +1 -1
  19. package/dist/{createContext-UOEgeYnA.mjs → createCompilerContextInstance-DJH_P_nZ.mjs} +31278 -32148
  20. package/dist/createContext-BqYzHhY9.mjs +12 -0
  21. package/dist/dashboard/analyze.d.mts +48 -0
  22. package/dist/dashboard/analyze.mjs +1 -0
  23. package/dist/dashboard/vite.d.mts +1 -1
  24. package/dist/dashboard-DvqF3Key.mjs +1842 -0
  25. package/dist/dashboard.d.mts +3 -2
  26. package/dist/dashboard.mjs +1 -2
  27. package/dist/dashboardViteBridge-Dy3nAD4m.mjs +299 -0
  28. package/dist/dev-fR5GiJRO.mjs +63 -0
  29. package/dist/devProvider-BGBkw28J.mjs +1050 -0
  30. package/dist/docs/README.md +51 -1
  31. package/dist/docs/ai-workflows.md +4 -0
  32. package/dist/docs/doctor.md +38 -0
  33. package/dist/docs/getting-started.md +3 -1
  34. package/dist/docs/index.md +3 -1
  35. package/dist/docs/mcp.md +94 -1
  36. package/dist/docs/testing.md +29 -2
  37. package/dist/docs/troubleshooting.md +8 -0
  38. package/dist/docs/uni-app-component-libraries.md +8 -7
  39. package/dist/docs/upload.md +40 -3
  40. package/dist/docs/vite-plugin.md +171 -0
  41. package/dist/docs/vue-sfc.md +19 -0
  42. package/dist/docs/weapp-config.md +54 -0
  43. package/dist/docs/wevu-authoring.md +10 -0
  44. package/dist/doctor-worker.d.mts +1 -0
  45. package/dist/doctor-worker.mjs +59 -0
  46. package/dist/doctor.d.mts +144 -0
  47. package/dist/doctor.mjs +848 -0
  48. package/dist/eslint.mjs +1 -2
  49. package/dist/file-DRb4eWqw.mjs +1 -0
  50. package/dist/{file-giE9UMZa.mjs → file-sC3jD7Fh.mjs} +15 -9
  51. package/dist/files-BqxFIPRp.mjs +45 -0
  52. package/dist/getInstance-DiGSzBNI.mjs +28 -0
  53. package/dist/getInstance-SiE6WZRh.mjs +1 -0
  54. package/dist/index-C9HjUX5c.d.mts +741 -0
  55. package/dist/index.d.mts +10 -8
  56. package/dist/index.mjs +3 -2
  57. package/dist/json.d.mts +1 -1
  58. package/dist/json.mjs +1 -2
  59. package/dist/lifecycle-BROTlwSL.mjs +320 -0
  60. package/dist/loadConfig-C3BQhz7F.mjs +59 -0
  61. package/dist/loadConfig-VGtBF4qm.mjs +1 -0
  62. package/dist/logger-DGn5iEVN.mjs +1 -0
  63. package/dist/{mcp-BG6TliEg.mjs → mcp-Cj8Maqo6.mjs} +16 -7
  64. package/dist/mcp-D3Yh24Cj.mjs +529 -0
  65. package/dist/mcp.d.mts +6 -3
  66. package/dist/mcp.mjs +1 -2
  67. package/dist/package-7swhyLmf.mjs +91 -0
  68. package/dist/path-doV773b7.mjs +103 -0
  69. package/dist/{platform-DTWs5QRi.mjs → platform-CnkySBKz.mjs} +1 -1
  70. package/dist/processCleanup-B6KA30Zc.mjs +77 -0
  71. package/dist/protocol-vg8HGJ4n.mjs +138 -0
  72. package/dist/{resolvers-icNxkpcy.mjs → resolvers-Dod-euqX.mjs} +4 -1
  73. package/dist/resolvers.mjs +1 -2
  74. package/dist/runtime.mjs +1 -2
  75. package/dist/session-PskhD-nZ.mjs +390 -0
  76. package/dist/test.d.mts +1 -0
  77. package/dist/test.mjs +201 -44
  78. package/dist/types.d.mts +2 -2
  79. package/dist/{upload-D0RcEWrq.mjs → upload-D4msblBf.mjs} +174 -15
  80. package/dist/upload-worker.mjs +136 -3
  81. package/dist/utils-CJSCgEj6.mjs +1094 -0
  82. package/dist/vite.d.mts +9 -0
  83. package/dist/vite.mjs +270 -0
  84. package/dist/volar.d.mts +2 -2
  85. package/dist/web-apis.mjs +1 -2
  86. package/package.json +67 -41
  87. package/dist/adapters-BwnMMIZR.mjs +0 -2
  88. package/dist/dashboard-BzaVesmQ.mjs +0 -558
  89. package/dist/file-D19nbDoN.mjs +0 -2
  90. package/dist/getInstance-CYF1vl5l.mjs +0 -2
  91. package/dist/index-DbqVupg0.d.mts +0 -246
  92. package/dist/logger-mt4mSTqV.mjs +0 -2
@@ -0,0 +1,390 @@
1
+ import { C as attachStatefulHmrHost, D as resolveHmrRuntimeDecision, E as createSharedBuildConfig, T as getStatefulHmrHost, _ as getPackNpmRelationList, b as createOxcRuntimeSupport, g as captureWatchDependencies, h as checkAppWorkersOptions, m as syncManagedTsconfigFiles, u as CompilerSession, v as createPackageBuilder, w as createStatefulHmrHostPlugins, y as settlePackageBuilds } from "./createCompilerContextInstance-DJH_P_nZ.mjs";
2
+ import { c as resolveRealpath } from "./protocol-vg8HGJ4n.mjs";
3
+ import { F as resolveNpmDistDirName, L as resolveTargetDependencies, M as resolveMainBuildDependencyPatterns, N as resolveNpmBuildCandidateDependenciesSync, f as attachDevModuleGraphHost } from "./devProvider-BGBkw28J.mjs";
4
+ import { v as loadHostRolldownBuild, y as pruneOwnedAssetFiles } from "./utils-CJSCgEj6.mjs";
5
+ import path from "pathe";
6
+ import { mkdtemp, readFile, readdir, rm } from "node:fs/promises";
7
+ import { build } from "vite";
8
+ import { createHash } from "node:crypto";
9
+ import { Buffer } from "node:buffer";
10
+ import os from "node:os";
11
+ //#region src/vite/npm/sourcemap.ts
12
+ const URL_SCHEME = /^[a-z][a-z\d+.-]*:/i;
13
+ const INLINE_MAP = /(\/\/[#@]\s*sourceMappingURL=data:application\/json(?:;charset=[^;,]*)?;base64,)([a-z\d+/=]+)/gi;
14
+ /** npm 镜像目录深度可能不同,重定位相对源码路径但保持 URL 与绝对来源不变。 */
15
+ function relocateNpmSourcemap(source, from, to) {
16
+ if (!from.endsWith(".map")) {
17
+ if (/\.[cm]?js$/.test(from)) return (typeof source === "string" ? source : Buffer.from(source).toString("utf8")).replace(INLINE_MAP, (_match, prefix, encoded) => {
18
+ const map = relocateNpmSourcemap(Buffer.from(encoded, "base64"), `${from}.map`, `${to}.map`);
19
+ return `${prefix}${Buffer.from(map).toString("base64")}`;
20
+ });
21
+ return source;
22
+ }
23
+ let value;
24
+ try {
25
+ value = JSON.parse(typeof source === "string" ? source : Buffer.from(source).toString("utf8"));
26
+ } catch {
27
+ return source;
28
+ }
29
+ if (!value || typeof value !== "object" || !("sources" in value) || !Array.isArray(value.sources) || !value.sources.every((item) => typeof item === "string")) return source;
30
+ const sourceRoot = "sourceRoot" in value && typeof value.sourceRoot === "string" ? value.sourceRoot : "";
31
+ if (URL_SCHEME.test(sourceRoot)) return source;
32
+ const sources = value.sources.map((item) => {
33
+ if (URL_SCHEME.test(item) || path.isAbsolute(item)) return item;
34
+ return path.relative(path.dirname(to), path.resolve(path.dirname(from), sourceRoot, item));
35
+ });
36
+ return JSON.stringify({
37
+ ...value,
38
+ sourceRoot: "",
39
+ sources
40
+ });
41
+ }
42
+ //#endregion
43
+ //#region src/vite/npm/output.ts
44
+ function relativeOutput(root, target) {
45
+ const relative = path.relative(root, target);
46
+ return relative !== ".." && !relative.startsWith("../") && !path.isAbsolute(relative) ? relative : void 0;
47
+ }
48
+ /** 依赖回调只决定逻辑路径,bundle 保持内存,原生包复制只进入本会话的临时目录。 */
49
+ function createNpmOutput(temporaryRoot, targetRoots) {
50
+ const directories = /* @__PURE__ */ new Map();
51
+ const roots = new Set(targetRoots);
52
+ const files = /* @__PURE__ */ new Map();
53
+ const watchFiles = /* @__PURE__ */ new Set();
54
+ function addFile(fileName, source) {
55
+ const existing = files.get(fileName);
56
+ if (existing !== void 0 && !Buffer.from(existing).equals(Buffer.from(source))) throw new Error(`[weapp-vite] 多个 npm 依赖产生了不同内容的同名文件:${fileName}`);
57
+ files.set(fileName, source);
58
+ }
59
+ const output = {
60
+ watchFile(fileName) {
61
+ watchFiles.add(fileName);
62
+ },
63
+ directory(outDir) {
64
+ roots.add(outDir);
65
+ const parent = [...directories.keys()].filter((root) => relativeOutput(root, outDir) !== void 0).sort((a, b) => b.length - a.length)[0];
66
+ if (parent) return path.join(directories.get(parent), relativeOutput(parent, outDir));
67
+ const directory = path.join(temporaryRoot, String(directories.size));
68
+ directories.set(outDir, directory);
69
+ return directory;
70
+ },
71
+ async bundle(options, outDir) {
72
+ roots.add(outDir);
73
+ const result = await build({
74
+ ...options,
75
+ build: {
76
+ ...options.build,
77
+ write: false,
78
+ watch: null
79
+ },
80
+ plugins: [
81
+ ...options.plugins ?? [],
82
+ captureWatchDependencies((file) => watchFiles.add(file)),
83
+ {
84
+ name: "weapp-vite:npm-inputs",
85
+ configResolved(config) {
86
+ for (const file of config.configFileDependencies) watchFiles.add(file);
87
+ },
88
+ buildEnd() {
89
+ for (const file of this.getModuleIds()) if (!file.includes("\0")) watchFiles.add(file.split("?")[0]);
90
+ }
91
+ }
92
+ ]
93
+ });
94
+ for (const bundle of Array.isArray(result) ? result : [result]) {
95
+ if (!("output" in bundle)) throw new Error("[weapp-vite] npm 子构建必须返回内存产物。");
96
+ for (const item of bundle.output) addFile(path.resolve(outDir, item.fileName), item.type === "chunk" ? item.code : item.source);
97
+ }
98
+ }
99
+ };
100
+ for (const root of targetRoots) output.directory(root);
101
+ async function collect(directory, logicalRoot) {
102
+ let entries;
103
+ try {
104
+ entries = await readdir(directory, { withFileTypes: true });
105
+ } catch (error) {
106
+ if (error.code === "ENOENT") return;
107
+ throw error;
108
+ }
109
+ for (const entry of entries) {
110
+ const source = path.join(directory, entry.name);
111
+ const destination = path.join(logicalRoot, entry.name);
112
+ if (entry.isDirectory()) await collect(source, destination);
113
+ else if (entry.isFile()) addFile(destination, await readFile(source));
114
+ else throw new Error(`[weapp-vite] npm 中间目录含有无法发布的文件类型:${destination}`);
115
+ }
116
+ }
117
+ return {
118
+ output,
119
+ async finish(hostOutDir, mainOutDir, mirrors) {
120
+ for (const [logicalRoot, directory] of directories) await collect(directory, logicalRoot);
121
+ const mainFiles = [...files].filter(([fileName]) => relativeOutput(mainOutDir, fileName) !== void 0);
122
+ for (const mirror of mirrors) {
123
+ roots.add(mirror);
124
+ for (const [fileName, source] of mainFiles) {
125
+ const mirroredFile = path.join(mirror, relativeOutput(mainOutDir, fileName));
126
+ addFile(mirroredFile, relocateNpmSourcemap(source, fileName, mirroredFile));
127
+ }
128
+ }
129
+ const result = {
130
+ assets: [],
131
+ external: /* @__PURE__ */ new Map(),
132
+ watchFiles: [...watchFiles]
133
+ };
134
+ for (const [absolutePath, source] of files) {
135
+ const relative = relativeOutput(hostOutDir, absolutePath);
136
+ if (relative !== void 0) {
137
+ result.assets.push({
138
+ type: "asset",
139
+ fileName: relative,
140
+ source
141
+ });
142
+ continue;
143
+ }
144
+ const directory = [...roots].filter((root) => relativeOutput(root, absolutePath) !== void 0).sort((a, b) => a.length - b.length)[0];
145
+ const assets = result.external.get(directory) ?? [];
146
+ assets.push({
147
+ type: "asset",
148
+ fileName: relativeOutput(directory, absolutePath),
149
+ source
150
+ });
151
+ result.external.set(directory, assets);
152
+ }
153
+ return result;
154
+ }
155
+ };
156
+ }
157
+ //#endregion
158
+ //#region src/vite/npm/index.ts
159
+ /** 复用依赖编译器和 npm 关联配置,最终输出统一交给宿主 emit/write。 */
160
+ async function prepareNpmAssets(ctx) {
161
+ const config = ctx.configService;
162
+ const empty = {
163
+ assets: [],
164
+ external: /* @__PURE__ */ new Map(),
165
+ watchFiles: []
166
+ };
167
+ if (config.weappLibConfig?.enabled || !config.weappViteConfig.npm?.enable) return empty;
168
+ const [main, ...mirrors] = getPackNpmRelationList(ctx, { defaultOutputRoot: config.outDir });
169
+ if (!main) return empty;
170
+ const packageJsonPath = path.resolve(config.cwd, main.packageJsonPath);
171
+ const resolveFrom = path.dirname(packageJsonPath);
172
+ let packageJson;
173
+ try {
174
+ packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
175
+ } catch (error) {
176
+ if (error.code === "ENOENT") return empty;
177
+ throw error;
178
+ }
179
+ const candidates = resolveNpmBuildCandidateDependenciesSync(ctx, packageJson, resolveFrom);
180
+ ctx.scanService.loadSubPackages();
181
+ const npmDirName = resolveNpmDistDirName(config);
182
+ const mainOutDir = path.resolve(config.cwd, main.miniprogramNpmDistDir, npmDirName);
183
+ const targets = [{
184
+ outDir: mainOutDir,
185
+ dependencies: resolveTargetDependencies(candidates, resolveMainBuildDependencyPatterns(ctx))
186
+ }, ...[...ctx.scanService.subPackageMap.values()].filter((entry) => entry.subPackage.dependencies?.length).map((entry) => ({
187
+ outDir: path.join(config.outDir, entry.subPackage.root, npmDirName),
188
+ dependencies: resolveTargetDependencies(candidates, entry.subPackage.dependencies)
189
+ }))];
190
+ const temporaryRoot = await mkdtemp(path.join(os.tmpdir(), "weapp-vite-npm-"));
191
+ const capture = createNpmOutput(temporaryRoot, targets.map((target) => target.outDir));
192
+ capture.output.watchFile?.(packageJsonPath);
193
+ const builder = createPackageBuilder(ctx, createOxcRuntimeSupport().vitePlugin, capture.output);
194
+ try {
195
+ for (const target of targets) await settlePackageBuilds(target.dependencies.map((dep) => builder.buildPackage({
196
+ dep,
197
+ resolveFrom,
198
+ outDir: target.outDir,
199
+ isDependenciesCacheOutdate: true
200
+ })));
201
+ const result = await capture.finish(config.outDir, mainOutDir, mirrors.map((relation) => path.resolve(config.cwd, relation.miniprogramNpmDistDir, npmDirName)));
202
+ if (result.external.size && config.inlineConfig.build?.write === false) throw new Error("[weapp-vite] build.write=false 无法表示宿主输出目录之外的 npm 产物;请使用 write=true 或将 npm 输出映射到宿主 outDir 内。");
203
+ return result;
204
+ } finally {
205
+ await rm(temporaryRoot, {
206
+ recursive: true,
207
+ force: true
208
+ });
209
+ }
210
+ }
211
+ //#endregion
212
+ //#region src/vite/npm/publish.ts
213
+ /** 手工映射到主目录外的依赖仍由宿主配套引擎写出,只清理上轮实际拥有的文件。 */
214
+ async function publishNpmAssets(outDir, assets, obsolete) {
215
+ const virtualEntry = "\0weapp-vite-npm-output";
216
+ const plugin = {
217
+ name: "weapp-vite:npm-output-writer",
218
+ resolveId: (id) => id === virtualEntry ? id : void 0,
219
+ load: (id) => id === virtualEntry ? "export {}" : void 0,
220
+ buildStart() {
221
+ for (const asset of assets) this.emitFile(asset);
222
+ },
223
+ generateBundle(_options, bundle) {
224
+ for (const [fileName, item] of Object.entries(bundle)) if (item.type === "chunk" && item.facadeModuleId === virtualEntry) delete bundle[fileName];
225
+ },
226
+ async writeBundle() {
227
+ await pruneOwnedAssetFiles(outDir, obsolete);
228
+ }
229
+ };
230
+ const { rolldown } = await loadHostRolldownBuild();
231
+ const bundle = await rolldown({
232
+ input: virtualEntry,
233
+ logLevel: "silent",
234
+ plugins: [plugin]
235
+ });
236
+ try {
237
+ await bundle.write({
238
+ dir: outDir,
239
+ format: "es"
240
+ });
241
+ } finally {
242
+ await bundle.close();
243
+ }
244
+ }
245
+ //#endregion
246
+ //#region src/vite/npm/ownership.ts
247
+ async function readOwnership(fileName) {
248
+ let value;
249
+ try {
250
+ value = JSON.parse(await readFile(fileName, "utf8"));
251
+ } catch (error) {
252
+ if (error.code === "ENOENT") return /* @__PURE__ */ new Map();
253
+ throw error;
254
+ }
255
+ if (!Array.isArray(value) || !value.every((entry) => Array.isArray(entry) && entry.length === 2 && typeof entry[0] === "string" && Array.isArray(entry[1]) && entry[1].every((file) => typeof file === "string"))) throw new Error("[weapp-vite] npm 输出归属记录损坏,请移除 .weapp-vite/native-npm-output 后重建。");
256
+ return new Map(value);
257
+ }
258
+ /** 保存外部产物的实际归属,使独立 build 也能删除旧依赖而保留非构建文件。 */
259
+ async function publishOwnedNpmAssets(cwd, hostOutDir, external) {
260
+ const key = createHash("sha256").update(hostOutDir).digest("hex").slice(0, 20);
261
+ const metadataRoot = path.join(cwd, ".weapp-vite/native-npm-output");
262
+ const metadataName = `${key}.json`;
263
+ const previous = await readOwnership(path.join(metadataRoot, metadataName));
264
+ const current = /* @__PURE__ */ new Map();
265
+ const currentFiles = /* @__PURE__ */ new Set();
266
+ for (const [directory, assets] of external) if (assets.length) {
267
+ const names = assets.map((asset) => asset.fileName);
268
+ current.set(directory, names);
269
+ for (const file of names) currentFiles.add(path.resolve(directory, file));
270
+ }
271
+ for (const directory of /* @__PURE__ */ new Set([...external.keys(), ...previous.keys()])) await publishNpmAssets(directory, external.get(directory) ?? [], (previous.get(directory) ?? []).filter((file) => !currentFiles.has(path.resolve(directory, file))));
272
+ if (current.size || previous.size) await publishNpmAssets(metadataRoot, [{
273
+ type: "asset",
274
+ fileName: metadataName,
275
+ source: JSON.stringify([...current])
276
+ }], []);
277
+ }
278
+ //#endregion
279
+ //#region src/vite/session.ts
280
+ /** 标准插件的目标校验和依赖准备,共享底层编译会话生命周期。 */
281
+ var WeappBuildSession = class extends CompilerSession {
282
+ dependencyBuild;
283
+ validating;
284
+ isWeb = false;
285
+ statefulController;
286
+ async prepare(config, cwd, mode, isDev = false) {
287
+ if (!config.resolve?.preserveSymlinks) try {
288
+ cwd = resolveRealpath(cwd);
289
+ } catch {}
290
+ this.isWeb = config.weapp?.platform === "web";
291
+ await this.initialize({
292
+ cwd,
293
+ mode,
294
+ isDev,
295
+ emitDefaultAutoImportOutputs: false,
296
+ hostConfig: { config: {
297
+ ...config,
298
+ root: cwd
299
+ } },
300
+ cliPlatform: this.isWeb ? "web" : void 0,
301
+ syncSupportFiles: false,
302
+ preloadAppEntry: false
303
+ });
304
+ const service = this.context.configService;
305
+ if (this.isWeb) {
306
+ const merged = service.mergeWeb();
307
+ if (!merged) throw new Error("[weapp-vite] Web 目标不能禁用 weapp.web。");
308
+ return merged;
309
+ }
310
+ if (isDev && resolveHmrRuntimeDecision({
311
+ platform: service.platform,
312
+ configured: service.weappViteConfig.hmr?.runtime,
313
+ compileHotReLoad: service.projectPrivateConfig.setting?.compileHotReLoad
314
+ }).runtime === "stateful-experimental") {
315
+ if (service.platform !== "weapp") throw new Error("[weapp-vite] stateful-experimental 仅支持微信;其他平台请配置 hmr.runtime=classic。");
316
+ this.statefulController = createStatefulHmrHostPlugins(this.context);
317
+ }
318
+ return this.context.configService.merge(void 0, createSharedBuildConfig(this.context.configService, this.context.scanService));
319
+ }
320
+ validateEntries() {
321
+ if (this.validating && !this.isClosing) return this.validating;
322
+ if (this.state !== "ready" && this.state !== "building") return Promise.reject(/* @__PURE__ */ new Error(`[weapp-vite] 无法从 ${this.state} 状态开始构建。`));
323
+ this.dependencyBuild = void 0;
324
+ return this.validating = this.run(async () => {
325
+ if (!this.isWeb && !this.context.configService.weappLibConfig?.enabled) {
326
+ const app = await this.context.scanService.loadAppEntry();
327
+ checkAppWorkersOptions("app", this.context.configService, app);
328
+ }
329
+ if (this.isClosing) throw new Error("[weapp-vite] 构建会话已关闭。");
330
+ await syncManagedTsconfigFiles(this.context);
331
+ if (this.isClosing) throw new Error("[weapp-vite] 构建会话已关闭。");
332
+ this.state = "building";
333
+ }).finally(() => {
334
+ this.validating = void 0;
335
+ });
336
+ }
337
+ async startDev(server) {
338
+ if (this.isWeb) {
339
+ await this.run(() => syncManagedTsconfigFiles(this.context));
340
+ this.state = "watching";
341
+ return;
342
+ }
343
+ let started = false;
344
+ let successful = false;
345
+ let reportedReady = false;
346
+ const reportReady = () => {
347
+ if (started && successful && !reportedReady && !this.isClosing) {
348
+ reportedReady = true;
349
+ server.config.logger.info(`[weapp-vite] 小程序开发产物已就绪 (${this.statefulController ? "stateful-experimental" : "classic"})`);
350
+ }
351
+ };
352
+ if (this.statefulController) this.onClose(attachStatefulHmrHost(this.context, {
353
+ server,
354
+ controller: this.statefulController
355
+ }));
356
+ this.onClose(attachDevModuleGraphHost(this.context, server, (result) => {
357
+ successful = result;
358
+ reportReady();
359
+ }));
360
+ this.onClose(() => this.context.watcherService.closeAll());
361
+ await this.validateEntries();
362
+ await this.run(() => this.context.buildService.build({}));
363
+ if (!this.isClosing) {
364
+ this.state = "watching";
365
+ if (this.statefulController) successful = true;
366
+ started = true;
367
+ reportReady();
368
+ }
369
+ }
370
+ async refreshHostControl() {
371
+ await getStatefulHmrHost(this.context)?.refreshControl?.();
372
+ }
373
+ buildDependencies() {
374
+ if (this.isWeb) return Promise.resolve({
375
+ assets: [],
376
+ external: /* @__PURE__ */ new Map(),
377
+ watchFiles: []
378
+ });
379
+ if (this.state !== "building") throw new Error("[weapp-vite] 依赖构建需要活动的构建会话。");
380
+ return this.dependencyBuild ??= this.run(() => prepareNpmAssets(this.context));
381
+ }
382
+ async publishDependencies() {
383
+ if (!this.dependencyBuild) return;
384
+ const { external } = await this.dependencyBuild;
385
+ const { cwd, outDir } = this.context.configService;
386
+ await this.run(() => publishOwnedNpmAssets(cwd, outDir, external));
387
+ }
388
+ };
389
+ //#endregion
390
+ export { WeappBuildSession };
package/dist/test.d.mts CHANGED
@@ -22,6 +22,7 @@ export interface WeappViteTestArtifactWatcher {
22
22
  close: () => Promise<void>;
23
23
  rebuild: () => Promise<WeappViteTestArtifact>;
24
24
  }
25
+ export declare function isTestArtifactCurrent(artifact: WeappViteTestArtifact): Promise<boolean>;
25
26
  export declare function buildTestArtifact(options?: BuildTestArtifactOptions): Promise<{
26
27
  appConfigPath: string;
27
28
  miniprogramRootPath: string;
package/dist/test.mjs CHANGED
@@ -1,82 +1,239 @@
1
- import { t as createCompilerContext } from "./createContext-UOEgeYnA.mjs";
2
- import path from "node:path";
1
+ import { g as captureWatchDependencies, u as CompilerSession } from "./createCompilerContextInstance-DJH_P_nZ.mjs";
2
+ import { a as parseSidecarModuleId, i as parseLogicalEntryId } from "./protocol-vg8HGJ4n.mjs";
3
3
  import process from "node:process";
4
+ import path from "node:path";
5
+ import fs from "node:fs/promises";
6
+ import { createHash, randomUUID } from "node:crypto";
4
7
  import chokidar from "chokidar";
8
+ //#region src/testArtifact/inputs.ts
9
+ const sessionId = randomUUID();
10
+ const excludedDirectories = /* @__PURE__ */ new Set([
11
+ ".git",
12
+ ".weapp-vite",
13
+ "node_modules"
14
+ ]);
15
+ function resolveArtifactOutDir(cwd, options, generation = randomUUID()) {
16
+ if (options.outDir) return path.resolve(cwd, options.outDir);
17
+ const key = createHash("sha256").update(JSON.stringify({
18
+ configFile: options.configFile && path.resolve(cwd, options.configFile),
19
+ mode: options.mode ?? "test",
20
+ projectConfigPath: options.projectConfigPath && path.resolve(cwd, options.projectConfigPath),
21
+ skipNpm: options.skipNpm ?? false
22
+ })).digest("hex").slice(0, 16);
23
+ return path.join(cwd, ".weapp-vite/test-artifacts", `${process.pid}-${sessionId}`, key, generation);
24
+ }
25
+ function projectInputFiles(cwd) {
26
+ return [
27
+ ...[
28
+ "vite.config",
29
+ "weapp-vite.config",
30
+ "vitest.config"
31
+ ].flatMap((name) => [
32
+ "ts",
33
+ "mts",
34
+ "cts",
35
+ "js",
36
+ "mjs",
37
+ "cjs"
38
+ ].map((ext) => `${name}.${ext}`)),
39
+ "package.json",
40
+ "pnpm-lock.yaml",
41
+ "package-lock.json",
42
+ "yarn.lock",
43
+ "tsconfig.json",
44
+ "project.config.json"
45
+ ].map((file) => path.join(cwd, file));
46
+ }
47
+ /** 对真实输入及目录成员取内容摘要,覆盖同长度改写、新增路由和删除文件。 */
48
+ async function fingerprintPaths(paths, excluded = []) {
49
+ const hash = createHash("sha256");
50
+ const visited = /* @__PURE__ */ new Set();
51
+ const ignored = excluded.map((file) => path.resolve(file));
52
+ const visit = async (file) => {
53
+ if (ignored.some((root) => file === root || file.startsWith(`${root}${path.sep}`))) return;
54
+ hash.update(`${file}\0`);
55
+ try {
56
+ if ((await fs.stat(file)).isDirectory()) {
57
+ const real = await fs.realpath(file);
58
+ if (visited.has(real)) return;
59
+ visited.add(real);
60
+ const entries = await fs.readdir(file, { withFileTypes: true });
61
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) if (!excludedDirectories.has(entry.name)) await visit(path.join(file, entry.name));
62
+ } else hash.update(await fs.readFile(file));
63
+ } catch (error) {
64
+ if (error.code !== "ENOENT") throw error;
65
+ hash.update("missing");
66
+ }
67
+ hash.update("\0");
68
+ };
69
+ for (const file of [...new Set(paths)].sort()) await visit(path.resolve(file));
70
+ return hash.digest("hex");
71
+ }
72
+ //#endregion
5
73
  //#region src/testArtifact.ts
6
74
  let pendingBuild = Promise.resolve();
75
+ const artifactInputs = /* @__PURE__ */ new WeakMap();
7
76
  function enqueueBuild(task) {
8
77
  const result = pendingBuild.then(task, task);
9
78
  pendingBuild = result.then(() => void 0, () => void 0);
10
79
  return result;
11
80
  }
12
- function resolveTestOutDir(cwd, outDir) {
13
- return path.resolve(cwd, outDir ?? ".weapp-vite/test-artifacts");
81
+ async function isTestArtifactCurrent(artifact) {
82
+ const inputs = artifactInputs.get(artifact);
83
+ return inputs !== void 0 && inputs.fingerprint === await fingerprintPaths(inputs.paths);
14
84
  }
15
85
  async function buildTestArtifact(options = {}) {
16
86
  return await enqueueBuild(async () => {
17
- const cwd = path.resolve(options.cwd ?? process.cwd());
18
- const outDir = resolveTestOutDir(cwd, options.outDir);
19
- const inlineConfig = { build: {
20
- emptyOutDir: true,
21
- outDir
22
- } };
23
- const ctx = await createCompilerContext({
24
- cwd,
25
- mode: options.mode ?? "test",
26
- isDev: false,
27
- configFile: options.configFile,
28
- inlineConfig,
29
- outputRoot: outDir,
30
- projectConfigPath: options.projectConfigPath,
31
- emitDefaultAutoImportOutputs: false,
32
- preloadAppEntry: false,
33
- syncSupportFiles: false
34
- });
35
- await ctx.buildService.build({ skipNpm: options.skipNpm });
36
- return {
37
- appConfigPath: path.join(ctx.configService.outDir, "app.json"),
38
- miniprogramRootPath: ctx.configService.outDir,
39
- projectPath: cwd,
40
- sourceRootPath: ctx.configService.absoluteSrcRoot
41
- };
87
+ const session = new CompilerSession();
88
+ try {
89
+ const cwd = path.resolve(options.cwd ?? process.cwd());
90
+ const outDir = resolveArtifactOutDir(cwd, options);
91
+ const dependencies = new Set(projectInputFiles(cwd));
92
+ const discoveredInputs = /* @__PURE__ */ new Map();
93
+ const inlineConfig = {
94
+ plugins: [captureWatchDependencies((file) => {
95
+ dependencies.add(file);
96
+ if (!discoveredInputs.has(file)) discoveredInputs.set(file, fingerprintPaths([file]));
97
+ }), {
98
+ name: "weapp-vite:test-artifact-inputs",
99
+ generateBundle() {
100
+ for (const file of this.getModuleIds()) {
101
+ const clean = (parseLogicalEntryId(file)?.sourceId ?? parseSidecarModuleId(file)?.ownerId ?? file).split("?")[0];
102
+ if (path.isAbsolute(clean)) {
103
+ dependencies.add(clean);
104
+ for (const dependency of session.context.moduleGraphService.getTransformDependencies(clean)) dependencies.add(dependency);
105
+ for (const dependency of session.context.moduleGraphService.getEntryDependencies(clean)) dependencies.add(dependency.sourceId);
106
+ }
107
+ }
108
+ }
109
+ }],
110
+ build: {
111
+ emptyOutDir: true,
112
+ outDir
113
+ }
114
+ };
115
+ const ctx = await session.initialize({
116
+ cwd,
117
+ mode: options.mode ?? "test",
118
+ isDev: false,
119
+ configFile: options.configFile,
120
+ inlineConfig,
121
+ outputRoot: outDir,
122
+ projectConfigPath: options.projectConfigPath,
123
+ emitDefaultAutoImportOutputs: false,
124
+ preloadAppEntry: false,
125
+ syncSupportFiles: false
126
+ });
127
+ for (const file of ctx.configService.configFileDependencies ?? []) dependencies.add(file);
128
+ if (options.projectConfigPath) dependencies.add(path.resolve(cwd, options.projectConfigPath));
129
+ dependencies.add(ctx.configService.absoluteSrcRoot);
130
+ const initialInputs = [...dependencies];
131
+ const before = await fingerprintPaths(initialInputs, [outDir]);
132
+ await session.run(() => ctx.buildService.build({ skipNpm: options.skipNpm }));
133
+ const artifact = {
134
+ appConfigPath: path.join(ctx.configService.outDir, "app.json"),
135
+ miniprogramRootPath: ctx.configService.outDir,
136
+ projectPath: cwd,
137
+ sourceRootPath: ctx.configService.absoluteSrcRoot
138
+ };
139
+ const paths = [...dependencies, outDir];
140
+ const stable = (await Promise.all([...discoveredInputs].map(async ([file, fingerprint]) => await fingerprint === await fingerprintPaths([file])))).every(Boolean) && before === await fingerprintPaths(initialInputs, [outDir]);
141
+ artifactInputs.set(artifact, {
142
+ paths,
143
+ fingerprint: stable ? await fingerprintPaths(paths) : ""
144
+ });
145
+ return artifact;
146
+ } finally {
147
+ await session.close();
148
+ }
42
149
  });
43
150
  }
44
151
  async function watchTestArtifact(options = {}) {
45
152
  let artifact = await buildTestArtifact(options);
46
153
  let closed = false;
47
154
  let scheduled;
48
- const rebuild = async () => {
49
- if (closed) throw new Error("The weapp-vite test artifact watcher has already closed.");
50
- artifact = await buildTestArtifact(options);
51
- await options.onRebuilt?.(artifact);
52
- return artifact;
155
+ let active;
156
+ let dirty = false;
157
+ let forced = false;
158
+ let closing;
159
+ const watchedInputs = () => artifactInputs.get(artifact).paths.filter((file) => file !== artifact.miniprogramRootPath && (file === artifact.sourceRootPath || !file.startsWith(`${artifact.sourceRootPath}${path.sep}`)));
160
+ let watched = watchedInputs();
161
+ const watcher = chokidar.watch(watched, {
162
+ ignoreInitial: true,
163
+ ignored(file) {
164
+ if (watched.some((input) => input === file || input.startsWith(`${file}${path.sep}`))) return false;
165
+ return /[/\\](?:node_modules|\.git|\.weapp-vite)(?:[/\\]|$)/.test(file) || file === artifact.miniprogramRootPath || file.startsWith(`${artifact.miniprogramRootPath}${path.sep}`);
166
+ }
167
+ });
168
+ const requestRebuild = (force) => {
169
+ if (closed) return Promise.reject(/* @__PURE__ */ new Error("The weapp-vite test artifact watcher has already closed."));
170
+ dirty = true;
171
+ forced ||= force;
172
+ if (!active) active = (async () => {
173
+ do {
174
+ dirty = false;
175
+ const forceBuild = forced;
176
+ forced = false;
177
+ if (!forceBuild && await isTestArtifactCurrent(artifact)) continue;
178
+ if (closed) break;
179
+ artifact = await buildTestArtifact(options);
180
+ if (!closed) {
181
+ const next = watchedInputs();
182
+ await watcher.unwatch(watched.filter((file) => !next.includes(file)));
183
+ watched = next;
184
+ watcher.add(watched);
185
+ await options.onRebuilt?.(artifact);
186
+ }
187
+ if (closed) break;
188
+ } while (dirty);
189
+ return artifact;
190
+ })().finally(() => {
191
+ active = void 0;
192
+ });
193
+ return active;
53
194
  };
54
195
  const scheduleRebuild = () => {
55
196
  if (closed || scheduled) return;
56
197
  scheduled = setTimeout(() => {
57
198
  scheduled = void 0;
58
- rebuild().catch((error) => options.onError?.(error));
199
+ requestRebuild(false).catch((error) => {
200
+ if (!closed) options.onError?.(error);
201
+ });
59
202
  }, 20);
60
203
  };
61
- const watcher = chokidar.watch(artifact.sourceRootPath, {
62
- ignoreInitial: true,
63
- ignored: [artifact.miniprogramRootPath]
64
- });
65
204
  watcher.on("add", scheduleRebuild);
66
205
  watcher.on("change", scheduleRebuild);
67
206
  watcher.on("unlink", scheduleRebuild);
207
+ watcher.on("error", (error) => options.onError?.(error));
208
+ try {
209
+ await new Promise((resolve, reject) => {
210
+ watcher.once("ready", resolve);
211
+ watcher.once("error", reject);
212
+ });
213
+ if (!await isTestArtifactCurrent(artifact)) await requestRebuild(false);
214
+ } catch (error) {
215
+ closed = true;
216
+ if (scheduled) clearTimeout(scheduled);
217
+ await watcher.close();
218
+ await active?.catch(() => void 0);
219
+ throw error;
220
+ }
68
221
  return {
69
222
  get artifact() {
70
223
  return artifact;
71
224
  },
72
- async close() {
73
- if (closed) return;
225
+ close() {
226
+ if (closing) return closing;
74
227
  closed = true;
75
228
  if (scheduled) clearTimeout(scheduled);
76
- await watcher.close();
229
+ closing = (async () => {
230
+ await watcher.close();
231
+ await active?.catch(() => void 0);
232
+ })();
233
+ return closing;
77
234
  },
78
- rebuild
235
+ rebuild: () => requestRebuild(true)
79
236
  };
80
237
  }
81
238
  //#endregion
82
- export { buildTestArtifact, watchTestArtifact };
239
+ export { buildTestArtifact, isTestArtifactCurrent, watchTestArtifact };