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.
- package/dist/node/plugins/client/client-taro.d.ts +1 -1
- package/dist/node/plugins/client/client-taro.js +2 -1
- package/dist/node/plugins/h5/create-stencil-client-adapter.d.ts +1 -1
- package/dist/node/plugins/h5/create-stencil-client-adapter.js +2 -1
- package/dist/node/plugins/h5/resolver/module-resolver.js +2 -1
- package/dist/node/plugins/wx/dev/create-hmr-results-stream.d.ts +23 -0
- package/dist/node/plugins/wx/dev/create-hmr-results-stream.js +57 -0
- package/dist/node/plugins/wx/dev/create-style-capture.d.ts +42 -0
- package/dist/node/plugins/wx/dev/create-style-capture.js +125 -0
- package/dist/node/plugins/wx/dev/dev-host.d.ts +1 -1
- package/dist/node/plugins/wx/dev/dev-host.js +151 -93
- package/dist/node/plugins/wx/dev/hmr-files.d.ts +1 -0
- package/dist/node/plugins/wx/dev/hmr-files.js +1 -0
- package/dist/node/plugins/wx/dev/host-actions.d.ts +16 -0
- package/dist/node/plugins/wx/dev/host-actions.js +58 -0
- package/dist/node/plugins/wx/dev/patch-publisher.d.ts +2 -2
- package/dist/node/plugins/wx/dev/patch-publisher.js +17 -1
- package/dist/node/plugins/wx/dev/plugins.d.ts +4 -0
- package/dist/node/plugins/wx/dev/plugins.js +24 -8
- package/dist/node/plugins/wx/dev/react-refresh.js +6 -2
- package/dist/node/plugins/wx/dev/runtime-reports.d.ts +27 -0
- package/dist/node/plugins/wx/dev/runtime-reports.js +45 -0
- package/dist/node/plugins/wx/dev/wx-dev-options.d.ts +7 -3
- package/dist/node/plugins/wx/dev/wx-dev-options.js +46 -28
- package/dist/node/plugins/wx/module.d.ts +1 -1
- package/dist/node/plugins/wx/module.js +2 -1
- package/dist/node/plugins/wx/native/compile-native-component-interface.js +2 -1
- package/dist/node/plugins/wx/native/create-native-component-output.js +2 -0
- package/dist/node/plugins/wx/native/native-component-assets.d.ts +1 -1
- package/dist/node/plugins/wx/native/native-component-assets.js +3 -3
- package/dist/node/plugins/wx/native/native-component-interface.js +2 -1
- package/dist/node/plugins/wx/resolve/resolver.js +3 -2
- package/dist/node/plugins/wx/styles/plugins.js +20 -1
- package/dist/node/utils/modules.d.ts +1 -1
- package/dist/node/utils/modules.js +4 -3
- package/dist/node/utils/packages.js +2 -2
- package/package.json +4 -3
- package/src/node/plugins/client/client-taro.ts +2 -2
- package/src/node/plugins/h5/create-stencil-client-adapter.ts +2 -2
- package/src/node/plugins/h5/resolver/module-resolver.ts +2 -1
- package/src/node/plugins/wx/dev/create-hmr-results-stream.ts +71 -0
- package/src/node/plugins/wx/dev/create-style-capture.ts +179 -0
- package/src/node/plugins/wx/dev/dev-host.ts +177 -124
- package/src/node/plugins/wx/dev/hmr-files.ts +1 -0
- package/src/node/plugins/wx/dev/host-actions.ts +88 -0
- package/src/node/plugins/wx/dev/patch-publisher.ts +19 -2
- package/src/node/plugins/wx/dev/plugins.ts +25 -8
- package/src/node/plugins/wx/dev/react-refresh.ts +6 -2
- package/src/node/plugins/wx/dev/runtime-reports.ts +70 -0
- package/src/node/plugins/wx/dev/wx-dev-options.ts +50 -36
- package/src/node/plugins/wx/module.ts +2 -4
- package/src/node/plugins/wx/native/compile-native-component-interface.ts +2 -1
- package/src/node/plugins/wx/native/create-native-component-output.ts +2 -0
- package/src/node/plugins/wx/native/native-component-assets.ts +3 -4
- package/src/node/plugins/wx/native/native-component-interface.ts +2 -1
- package/src/node/plugins/wx/resolve/resolver.ts +3 -2
- package/src/node/plugins/wx/styles/plugins.ts +21 -1
- package/src/node/utils/modules.ts +4 -3
- package/src/node/utils/packages.ts +2 -2
- package/dist/node/plugins/wx/dev/create-wx-dev-mode.d.ts +0 -6
- package/dist/node/plugins/wx/dev/create-wx-dev-mode.js +0 -9
- package/dist/node/plugins/wx/dev/styles/create-style-capture-plugin.d.ts +0 -12
- package/dist/node/plugins/wx/dev/styles/create-style-capture-plugin.js +0 -21
- package/dist/node/plugins/wx/dev/styles/publish-style-hmr.d.ts +0 -27
- package/dist/node/plugins/wx/dev/styles/publish-style-hmr.js +0 -48
- package/dist/node/utils/serialized-task-queue.d.ts +0 -13
- package/dist/node/utils/serialized-task-queue.js +0 -25
- package/src/node/plugins/wx/dev/create-wx-dev-mode.ts +0 -14
- package/src/node/plugins/wx/dev/styles/create-style-capture-plugin.ts +0 -34
- package/src/node/plugins/wx/dev/styles/publish-style-hmr.ts +0 -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 {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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 {
|
|
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
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
//
|
|
45
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
130
|
-
|
|
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: {
|
|
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
|
-
/**
|
|
136
|
-
function
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
/**
|
|
220
|
+
/** Selects active updates and publishes their resulting host transaction. */
|
|
144
221
|
async function publishUpdates(result) {
|
|
145
|
-
|
|
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
|
-
|
|
232
|
+
patches.push(update);
|
|
152
233
|
continue;
|
|
153
234
|
}
|
|
154
|
-
|
|
155
|
-
engine.triggerFullBuild();
|
|
235
|
+
requestFullBuild(update.reason);
|
|
156
236
|
return;
|
|
157
237
|
}
|
|
158
|
-
|
|
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
|
|
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(
|
|
170
|
-
await commitPublishedBatch(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
22
|
-
const normalizedPageCapsulePath =
|
|
23
|
-
const normalizedTaroRuntimePath =
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
197
|
-
|
|
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 {};
|