vite-plugin-taro 0.5.13 → 0.5.14

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 (71) hide show
  1. package/dist/node/plugins/client/client-taro.d.ts +1 -1
  2. package/dist/node/plugins/client/client-taro.js +2 -1
  3. package/dist/node/plugins/h5/create-stencil-client-adapter.d.ts +1 -1
  4. package/dist/node/plugins/h5/create-stencil-client-adapter.js +2 -1
  5. package/dist/node/plugins/h5/resolver/module-resolver.js +2 -1
  6. package/dist/node/plugins/wx/dev/create-hmr-results-stream.d.ts +23 -0
  7. package/dist/node/plugins/wx/dev/create-hmr-results-stream.js +57 -0
  8. package/dist/node/plugins/wx/dev/create-style-capture.d.ts +42 -0
  9. package/dist/node/plugins/wx/dev/create-style-capture.js +125 -0
  10. package/dist/node/plugins/wx/dev/dev-host.d.ts +1 -1
  11. package/dist/node/plugins/wx/dev/dev-host.js +151 -93
  12. package/dist/node/plugins/wx/dev/hmr-files.d.ts +1 -0
  13. package/dist/node/plugins/wx/dev/hmr-files.js +1 -0
  14. package/dist/node/plugins/wx/dev/host-actions.d.ts +16 -0
  15. package/dist/node/plugins/wx/dev/host-actions.js +58 -0
  16. package/dist/node/plugins/wx/dev/patch-publisher.d.ts +2 -2
  17. package/dist/node/plugins/wx/dev/patch-publisher.js +17 -1
  18. package/dist/node/plugins/wx/dev/plugins.d.ts +4 -0
  19. package/dist/node/plugins/wx/dev/plugins.js +24 -8
  20. package/dist/node/plugins/wx/dev/react-refresh.js +6 -2
  21. package/dist/node/plugins/wx/dev/runtime-reports.d.ts +27 -0
  22. package/dist/node/plugins/wx/dev/runtime-reports.js +45 -0
  23. package/dist/node/plugins/wx/dev/wx-dev-options.d.ts +7 -3
  24. package/dist/node/plugins/wx/dev/wx-dev-options.js +46 -28
  25. package/dist/node/plugins/wx/module.d.ts +1 -1
  26. package/dist/node/plugins/wx/module.js +2 -1
  27. package/dist/node/plugins/wx/native/compile-native-component-interface.js +2 -1
  28. package/dist/node/plugins/wx/native/create-native-component-output.js +2 -0
  29. package/dist/node/plugins/wx/native/native-component-assets.d.ts +1 -1
  30. package/dist/node/plugins/wx/native/native-component-assets.js +3 -3
  31. package/dist/node/plugins/wx/native/native-component-interface.js +2 -1
  32. package/dist/node/plugins/wx/resolve/resolver.js +3 -2
  33. package/dist/node/plugins/wx/styles/plugins.js +20 -1
  34. package/dist/node/utils/modules.d.ts +1 -1
  35. package/dist/node/utils/modules.js +4 -3
  36. package/dist/node/utils/packages.js +2 -2
  37. package/package.json +4 -3
  38. package/src/node/plugins/client/client-taro.ts +2 -2
  39. package/src/node/plugins/h5/create-stencil-client-adapter.ts +2 -2
  40. package/src/node/plugins/h5/resolver/module-resolver.ts +2 -1
  41. package/src/node/plugins/wx/dev/create-hmr-results-stream.ts +71 -0
  42. package/src/node/plugins/wx/dev/create-style-capture.ts +179 -0
  43. package/src/node/plugins/wx/dev/dev-host.ts +177 -124
  44. package/src/node/plugins/wx/dev/hmr-files.ts +1 -0
  45. package/src/node/plugins/wx/dev/host-actions.ts +88 -0
  46. package/src/node/plugins/wx/dev/patch-publisher.ts +19 -2
  47. package/src/node/plugins/wx/dev/plugins.ts +25 -8
  48. package/src/node/plugins/wx/dev/react-refresh.ts +6 -2
  49. package/src/node/plugins/wx/dev/runtime-reports.ts +70 -0
  50. package/src/node/plugins/wx/dev/wx-dev-options.ts +50 -36
  51. package/src/node/plugins/wx/module.ts +2 -4
  52. package/src/node/plugins/wx/native/compile-native-component-interface.ts +2 -1
  53. package/src/node/plugins/wx/native/create-native-component-output.ts +2 -0
  54. package/src/node/plugins/wx/native/native-component-assets.ts +3 -4
  55. package/src/node/plugins/wx/native/native-component-interface.ts +2 -1
  56. package/src/node/plugins/wx/resolve/resolver.ts +3 -2
  57. package/src/node/plugins/wx/styles/plugins.ts +21 -1
  58. package/src/node/utils/modules.ts +4 -3
  59. package/src/node/utils/packages.ts +2 -2
  60. package/dist/node/plugins/wx/dev/create-wx-dev-mode.d.ts +0 -6
  61. package/dist/node/plugins/wx/dev/create-wx-dev-mode.js +0 -9
  62. package/dist/node/plugins/wx/dev/styles/create-style-capture-plugin.d.ts +0 -12
  63. package/dist/node/plugins/wx/dev/styles/create-style-capture-plugin.js +0 -21
  64. package/dist/node/plugins/wx/dev/styles/publish-style-hmr.d.ts +0 -27
  65. package/dist/node/plugins/wx/dev/styles/publish-style-hmr.js +0 -48
  66. package/dist/node/utils/serialized-task-queue.d.ts +0 -13
  67. package/dist/node/utils/serialized-task-queue.js +0 -25
  68. package/src/node/plugins/wx/dev/create-wx-dev-mode.ts +0 -14
  69. package/src/node/plugins/wx/dev/styles/create-style-capture-plugin.ts +0 -34
  70. package/src/node/plugins/wx/dev/styles/publish-style-hmr.ts +0 -71
  71. package/src/node/utils/serialized-task-queue.ts +0 -32
@@ -2,14 +2,16 @@ import { readFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import colors from 'picocolors';
4
4
  import { dev } from 'rolldown/experimental';
5
- import { isCSSRequest } from 'vite';
6
- import { SerializedTaskQueue } from '../../../utils/serialized-task-queue.js';
7
- import { createGraphStylePlan, isGlobalStyleRequest } from '../styles/utils.js';
5
+ import { asyncScheduler } from 'rxjs';
6
+ import { createHmrResultsStream } from './create-hmr-results-stream.js';
7
+ import { createStyleCapture } from './create-style-capture.js';
8
8
  import { developmentAppWxssFileName, hmrControlPath, hmrInfoFileName, hmrPatchesFileName, renderDevelopmentAppWxss, renderHmrInfo, renderInitialHmrPatches, writeHmrFile } from './hmr-files.js';
9
+ import { createHostActions } from './host-actions.js';
9
10
  import { PatchPublisher } from './patch-publisher.js';
10
- import { createStyleCapturePlugin } from './styles/create-style-capture-plugin.js';
11
- import { globalWxssFileName, publishStyleHmr, refreshTailwindStyles } from './styles/publish-style-hmr.js';
11
+ import { createRuntimeReportsStream } from './runtime-reports.js';
12
12
  import { installWxDevOptions, requireSingleOutput } from './wx-dev-options.js';
13
+ /** One short trailing-edge window absorbs editor bursts before style preparation and physical Page notification. */
14
+ const hmrSettleMilliseconds = 32;
13
15
  /**
14
16
  * Creates the wx dev host: the adapter that owns the physical Rolldown DevEngine (created
15
17
  * with dev(...)) and the patch publisher, and replaces Vite's bundledDev.listen so the
@@ -21,45 +23,70 @@ import { installWxDevOptions, requireSingleOutput } from './wx-dev-options.js';
21
23
  */
22
24
  export async function createWxDevHost({ server, options, applicationEntryIds }) {
23
25
  const bundledDev = getBundledDev(server);
24
- // This is the host's mutable style projection: CSS absent from Rolldown, the live graph capability rebound by each
25
- // complete build, and the last durable WXSS bytes used only to suppress identical filesystem publications. Style capture
26
- // remains O(1); derived order and reachability stay local to each HMR transaction.
27
- const styleState = {
28
- getModuleInfo: undefined,
29
- processedStyles: new Map(),
30
- publishedWxss: undefined
31
- };
32
- const styleCapturePlugin = createStyleCapturePlugin({
33
- captureGraph(reader) {
34
- styleState.getModuleInfo = reader;
35
- },
36
- captureStyle(id, style) {
37
- styleState.processedStyles.set(id, style);
26
+ // All callbacks admit typed actions through this edge; concatMap is the sole owner of effect ordering and mutable host state.
27
+ const hostActions = createHostActions(applyHostAction, (action, error) => logWxError(server.config.logger, `wx HMR ${action.kind} failed`, error));
28
+ const styleCapture = createStyleCapture({
29
+ applicationEntryIds: applicationEntryIds,
30
+ outDir: server.config.build.outDir,
31
+ // Capture hooks run only after engine.run, when the host action subscription and all reducer dependencies are ready.
32
+ emit: (action) => hostActions.next(action),
33
+ transformTailwindRoot: async (rootId, requestId) => {
34
+ // The capture contains generated CSS, while the sidecar must re-run from raw Tailwind directives. Vite exposes no
35
+ // raw source in its live module graph, so this read is the authoritative source generation rather than a read-back.
36
+ return server.environments.client.pluginContainer.transform(await readFile(rootId, 'utf8'), requestId);
38
37
  }
39
38
  });
40
- // Rolldown invokes output callbacks without awaiting their promises. This queue is the single owner of mutable HMR host
41
- // state and physical metadata writes, preventing a later patch or build identity from being overwritten by older work.
42
- const hostTasks = new SerializedTaskQueue((operation, error) => logWxError(server.config.logger, operation, error));
43
39
  const publisher = new PatchPublisher((content) => writeHmrFile(server.config.build.outDir, hmrPatchesFileName, content));
44
- // DevEngine does not reject run() after an initial plugin failure. The options layer owns a first-build buildEnd barrier
45
- // and exposes only its result; later build errors continue independently through onOutput and onHmrUpdates.
46
- const initialBuild = installWxDevOptions({ bundledDev, server, options, hostPlugins: [styleCapturePlugin] });
40
+ // Option installation now configures only Rolldown. Build lifecycle results enter through the engine's output action below.
41
+ installWxDevOptions({ bundledDev, server, options, hostPlugins: [styleCapture.plugin] });
47
42
  const engine = await createEngine();
48
- // The wx dev host owns the only DevEngine. Vite's default listen() would create a second
49
- // skip-write engine that renders into memory for browser HMR; instead the physical
50
- // engine's initial build must finish before the HTTP server becomes ready, because
51
- // DevTools opens the output directory directly and the app requires its files on disk.
43
+ const hmrResults = createHmrResultsStream(hmrSettleMilliseconds, asyncScheduler, (result) => {
44
+ // One reduced window becomes one existing host transaction: style preparation, one cumulative patch write, then
45
+ // ordered delivery notifications for every original Rolldown payload retained by the stream.
46
+ hostActions.next({ kind: 'publish', result: result });
47
+ }, (error) => {
48
+ /*
49
+ * Invalid syntax is a normal intermediate editor generation. Rolldown produced no patch, so neither the published
50
+ * nor applied runtime frontier needs repair. Triggering a complete build here would immediately compile the same
51
+ * invalid source and empirically wedges that DevEngine generation. Admit only the diagnostic; when the user saves
52
+ * valid source, the watch engine emits an ordinary successful callback and resumes the normal publication path.
53
+ */
54
+ hostActions.next({ kind: 'error', error: error });
55
+ });
56
+ const runtimeReports = createRuntimeReportsStream(hmrSettleMilliseconds, asyncScheduler, (reports) => {
57
+ // ACK conflation happens before admission, but publisher validation and mutation remain ordered with patch writes and
58
+ // build rotation. Reports arriving during publication therefore execute only after that physical transaction commits.
59
+ hostActions.next({ kind: 'reports', reports: reports });
60
+ });
61
+ /*
62
+ * BundledDev is Vite's mutable environment adapter. Transfer its engine slot to the one physical writer created above so
63
+ * Vite middleware and close() address the same engine; leaving the slot untouched would make listen() create a second,
64
+ * skip-write browser engine with a divergent graph. Disable Vite's access-triggered regeneration because runtime reports and
65
+ * Rolldown HMR results now own every rebuild decision. Finally replace listen so readiness waits for physical initial output
66
+ * instead of Vite's in-memory bundle. These assignments are installation-time ownership transfer, never per-update state.
67
+ */
52
68
  bundledDev._devEngine = engine;
53
69
  bundledDev.triggerBundleRegenerationIfStale = async () => false;
54
70
  bundledDev.listen = async () => {
55
- await Promise.all([engine.run(), initialBuild]);
71
+ /*
72
+ * Subscribe before run because DevEngine intentionally fulfills run() after an initial plugin failure and emits that
73
+ * Error through onOutput. Observing the existing action edge preserves the exact failure without a second buildEnd
74
+ * Promise or forwarding Subject; waitForAction does not consume the action from concatMap's reducer subscription.
75
+ */
76
+ const initialOutput = hostActions.waitForAction((action) => action.kind === 'output');
77
+ await engine.run();
78
+ const { result } = await initialOutput;
79
+ if (result instanceof Error) {
80
+ throw result;
81
+ }
56
82
  await engine.ensureCurrentBuildFinish();
57
83
  };
58
84
  // Vite binds the port only after initServer (and therefore the initial build) completes, so
59
85
  // the actual port is not observable while onOutput runs for the first build. The App metadata
60
86
  // is written once the port is real; later full builds rewrite it from onOutput.
61
87
  server.httpServer?.once('listening', () => {
62
- hostTasks.enqueue('wx HMR initialization failed', rotateBuildSession);
88
+ // Port availability is lifecycle data, so initial identity rotation follows prior output/capture actions in the reducer.
89
+ hostActions.next({ kind: 'listening' });
63
90
  });
64
91
  // The runtime's metadata-only reports land on the control path; the buildId in each report
65
92
  // IS the Rolldown client ID.
@@ -67,8 +94,13 @@ export async function createWxDevHost({ server, options, applicationEntryIds })
67
94
  installDevToolsPrinter(server);
68
95
  return {
69
96
  close: async () => {
70
- await hostTasks.waitForIdle();
71
- await engine.close();
97
+ // Quiet-window completion first admits final HMR publications and rebuild reports into the serialized action edge.
98
+ hmrResults.complete();
99
+ runtimeReports.complete();
100
+ await hostActions.waitForIdle();
101
+ // Keep the action edge open until the final generation has admitted all capture and output callbacks.
102
+ await engine.ensureCurrentBuildFinish();
103
+ await hostActions.complete();
72
104
  }
73
105
  };
74
106
  /** Rotates the build identity and materializes the App metadata for it. */
@@ -86,6 +118,10 @@ export async function createWxDevHost({ server, options, applicationEntryIds })
86
118
  await engine.registerClient(buildId);
87
119
  await publishBuildMetadata(server, buildId, port);
88
120
  }
121
+ /**
122
+ * Mutates only the request-local HTTP response: unsupported methods become 404, malformed bounded bodies become 400, and a
123
+ * valid report is admitted before the default 200 response ends. No response object or parser state crosses requests.
124
+ */
89
125
  async function handleReport(req, res) {
90
126
  if (req.method !== 'POST') {
91
127
  res.statusCode = 404;
@@ -94,7 +130,7 @@ export async function createWxDevHost({ server, options, applicationEntryIds })
94
130
  }
95
131
  try {
96
132
  const report = JSON.parse(await readBody(req));
97
- await hostTasks.run(() => processReport(report));
133
+ runtimeReports.next(report);
98
134
  res.end();
99
135
  }
100
136
  catch (e) {
@@ -103,6 +139,36 @@ export async function createWxDevHost({ server, options, applicationEntryIds })
103
139
  res.end();
104
140
  }
105
141
  }
142
+ /** Reduces one merged source action through the existing authoritative host state. */
143
+ function applyHostAction(action) {
144
+ switch (action.kind) {
145
+ case 'capture-graph':
146
+ // The buildStart reader is a live capability; replacing it only here makes the reducer own build rebinding.
147
+ styleCapture.captureGraph(action.getModuleInfo);
148
+ return;
149
+ case 'capture-style':
150
+ // A failed upstream transform emits no action, intentionally retaining the last valid processed CSS generation.
151
+ styleCapture.captureStyle(action.id, action.style);
152
+ return;
153
+ case 'publish':
154
+ return publishUpdates(action.result);
155
+ case 'error':
156
+ logWxError(server.config.logger, 'wx HMR update failed', action.error);
157
+ return;
158
+ case 'reports':
159
+ action.reports.forEach(processReport);
160
+ return;
161
+ case 'output':
162
+ if (action.result instanceof Error) {
163
+ logWxError(server.config.logger, 'wx dev build failed', action.result);
164
+ return;
165
+ }
166
+ styleCapture.bindOutput(action.result.output);
167
+ return rotateBuildSession();
168
+ case 'listening':
169
+ return rotateBuildSession();
170
+ }
171
+ }
106
172
  /** Applies one runtime receipt to the active physical patch history. */
107
173
  function processReport(report) {
108
174
  // Delayed reports from older builds must never prune the live build's cumulative patch history.
@@ -111,8 +177,7 @@ export async function createWxDevHost({ server, options, applicationEntryIds })
111
177
  }
112
178
  switch (report.kind) {
113
179
  case 'rebuild': {
114
- server.config.logger.info(`[vpt] wx runtime requested a full rebuild: ${report.reason}`);
115
- engine.triggerFullBuild();
180
+ requestFullBuild(report.reason);
116
181
  return;
117
182
  }
118
183
  case 'applied': {
@@ -126,73 +191,67 @@ export async function createWxDevHost({ server, options, applicationEntryIds })
126
191
  const rolldownOptions = await bundledDev.getRolldownOptions();
127
192
  const output = requireSingleOutput(rolldownOptions);
128
193
  return dev(rolldownOptions, output, {
129
- onHmrUpdates: handleHmrUpdates,
130
- onOutput: handleDevOutput,
194
+ /**
195
+ * Admits Rolldown's non-awaited callback without starting asynchronous host work on the binding callback stack.
196
+ * The Subject retains Error values as control events and successful values as lossless patch data until its quiet edge.
197
+ */
198
+ onHmrUpdates: (result) => {
199
+ hmrResults.next(result);
200
+ },
201
+ // Initial and later complete builds share one admission path; startup merely observes the first OutputAction.
202
+ onOutput: (result) => {
203
+ hostActions.next({ kind: 'output', result: result });
204
+ },
131
205
  rebuildStrategy: 'never',
132
- watch: { skipWrite: false }
206
+ watch: {
207
+ // Rolldown must observe every source generation and emit every incremental factory: later patches do not
208
+ // reconstruct modules changed only by an earlier callback. RxJS conflates publication after compilation,
209
+ // preserving the complete patch sequence while avoiding repeated physical DevTools notifications.
210
+ skipWrite: false,
211
+ useDebounce: false
212
+ }
133
213
  });
134
214
  }
135
- /** Converts Rolldown's non-awaited callback into one ordered host publication task. */
136
- function handleHmrUpdates(result) {
137
- if (result instanceof Error) {
138
- logWxError(server.config.logger, 'wx HMR update failed', result);
139
- return;
140
- }
141
- hostTasks.enqueue('wx HMR publication failed', () => publishUpdates(result));
215
+ /** Centralizes the one diagnostic and DevEngine command used by every rebuild authority. */
216
+ function requestFullBuild(reason) {
217
+ server.config.logger.info(`[vpt] wx full rebuild required${reason ? `: ${reason}` : ''}`);
218
+ engine.triggerFullBuild();
142
219
  }
143
- /** Publishes only the active client's patches or requests the complete build required by Rolldown. */
220
+ /** Selects active updates and publishes their resulting host transaction. */
144
221
  async function publishUpdates(result) {
145
- const batch = [];
222
+ /*
223
+ * This transaction-local array preserves every patch in one pass and permits immediate rebuild dominance. It never escapes
224
+ * the serialized action. A filter/find/flatMap pipeline would scan a large burst repeatedly and allocate an intermediate.
225
+ */
226
+ const patches = [];
146
227
  for (const { clientId, update } of result.updates) {
147
228
  if (!publisher.isCurrentBuild(clientId) || update.type === 'Noop') {
148
229
  continue;
149
230
  }
150
231
  if (update.type === 'Patch') {
151
- batch.push(update);
232
+ patches.push(update);
152
233
  continue;
153
234
  }
154
- server.config.logger.info(`[vpt] wx full rebuild required${update.reason ? `: ${update.reason}` : ''}`);
155
- engine.triggerFullBuild();
235
+ requestFullBuild(update.reason);
156
236
  return;
157
237
  }
158
- if (batch.length === 0) {
238
+ await publishPatchBatch(patches);
239
+ }
240
+ /** Publishes one coherent WXSS, patch-file, and Rolldown-frontier transaction. */
241
+ async function publishPatchBatch(patches) {
242
+ if (patches.length === 0) {
159
243
  return;
160
244
  }
161
245
  // `onHmrUpdates` is the transaction boundary after every affected transform has updated graph and candidate state.
162
246
  // Every non-CSS edit may alter imports or Tailwind classes; rendering broadly and comparing finalized bytes avoids
163
247
  // source scanning while preventing unrelated JavaScript edits from notifying DevTools through an identical rename.
164
- await publishChangedStyles(batch);
248
+ await styleCapture.publishChanged(patches.flatMap((patch) => patch.changedIds));
165
249
  // Publish global.wxss before the matching JavaScript patch so DevTools observes a coherent HMR transaction.
166
250
  // The physical file must exist before Rolldown advances: once committed, later patches may be generated relative to
167
251
  // this batch even if DevTools has not observed its file event yet. PatchPublisher keeps the unapplied range cumulative,
168
252
  // so any later file generation still carries every factory needed to bridge the runtime's older application frontier.
169
- await publisher.produce(batch);
170
- await commitPublishedBatch(batch);
171
- }
172
- /** Publishes the style projection for one completed patch transaction when its source or topology may have changed. */
173
- async function publishChangedStyles(batch) {
174
- const changedIds = batch.flatMap((patch) => patch.changedIds);
175
- const styleChanged = changedIds.some(isGlobalStyleRequest);
176
- const candidatesChanged = changedIds.some((id) => !isCSSRequest(id));
177
- if (!styleChanged && !candidatesChanged) {
178
- return;
179
- }
180
- // buildStart installs this reader before Rolldown can produce either a complete output or an incremental batch.
181
- const getModuleInfo = styleState.getModuleInfo;
182
- if (!getModuleInfo) {
183
- throw new Error('WX style graph is unavailable before HMR publication');
184
- }
185
- // Traverse topology exactly once; root refresh and final rendering consume this immutable transaction plan.
186
- const styleIds = createGraphStylePlan(applicationEntryIds, getModuleInfo, (styleId) => styleState.processedStyles.has(styleId));
187
- if (candidatesChanged) {
188
- await refreshTailwindStyles(styleIds, styleState.processedStyles, async (rootId, requestId) => server.environments.client.pluginContainer.transform(await readFile(rootId, 'utf8'), requestId));
189
- }
190
- styleState.publishedWxss = await publishStyleHmr({
191
- styleIds: styleIds,
192
- outDir: server.config.build.outDir,
193
- processedStyles: styleState.processedStyles,
194
- publishedWxss: styleState.publishedWxss
195
- });
253
+ await publisher.produce(patches);
254
+ await commitPublishedBatch(patches);
196
255
  }
197
256
  /**
198
257
  * Advances Rolldown's published frontier in the same sequence order materialized in the cumulative physical file.
@@ -209,19 +268,6 @@ export async function createWxDevHost({ server, options, applicationEntryIds })
209
268
  await engine.notifyPayloadDelivered(patch.filename);
210
269
  }
211
270
  }
212
- /** Rotates metadata after each successful complete output. */
213
- function handleDevOutput(result) {
214
- if (result instanceof Error) {
215
- logWxError(server.config.logger, 'wx dev build failed', result);
216
- return;
217
- }
218
- hostTasks.enqueue('wx dev build finalization failed', finalizeDevOutput);
219
- }
220
- /** Rebinds the byte frontier after a complete build replaces the physical stylesheet outside HMR publication. */
221
- async function finalizeDevOutput() {
222
- styleState.publishedWxss = await readFile(path.join(server.config.build.outDir, globalWxssFileName), 'utf8');
223
- await rotateBuildSession();
224
- }
225
271
  }
226
272
  /** The bound HTTP port, or undefined before Vite's server is listening. */
227
273
  function boundPort(server) {
@@ -252,6 +298,12 @@ async function publishBuildMetadata(server, buildId, port) {
252
298
  }
253
299
  /** Replaces browser server URLs with the physical project directory consumed by WeChat DevTools. */
254
300
  function installDevToolsPrinter(server) {
301
+ /*
302
+ * Vite exposes printing as a mutable server callback because URLs are unknown until startup. WX serves control reports over
303
+ * HTTP but users open the physical output directory, so retaining the browser printer advertises unusable navigation URLs.
304
+ * Replacing only this presentation callback leaves resolved URLs and server routing untouched and naturally dies with the
305
+ * server instance; logging elsewhere would duplicate Vite's one readiness notification.
306
+ */
255
307
  server.printUrls = () => {
256
308
  server.config.logger.info(` ${colors.green('➜')} ${colors.bold('WeChat DevTools')}: ${colors.cyan(relativeToViteConfig(server.config.build.outDir, server.config.configFile, server.config.root))}`);
257
309
  };
@@ -279,6 +331,12 @@ function logWxError(logger, prefix, error) {
279
331
  const maximumBodyBytes = 64 * 1024;
280
332
  function readBody(req) {
281
333
  return new Promise((resolve, reject) => {
334
+ /*
335
+ * Node delivers one HTTP body through multiple callback invocations, so this request-local mutable string is the minimal
336
+ * state that joins chunks in wire order. The 64 KiB bound caps both retained memory and repeated-concatenation work; the
337
+ * value is parsed once on end and then discarded. Parsing per chunk is invalid because JSON tokens may cross boundaries,
338
+ * while retaining request bodies in host state would couple unrelated Page reports and leak across the server lifecycle.
339
+ */
282
340
  let body = '';
283
341
  req.on('data', (chunk) => {
284
342
  body += chunk.toString('utf8');
@@ -1,5 +1,6 @@
1
1
  import type { BindingClientHmrUpdate } from 'rolldown/experimental';
2
2
  export declare const developmentAppWxssFileName = "app.wxss";
3
+ export declare const globalWxssFileName = "assets/global.wxss";
3
4
  export declare const hmrInfoFileName = "hmr/info.js";
4
5
  export declare const hmrPatchesFileName = "hmr/patches.js";
5
6
  /** Report endpoint path served by the wx dev control edge. */
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto';
2
2
  import fs from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  export const developmentAppWxssFileName = 'app.wxss';
5
+ export const globalWxssFileName = 'assets/global.wxss';
5
6
  export const hmrInfoFileName = 'hmr/info.js';
6
7
  export const hmrPatchesFileName = 'hmr/patches.js';
7
8
  /** Report endpoint path served by the wx dev control edge. */
@@ -0,0 +1,16 @@
1
+ export type HostActions<Action> = Readonly<{
2
+ complete: () => Promise<void>;
3
+ next: (action: Action) => void;
4
+ waitForAction: <Selected extends Action>(select: (action: Action) => action is Selected) => Promise<Selected>;
5
+ waitForIdle: () => Promise<void>;
6
+ }>;
7
+ /**
8
+ * Executes semantic host actions one at a time while keeping source admission synchronous.
9
+ *
10
+ * `concatMap` is the sole effect serializer: each deferred reducer call starts only after the prior Promise settles. Errors are
11
+ * converted to empty inner streams so one failed physical transaction is reported without terminating later host actions.
12
+ * Barrier envelopes participate in the same ordering but run no effect; resolving one proves every action admitted before it
13
+ * has settled. `waitForAction` observes this same admission edge for lifecycle gates without introducing a forwarding Subject.
14
+ * Completing the source drains already-admitted effects and gives shutdown one Promise for final quiescence.
15
+ */
16
+ export declare function createHostActions<Action>(apply: (action: Action) => void | Promise<void>, reportError: (action: Action, error: unknown) => void): HostActions<Action>;
@@ -0,0 +1,58 @@
1
+ import { catchError, concatMap, defer, EMPTY, filter, firstValueFrom, lastValueFrom, map, Subject } from 'rxjs';
2
+ /**
3
+ * Executes semantic host actions one at a time while keeping source admission synchronous.
4
+ *
5
+ * `concatMap` is the sole effect serializer: each deferred reducer call starts only after the prior Promise settles. Errors are
6
+ * converted to empty inner streams so one failed physical transaction is reported without terminating later host actions.
7
+ * Barrier envelopes participate in the same ordering but run no effect; resolving one proves every action admitted before it
8
+ * has settled. `waitForAction` observes this same admission edge for lifecycle gates without introducing a forwarding Subject.
9
+ * Completing the source drains already-admitted effects and gives shutdown one Promise for final quiescence.
10
+ */
11
+ export function createHostActions(apply, reportError) {
12
+ /*
13
+ * This Subject is the host's only mutable admission edge. Independent callback stacks synchronously append semantic actions
14
+ * or barriers; concatMap owns the queued ordering and admits exactly one asynchronous effect at a time. A Promise chain would
15
+ * require mutation at every producer and is permanently rejected after one failure, while mergeMap could overlap physical
16
+ * writes and build rotation. The Subject retains no business frontier itself and is completed only after all external sources
17
+ * are quiescent, allowing lastValueFrom to prove the final queued effect settled.
18
+ */
19
+ const envelopes = new Subject();
20
+ const completion = lastValueFrom(envelopes.pipe(concatMap((envelope) => {
21
+ if (envelope.kind === 'barrier') {
22
+ return defer(() => {
23
+ envelope.resolve();
24
+ return EMPTY;
25
+ });
26
+ }
27
+ return defer(() => Promise.resolve(apply(envelope.action))).pipe(catchError((error) => {
28
+ reportError(envelope.action, error);
29
+ return EMPTY;
30
+ }));
31
+ })), { defaultValue: undefined });
32
+ return {
33
+ complete: async () => {
34
+ envelopes.complete();
35
+ await completion;
36
+ },
37
+ next(action) {
38
+ envelopes.next({ kind: 'action', action: action });
39
+ },
40
+ waitForAction(select) {
41
+ /*
42
+ * Subscribe to the multicast admission Subject rather than creating another lifecycle source. Barrier envelopes are
43
+ * internal ordering controls, so expose only semantic actions and let the caller's type guard select its milestone.
44
+ * firstValueFrom unsubscribes after the match; concatMap's independent subscription still applies the same action.
45
+ */
46
+ return firstValueFrom(envelopes.pipe(filter((envelope) => envelope.kind === 'action'), map((envelope) => envelope.action), filter(select)));
47
+ },
48
+ waitForIdle() {
49
+ /*
50
+ * The resolver is one transaction-local mutable latch. Enqueuing it behind ordinary actions makes resolution mean
51
+ * every earlier effect settled, without completing the shared source needed by final DevEngine output callbacks.
52
+ */
53
+ const barrier = Promise.withResolvers();
54
+ envelopes.next({ kind: 'barrier', resolve: barrier.resolve });
55
+ return barrier.promise;
56
+ }
57
+ };
58
+ }
@@ -1,6 +1,6 @@
1
1
  import { type PatchUpdate } from './hmr-files.ts';
2
2
  /** Abstracts the physical patches write; the engine owns the file destination. */
3
- export type WritePatches = (content: string) => Promise<void>;
3
+ type WritePatches = (content: string) => Promise<void>;
4
4
  /**
5
5
  * Owns the cumulative sequence range between the host's published frontier and the runtime's applied frontier.
6
6
  *
@@ -17,7 +17,6 @@ export type WritePatches = (content: string) => Promise<void>;
17
17
  export declare class PatchPublisher {
18
18
  private readonly writePatches;
19
19
  private buildId;
20
- /** Executable updates retained until the runtime confirms successful application. */
21
20
  private readonly pendingPatches;
22
21
  constructor(writePatches: WritePatches);
23
22
  /** True when the buildId belongs to the current full build. */
@@ -40,3 +39,4 @@ export declare class PatchPublisher {
40
39
  */
41
40
  acknowledge(seq: number): void;
42
41
  }
42
+ export {};
@@ -15,8 +15,20 @@ import { renderHmrPatches } from './hmr-files.js';
15
15
  */
16
16
  export class PatchPublisher {
17
17
  writePatches;
18
+ /*
19
+ * This mutable identity is the sole session authority for runtime reports and per-client Rolldown updates. startBuild rotates
20
+ * it only after a successful complete output reaches the serialized host reducer. Delayed reports retain their original ID
21
+ * and therefore fail isCurrentBuild instead of pruning or rebuilding the new session. An immutable constructor value cannot
22
+ * represent full-build rotation, while deriving identity from patches would leave empty builds without a client ID.
23
+ */
18
24
  buildId;
19
- /** Executable updates retained until the runtime confirms successful application. */
25
+ /*
26
+ * This mutable ordered suffix bridges two independent frontiers: Rolldown payloads become published immediately after the
27
+ * cumulative file is durable, but entries remain here until the runtime acknowledges applying their sequence. Appending is
28
+ * lossless because later patches are deltas; prefix removal is the only valid mutation because acknowledgements are monotonic
29
+ * frontiers. A Set, latest-only value, or immutable reconstruction would either lose duplicate filenames/order or copy the
30
+ * complete unapplied history for every burst.
31
+ */
20
32
  pendingPatches = [];
21
33
  // Explicit field assignment: node --test strips types and does not support parameter properties.
22
34
  constructor(writePatches) {
@@ -52,6 +64,10 @@ export class PatchPublisher {
52
64
  * retaining the gap between published and applied frontiers is what lets DevTools safely miss intermediate file events.
53
65
  */
54
66
  acknowledge(seq) {
67
+ /*
68
+ * This local counter scans the monotonic prefix without allocating or invoking a callback for each patch. It exists only
69
+ * long enough to supply splice's prefix length; pendingPatches remains the sole durable frontier and is mutated once.
70
+ */
55
71
  let appliedCount = 0;
56
72
  while (appliedCount < this.pendingPatches.length && this.pendingPatches[appliedCount].seq <= seq) {
57
73
  appliedCount++;
@@ -1,5 +1,9 @@
1
1
  import { type PluginOption } from 'vite';
2
2
  import type { VitePluginTaroOptions } from '../../../../options.ts';
3
+ /** Selects the sole Vite environment that owns the physical Mini Program development project. */
4
+ export declare function isWxClientEnvironment(environment: Readonly<{
5
+ name: string;
6
+ }>): boolean;
3
7
  /**
4
8
  * Adds the serve-only bundled-development plugin set for the wx target: the dev adapter,
5
9
  * Page HMR activation, and React Refresh adaptation transforms.
@@ -1,4 +1,4 @@
1
- import { transformWithOxc } from 'vite';
1
+ import { normalizePath, transformWithOxc } from 'vite';
2
2
  import { esTarget } from '../../../utils/constant.js';
3
3
  import { memoize } from '../../../utils/memoize.js';
4
4
  import { normalizeModuleId } from '../../../utils/modules.js';
@@ -7,6 +7,10 @@ import { createWxDevHost } from './dev-host.js';
7
7
  import { developmentAppWxssFileName } from './hmr-files.js';
8
8
  import { createWxReactRefreshTransforms } from './react-refresh.js';
9
9
  const taroRuntimeId = '@tarojs/runtime';
10
+ /** Selects the sole Vite environment that owns the physical Mini Program development project. */
11
+ export function isWxClientEnvironment(environment) {
12
+ return environment.name === 'client';
13
+ }
10
14
  /**
11
15
  * Adds the serve-only bundled-development plugin set for the wx target: the dev adapter,
12
16
  * Page HMR activation, and React Refresh adaptation transforms.
@@ -15,16 +19,25 @@ const taroRuntimeId = '@tarojs/runtime';
15
19
  * reconstructing the resolver's App/Page ownership policy from unrelated Rolldown shell and transport entries.
16
20
  */
17
21
  export function createWxDevelopmentPlugin(options, applicationEntryIds) {
18
- // This mutable handle transfers ownership from configureServer to closeBundle; at most one host exists per plugin instance.
22
+ /*
23
+ * Vite creates this plugin descriptor before a server or DevEngine exists, then invokes configureServer and closeBundle on
24
+ * different lifecycle stacks. This mutable handle transfers the one client-owned host between those hooks: configureServer
25
+ * assigns it after asynchronous construction, and closeBundle reads it to drain the same engine. Environment scoping ensures
26
+ * only the client hook pair participates. Capturing a construction Promise would start too early, while recreating the host
27
+ * in closeBundle would lose every live action, patch, style, and client frontier owned by the running instance.
28
+ */
19
29
  let host = null;
20
30
  // Portable hook filters stay broad; these exact identities exclude similarly named user modules.
21
- const normalizedAppCapsulePath = normalizeModuleId(appCapsulePath);
22
- const normalizedPageCapsulePath = normalizeModuleId(pageCapsulePath);
23
- const normalizedTaroRuntimePath = normalizeModuleId(taroRuntimePath);
31
+ const normalizedAppCapsulePath = normalizePath(appCapsulePath);
32
+ const normalizedPageCapsulePath = normalizePath(pageCapsulePath);
33
+ const normalizedTaroRuntimePath = normalizePath(taroRuntimePath);
24
34
  return [
25
35
  {
26
36
  name: 'vpt:wx-dev',
27
37
  apply: 'serve',
38
+ // The physical WX DevEngine is a client build. Native environment scoping gives its shared host exactly one
39
+ // generate/close lifecycle instead of admitting hooks from Vite's unrelated SSR environment.
40
+ applyToEnvironment: isWxClientEnvironment,
28
41
  config() {
29
42
  return {
30
43
  build: {
@@ -189,9 +202,12 @@ function getPageRoute(id) {
189
202
  throw new Error(`WX Page capsule is missing its route: ${id}`);
190
203
  return route;
191
204
  }
192
- // The assembled runtime chunk is byte-identical on every build (the base runtime and the
193
- // bundled implement are immutable for the server's lifetime), so the lowering runs once
194
- // and every build reuses it.
205
+ /*
206
+ * memoize owns a mutable one-entry-by-input cache. The assembled runtime source is byte-identical across complete generations
207
+ * because both the Rolldown base and injected implementation are immutable for the server lifetime. Reusing its lowered result
208
+ * avoids repeating the only large Oxc transform on every rebuild; keying by source still invalidates correctly if a future Vite
209
+ * generation changes the runtime, unlike a module-global cached string detached from its actual input.
210
+ */
195
211
  const fixRolldownRuntime = memoize((code) => {
196
212
  return transformWithOxc(code, rolldownRuntimeId, {
197
213
  lang: 'js',
@@ -193,7 +193,11 @@ export function removeRefreshPreambleGuard({ code, id }) {
193
193
  createVisitor: createRefreshPreambleGuardVisitor
194
194
  });
195
195
  }
196
- // The refresh runtime module and the react-family modules are immutable for the server's
197
- // lifetime, so their Oxc parses run once per module and every build reuses the output.
196
+ /*
197
+ * Each memoized transform owns a mutable cache keyed only by source bytes. The matching runtime/react-family modules are
198
+ * immutable between complete generations, so reparsing them would repeat O(source bytes) Oxc work without observing new state.
199
+ * Source keys still create a fresh result after a dependency upgrade or real module edit, unlike a once-only cache keyed by ID;
200
+ * keeping separate caches prevents identical text in different transform domains from sharing the wrong adaptation.
201
+ */
198
202
  const fixRefreshRuntime = memoize(transformRefreshRuntime, { getCacheKey: ({ code }) => code });
199
203
  const fixReactDevtoolsHook = memoize(transformReactDevtoolsHook, { getCacheKey: ({ code }) => code });
@@ -0,0 +1,27 @@
1
+ import { type SchedulerLike, Subject } from 'rxjs';
2
+ type AppliedReport = Readonly<{
3
+ kind: 'applied';
4
+ buildId: string;
5
+ seq: number;
6
+ }>;
7
+ /** The runtime hit an unrecoverable state and needs a complete build. */
8
+ type RebuildReport = Readonly<{
9
+ kind: 'rebuild';
10
+ buildId: string;
11
+ reason: string;
12
+ }>;
13
+ export type RuntimeReport = AppliedReport | RebuildReport;
14
+ /**
15
+ * Conflates runtime receipts without weakening their build identity or recovery semantics.
16
+ *
17
+ * Reports are metadata frontiers rather than executable deltas: for one build, the highest applied sequence subsumes lower and
18
+ * duplicate acknowledgements. A rebuild does not subsume reports from a different build, because delayed Pages may still POST
19
+ * after session rotation, but it dominates every acknowledgement for its own build. The host validates each retained build ID
20
+ * against the physical publisher only when its serialized task executes, preserving rotation order.
21
+ *
22
+ * Completing the Subject flushes the active window synchronously so shutdown can include its host task before closing Rolldown.
23
+ * Reduction is O(reports) time and O(distinct builds) retained space per quiet window.
24
+ */
25
+ export declare function createRuntimeReportsStream(settleMilliseconds: number, scheduler: SchedulerLike, publish: (reports: readonly RuntimeReport[]) => void): Subject<RuntimeReport>;
26
+ export declare function reduceRuntimeReportWindow(window: readonly RuntimeReport[]): readonly RuntimeReport[];
27
+ export {};