vite-plugin-taro 0.5.13 → 0.5.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +54 -0
- package/dist/node/plugins/wx/dev/create-style-capture.js +173 -0
- package/dist/node/plugins/wx/dev/dev-host.d.ts +1 -1
- package/dist/node/plugins/wx/dev/dev-host.js +161 -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 +248 -0
- package/src/node/plugins/wx/dev/dev-host.ts +188 -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,12 +2,12 @@ import { readFile } from 'node:fs/promises'
|
|
|
2
2
|
import type { ServerResponse } from 'node:http'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import colors from 'picocolors'
|
|
5
|
-
import type { GetModuleInfo } from 'rolldown'
|
|
6
5
|
import { type DevEngine, type DevOptions, dev } from 'rolldown/experimental'
|
|
7
|
-
import {
|
|
6
|
+
import { asyncScheduler } from 'rxjs'
|
|
7
|
+
import type { Connect, ViteDevServer } from 'vite'
|
|
8
8
|
import type { VitePluginTaroOptions } from '../../../../options.ts'
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { createHmrResultsStream } from './create-hmr-results-stream.ts'
|
|
10
|
+
import { createStyleCapture, type StyleCaptureAction } from './create-style-capture.ts'
|
|
11
11
|
import {
|
|
12
12
|
developmentAppWxssFileName,
|
|
13
13
|
type HmrInfo,
|
|
@@ -20,32 +20,31 @@ import {
|
|
|
20
20
|
renderInitialHmrPatches,
|
|
21
21
|
writeHmrFile
|
|
22
22
|
} from './hmr-files.ts'
|
|
23
|
+
import { createHostActions } from './host-actions.ts'
|
|
23
24
|
import { PatchPublisher } from './patch-publisher.ts'
|
|
24
|
-
import {
|
|
25
|
-
import { globalWxssFileName, publishStyleHmr, refreshTailwindStyles } from './styles/publish-style-hmr.ts'
|
|
25
|
+
import { createRuntimeReportsStream, type RuntimeReport } from './runtime-reports.ts'
|
|
26
26
|
import { type BundledDev, installWxDevOptions, requireSingleOutput } from './wx-dev-options.ts'
|
|
27
27
|
|
|
28
28
|
export type WxDevHost = Readonly<{
|
|
29
29
|
close: () => Promise<void>
|
|
30
30
|
}>
|
|
31
31
|
|
|
32
|
-
type AppliedReport = Readonly<{
|
|
33
|
-
kind: 'applied'
|
|
34
|
-
buildId: string
|
|
35
|
-
seq: number
|
|
36
|
-
}>
|
|
37
|
-
|
|
38
|
-
/** The runtime hit an unrecoverable state (e.g. a corrupted patch range) and needs a full rebuild. */
|
|
39
|
-
type RebuildReport = Readonly<{
|
|
40
|
-
kind: 'rebuild'
|
|
41
|
-
buildId: string
|
|
42
|
-
reason: string
|
|
43
|
-
}>
|
|
44
|
-
|
|
45
|
-
type HmrReport = AppliedReport | RebuildReport
|
|
46
32
|
type HmrUpdatesResult = Parameters<NonNullable<DevOptions['onHmrUpdates']>>[0]
|
|
47
33
|
type HmrUpdates = Exclude<HmrUpdatesResult, Error>
|
|
48
34
|
type DevOutputResult = Parameters<NonNullable<DevOptions['onOutput']>>[0]
|
|
35
|
+
/** A complete build callback is a host action whether it carries durable output or an initial/later build failure. */
|
|
36
|
+
type OutputAction = Readonly<{ kind: 'output'; result: DevOutputResult }>
|
|
37
|
+
|
|
38
|
+
type HostAction =
|
|
39
|
+
| StyleCaptureAction
|
|
40
|
+
| Readonly<{ kind: 'publish'; result: HmrUpdates }>
|
|
41
|
+
| Readonly<{ kind: 'error'; error: Error }>
|
|
42
|
+
| Readonly<{ kind: 'reports'; reports: readonly RuntimeReport[] }>
|
|
43
|
+
| OutputAction
|
|
44
|
+
| Readonly<{ kind: 'listening' }>
|
|
45
|
+
|
|
46
|
+
/** One short trailing-edge window absorbs editor bursts before style preparation and physical Page notification. */
|
|
47
|
+
const hmrSettleMilliseconds = 16
|
|
49
48
|
|
|
50
49
|
/**
|
|
51
50
|
* Creates the wx dev host: the adapter that owns the physical Rolldown DevEngine (created
|
|
@@ -66,48 +65,76 @@ export async function createWxDevHost({
|
|
|
66
65
|
applicationEntryIds: readonly string[]
|
|
67
66
|
}): Promise<WxDevHost> {
|
|
68
67
|
const bundledDev = getBundledDev(server)
|
|
69
|
-
// This is the host's mutable style projection: CSS absent from Rolldown, the live graph capability rebound by each
|
|
70
|
-
// complete build, and the last durable WXSS bytes used only to suppress identical filesystem publications. Style capture
|
|
71
|
-
// remains O(1); derived order and reachability stay local to each HMR transaction.
|
|
72
|
-
const styleState: {
|
|
73
|
-
getModuleInfo: GetModuleInfo | undefined
|
|
74
|
-
processedStyles: Map<string, ProcessedStyle>
|
|
75
|
-
publishedWxss: string | undefined
|
|
76
|
-
} = {
|
|
77
|
-
getModuleInfo: undefined,
|
|
78
|
-
processedStyles: new Map(),
|
|
79
|
-
publishedWxss: undefined
|
|
80
|
-
}
|
|
81
|
-
const styleCapturePlugin = createStyleCapturePlugin({
|
|
82
|
-
captureGraph(reader) {
|
|
83
|
-
styleState.getModuleInfo = reader
|
|
84
|
-
},
|
|
85
|
-
captureStyle(id, style) {
|
|
86
|
-
styleState.processedStyles.set(id, style)
|
|
87
|
-
}
|
|
88
|
-
})
|
|
89
68
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
69
|
+
// All callbacks admit typed actions through this edge; concatMap is the sole owner of effect ordering and mutable host state.
|
|
70
|
+
const hostActions = createHostActions<HostAction>(applyHostAction, (action, error) =>
|
|
71
|
+
logWxError(server.config.logger, `wx HMR ${action.kind} failed`, error)
|
|
72
|
+
)
|
|
93
73
|
|
|
74
|
+
const styleCapture = createStyleCapture({
|
|
75
|
+
applicationEntryIds: applicationEntryIds,
|
|
76
|
+
outDir: server.config.build.outDir,
|
|
77
|
+
// Capture hooks run only after engine.run, when the host action subscription and all reducer dependencies are ready.
|
|
78
|
+
emit: (action) => hostActions.next(action),
|
|
79
|
+
transformTailwindRoot: async (rootId, requestId) => {
|
|
80
|
+
// The capture contains generated CSS, while the sidecar must re-run from raw Tailwind directives. Vite exposes no
|
|
81
|
+
// raw source in its live module graph, so this read is the authoritative source generation rather than a read-back.
|
|
82
|
+
return server.environments.client.pluginContainer.transform(await readFile(rootId, 'utf8'), requestId)
|
|
83
|
+
}
|
|
84
|
+
})
|
|
94
85
|
const publisher = new PatchPublisher((content) =>
|
|
95
86
|
writeHmrFile(server.config.build.outDir, hmrPatchesFileName, content)
|
|
96
87
|
)
|
|
97
88
|
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
const initialBuild = installWxDevOptions({ bundledDev, server, options, hostPlugins: [styleCapturePlugin] })
|
|
89
|
+
// Option installation now configures only Rolldown. Build lifecycle results enter through the engine's output action below.
|
|
90
|
+
installWxDevOptions({ bundledDev, server, options, hostPlugins: [styleCapture.plugin] })
|
|
101
91
|
const engine: DevEngine = await createEngine()
|
|
102
92
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
93
|
+
const hmrResults = createHmrResultsStream(
|
|
94
|
+
hmrSettleMilliseconds,
|
|
95
|
+
asyncScheduler,
|
|
96
|
+
(result) => {
|
|
97
|
+
// One reduced window becomes one existing host transaction: style preparation, one cumulative patch write, then
|
|
98
|
+
// ordered delivery notifications for every original Rolldown payload retained by the stream.
|
|
99
|
+
hostActions.next({ kind: 'publish', result: result })
|
|
100
|
+
},
|
|
101
|
+
(error) => {
|
|
102
|
+
/*
|
|
103
|
+
* Invalid syntax is a normal intermediate editor generation. Rolldown produced no patch, so neither the published
|
|
104
|
+
* nor applied runtime frontier needs repair. Triggering a complete build here would immediately compile the same
|
|
105
|
+
* invalid source and empirically wedges that DevEngine generation. Admit only the diagnostic; when the user saves
|
|
106
|
+
* valid source, the watch engine emits an ordinary successful callback and resumes the normal publication path.
|
|
107
|
+
*/
|
|
108
|
+
hostActions.next({ kind: 'error', error: error })
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
const runtimeReports = createRuntimeReportsStream(hmrSettleMilliseconds, asyncScheduler, (reports) => {
|
|
113
|
+
// ACK conflation happens before admission, but publisher validation and mutation remain ordered with patch writes and
|
|
114
|
+
// build rotation. Reports arriving during publication therefore execute only after that physical transaction commits.
|
|
115
|
+
hostActions.next({ kind: 'reports', reports: reports })
|
|
116
|
+
})
|
|
117
|
+
/*
|
|
118
|
+
* BundledDev is Vite's mutable environment adapter. Transfer its engine slot to the one physical writer created above so
|
|
119
|
+
* Vite middleware and close() address the same engine; leaving the slot untouched would make listen() create a second,
|
|
120
|
+
* skip-write browser engine with a divergent graph. Disable Vite's access-triggered regeneration because runtime reports and
|
|
121
|
+
* Rolldown HMR results now own every rebuild decision. Finally replace listen so readiness waits for physical initial output
|
|
122
|
+
* instead of Vite's in-memory bundle. These assignments are installation-time ownership transfer, never per-update state.
|
|
123
|
+
*/
|
|
107
124
|
bundledDev._devEngine = engine
|
|
108
125
|
bundledDev.triggerBundleRegenerationIfStale = async () => false
|
|
109
126
|
bundledDev.listen = async () => {
|
|
110
|
-
|
|
127
|
+
/*
|
|
128
|
+
* Subscribe before run because DevEngine intentionally fulfills run() after an initial plugin failure and emits that
|
|
129
|
+
* Error through onOutput. Observing the existing action edge preserves the exact failure without a second buildEnd
|
|
130
|
+
* Promise or forwarding Subject; waitForAction does not consume the action from concatMap's reducer subscription.
|
|
131
|
+
*/
|
|
132
|
+
const initialOutput = hostActions.waitForAction((action): action is OutputAction => action.kind === 'output')
|
|
133
|
+
await engine.run()
|
|
134
|
+
const { result } = await initialOutput
|
|
135
|
+
if (result instanceof Error) {
|
|
136
|
+
throw result
|
|
137
|
+
}
|
|
111
138
|
await engine.ensureCurrentBuildFinish()
|
|
112
139
|
}
|
|
113
140
|
|
|
@@ -115,7 +142,8 @@ export async function createWxDevHost({
|
|
|
115
142
|
// the actual port is not observable while onOutput runs for the first build. The App metadata
|
|
116
143
|
// is written once the port is real; later full builds rewrite it from onOutput.
|
|
117
144
|
server.httpServer?.once('listening', () => {
|
|
118
|
-
|
|
145
|
+
// Port availability is lifecycle data, so initial identity rotation follows prior output/capture actions in the reducer.
|
|
146
|
+
hostActions.next({ kind: 'listening' })
|
|
119
147
|
})
|
|
120
148
|
|
|
121
149
|
// The runtime's metadata-only reports land on the control path; the buildId in each report
|
|
@@ -126,8 +154,13 @@ export async function createWxDevHost({
|
|
|
126
154
|
|
|
127
155
|
return {
|
|
128
156
|
close: async () => {
|
|
129
|
-
|
|
130
|
-
|
|
157
|
+
// Quiet-window completion first admits final HMR publications and rebuild reports into the serialized action edge.
|
|
158
|
+
hmrResults.complete()
|
|
159
|
+
runtimeReports.complete()
|
|
160
|
+
await hostActions.waitForIdle()
|
|
161
|
+
// Keep the action edge open until the final generation has admitted all capture and output callbacks.
|
|
162
|
+
await engine.ensureCurrentBuildFinish()
|
|
163
|
+
await hostActions.complete()
|
|
131
164
|
}
|
|
132
165
|
}
|
|
133
166
|
|
|
@@ -149,6 +182,10 @@ export async function createWxDevHost({
|
|
|
149
182
|
await publishBuildMetadata(server, buildId, port)
|
|
150
183
|
}
|
|
151
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Mutates only the request-local HTTP response: unsupported methods become 404, malformed bounded bodies become 400, and a
|
|
187
|
+
* valid report is admitted before the default 200 response ends. No response object or parser state crosses requests.
|
|
188
|
+
*/
|
|
152
189
|
async function handleReport(req: Connect.IncomingMessage, res: ServerResponse): Promise<void> {
|
|
153
190
|
if (req.method !== 'POST') {
|
|
154
191
|
res.statusCode = 404
|
|
@@ -157,8 +194,8 @@ export async function createWxDevHost({
|
|
|
157
194
|
}
|
|
158
195
|
|
|
159
196
|
try {
|
|
160
|
-
const report = JSON.parse(await readBody(req)) as
|
|
161
|
-
|
|
197
|
+
const report = JSON.parse(await readBody(req)) as RuntimeReport
|
|
198
|
+
runtimeReports.next(report)
|
|
162
199
|
res.end()
|
|
163
200
|
} catch (e) {
|
|
164
201
|
logWxError(server.config.logger, 'wx HMR report failed', e)
|
|
@@ -167,8 +204,50 @@ export async function createWxDevHost({
|
|
|
167
204
|
}
|
|
168
205
|
}
|
|
169
206
|
|
|
207
|
+
/** Reduces one merged source action through the existing authoritative host state. */
|
|
208
|
+
function applyHostAction(action: HostAction): void | Promise<void> {
|
|
209
|
+
switch (action.kind) {
|
|
210
|
+
case 'capture-graph':
|
|
211
|
+
// The buildStart reader is a live capability; replacing it only here makes the reducer own build rebinding.
|
|
212
|
+
styleCapture.captureGraph(action.getModuleInfo)
|
|
213
|
+
return
|
|
214
|
+
case 'capture-style':
|
|
215
|
+
// A failed upstream transform emits no action, intentionally retaining the last valid processed CSS generation.
|
|
216
|
+
styleCapture.captureStyle(action.id, action.style)
|
|
217
|
+
return
|
|
218
|
+
case 'publish':
|
|
219
|
+
return publishUpdates(action.result)
|
|
220
|
+
case 'error':
|
|
221
|
+
logWxError(server.config.logger, 'wx HMR update failed', action.error)
|
|
222
|
+
return
|
|
223
|
+
case 'reports':
|
|
224
|
+
action.reports.forEach(processReport)
|
|
225
|
+
return
|
|
226
|
+
case 'output':
|
|
227
|
+
if (action.result instanceof Error) {
|
|
228
|
+
logWxError(server.config.logger, 'wx dev build failed', action.result)
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
return reconcileCompleteOutput(action.result.output)
|
|
232
|
+
case 'listening':
|
|
233
|
+
return rotateBuildSession()
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Reconciles graph-complete styles before rotating the App-visible build identity.
|
|
239
|
+
*
|
|
240
|
+
* DevEngine has already written its compiler asset when onOutput fires, but bundled development may have omitted CSS Modules
|
|
241
|
+
* from that asset. Awaiting reconciliation inside the serialized reducer guarantees the subsequent app.wxss rotation—the
|
|
242
|
+
* event that refreshes DevTools—can only expose a generation whose global WXSS already represents the complete App/Page graph.
|
|
243
|
+
*/
|
|
244
|
+
async function reconcileCompleteOutput(output: Exclude<DevOutputResult, Error>['output']): Promise<void> {
|
|
245
|
+
await styleCapture.reconcileComplete(output)
|
|
246
|
+
await rotateBuildSession()
|
|
247
|
+
}
|
|
248
|
+
|
|
170
249
|
/** Applies one runtime receipt to the active physical patch history. */
|
|
171
|
-
function processReport(report:
|
|
250
|
+
function processReport(report: RuntimeReport): void {
|
|
172
251
|
// Delayed reports from older builds must never prune the live build's cumulative patch history.
|
|
173
252
|
if (!publisher.isCurrentBuild(report.buildId)) {
|
|
174
253
|
return
|
|
@@ -176,8 +255,7 @@ export async function createWxDevHost({
|
|
|
176
255
|
|
|
177
256
|
switch (report.kind) {
|
|
178
257
|
case 'rebuild': {
|
|
179
|
-
|
|
180
|
-
engine.triggerFullBuild()
|
|
258
|
+
requestFullBuild(report.reason)
|
|
181
259
|
return
|
|
182
260
|
}
|
|
183
261
|
case 'applied': {
|
|
@@ -193,87 +271,76 @@ export async function createWxDevHost({
|
|
|
193
271
|
const output = requireSingleOutput(rolldownOptions)
|
|
194
272
|
|
|
195
273
|
return dev(rolldownOptions, output, {
|
|
196
|
-
|
|
197
|
-
|
|
274
|
+
/**
|
|
275
|
+
* Admits Rolldown's non-awaited callback without starting asynchronous host work on the binding callback stack.
|
|
276
|
+
* The Subject retains Error values as control events and successful values as lossless patch data until its quiet edge.
|
|
277
|
+
*/
|
|
278
|
+
onHmrUpdates: (result: HmrUpdatesResult) => {
|
|
279
|
+
hmrResults.next(result)
|
|
280
|
+
},
|
|
281
|
+
// Initial and later complete builds share one admission path; startup merely observes the first OutputAction.
|
|
282
|
+
onOutput: (result: DevOutputResult) => {
|
|
283
|
+
hostActions.next({ kind: 'output', result: result })
|
|
284
|
+
},
|
|
198
285
|
rebuildStrategy: 'never',
|
|
199
|
-
watch: {
|
|
286
|
+
watch: {
|
|
287
|
+
// Rolldown must observe every source generation and emit every incremental factory: later patches do not
|
|
288
|
+
// reconstruct modules changed only by an earlier callback. RxJS conflates publication after compilation,
|
|
289
|
+
// preserving the complete patch sequence while avoiding repeated physical DevTools notifications.
|
|
290
|
+
skipWrite: false,
|
|
291
|
+
useDebounce: false
|
|
292
|
+
}
|
|
200
293
|
})
|
|
201
294
|
}
|
|
202
295
|
|
|
203
|
-
/**
|
|
204
|
-
function
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
return
|
|
208
|
-
}
|
|
209
|
-
hostTasks.enqueue('wx HMR publication failed', () => publishUpdates(result))
|
|
296
|
+
/** Centralizes the one diagnostic and DevEngine command used by every rebuild authority. */
|
|
297
|
+
function requestFullBuild(reason: string | undefined): void {
|
|
298
|
+
server.config.logger.info(`[vpt] wx full rebuild required${reason ? `: ${reason}` : ''}`)
|
|
299
|
+
engine.triggerFullBuild()
|
|
210
300
|
}
|
|
211
301
|
|
|
212
|
-
/**
|
|
302
|
+
/** Selects active updates and publishes their resulting host transaction. */
|
|
213
303
|
async function publishUpdates(result: HmrUpdates): Promise<void> {
|
|
214
|
-
|
|
304
|
+
/*
|
|
305
|
+
* This transaction-local array preserves every patch in one pass and permits immediate rebuild dominance. It never escapes
|
|
306
|
+
* the serialized action. A filter/find/flatMap pipeline would scan a large burst repeatedly and allocate an intermediate.
|
|
307
|
+
*/
|
|
308
|
+
const patches: PatchUpdate[] = []
|
|
215
309
|
for (const { clientId, update } of result.updates) {
|
|
216
310
|
if (!publisher.isCurrentBuild(clientId) || update.type === 'Noop') {
|
|
217
311
|
continue
|
|
218
312
|
}
|
|
219
313
|
|
|
220
314
|
if (update.type === 'Patch') {
|
|
221
|
-
|
|
315
|
+
patches.push(update)
|
|
222
316
|
continue
|
|
223
317
|
}
|
|
224
318
|
|
|
225
|
-
|
|
226
|
-
engine.triggerFullBuild()
|
|
319
|
+
requestFullBuild(update.reason)
|
|
227
320
|
return
|
|
228
321
|
}
|
|
229
322
|
|
|
230
|
-
|
|
323
|
+
await publishPatchBatch(patches)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** Publishes one coherent WXSS, patch-file, and Rolldown-frontier transaction. */
|
|
327
|
+
async function publishPatchBatch(patches: readonly PatchUpdate[]): Promise<void> {
|
|
328
|
+
if (patches.length === 0) {
|
|
231
329
|
return
|
|
232
330
|
}
|
|
233
331
|
|
|
234
332
|
// `onHmrUpdates` is the transaction boundary after every affected transform has updated graph and candidate state.
|
|
235
333
|
// Every non-CSS edit may alter imports or Tailwind classes; rendering broadly and comparing finalized bytes avoids
|
|
236
334
|
// source scanning while preventing unrelated JavaScript edits from notifying DevTools through an identical rename.
|
|
237
|
-
await
|
|
335
|
+
await styleCapture.publishChanged(patches.flatMap((patch) => patch.changedIds))
|
|
238
336
|
|
|
239
337
|
// Publish global.wxss before the matching JavaScript patch so DevTools observes a coherent HMR transaction.
|
|
240
338
|
// The physical file must exist before Rolldown advances: once committed, later patches may be generated relative to
|
|
241
339
|
// this batch even if DevTools has not observed its file event yet. PatchPublisher keeps the unapplied range cumulative,
|
|
242
340
|
// so any later file generation still carries every factory needed to bridge the runtime's older application frontier.
|
|
243
|
-
await publisher.produce(
|
|
244
|
-
|
|
245
|
-
await commitPublishedBatch(batch)
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/** Publishes the style projection for one completed patch transaction when its source or topology may have changed. */
|
|
249
|
-
async function publishChangedStyles(batch: readonly PatchUpdate[]): Promise<void> {
|
|
250
|
-
const changedIds = batch.flatMap((patch) => patch.changedIds)
|
|
251
|
-
const styleChanged = changedIds.some(isGlobalStyleRequest)
|
|
252
|
-
const candidatesChanged = changedIds.some((id) => !isCSSRequest(id))
|
|
253
|
-
if (!styleChanged && !candidatesChanged) {
|
|
254
|
-
return
|
|
255
|
-
}
|
|
341
|
+
await publisher.produce(patches)
|
|
256
342
|
|
|
257
|
-
|
|
258
|
-
const getModuleInfo = styleState.getModuleInfo
|
|
259
|
-
if (!getModuleInfo) {
|
|
260
|
-
throw new Error('WX style graph is unavailable before HMR publication')
|
|
261
|
-
}
|
|
262
|
-
// Traverse topology exactly once; root refresh and final rendering consume this immutable transaction plan.
|
|
263
|
-
const styleIds = createGraphStylePlan(applicationEntryIds, getModuleInfo, (styleId) =>
|
|
264
|
-
styleState.processedStyles.has(styleId)
|
|
265
|
-
)
|
|
266
|
-
if (candidatesChanged) {
|
|
267
|
-
await refreshTailwindStyles(styleIds, styleState.processedStyles, async (rootId, requestId) =>
|
|
268
|
-
server.environments.client.pluginContainer.transform(await readFile(rootId, 'utf8'), requestId)
|
|
269
|
-
)
|
|
270
|
-
}
|
|
271
|
-
styleState.publishedWxss = await publishStyleHmr({
|
|
272
|
-
styleIds: styleIds,
|
|
273
|
-
outDir: server.config.build.outDir,
|
|
274
|
-
processedStyles: styleState.processedStyles,
|
|
275
|
-
publishedWxss: styleState.publishedWxss
|
|
276
|
-
})
|
|
343
|
+
await commitPublishedBatch(patches)
|
|
277
344
|
}
|
|
278
345
|
|
|
279
346
|
/**
|
|
@@ -291,21 +358,6 @@ export async function createWxDevHost({
|
|
|
291
358
|
await engine.notifyPayloadDelivered(patch.filename)
|
|
292
359
|
}
|
|
293
360
|
}
|
|
294
|
-
|
|
295
|
-
/** Rotates metadata after each successful complete output. */
|
|
296
|
-
function handleDevOutput(result: DevOutputResult): void {
|
|
297
|
-
if (result instanceof Error) {
|
|
298
|
-
logWxError(server.config.logger, 'wx dev build failed', result)
|
|
299
|
-
return
|
|
300
|
-
}
|
|
301
|
-
hostTasks.enqueue('wx dev build finalization failed', finalizeDevOutput)
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/** Rebinds the byte frontier after a complete build replaces the physical stylesheet outside HMR publication. */
|
|
305
|
-
async function finalizeDevOutput(): Promise<void> {
|
|
306
|
-
styleState.publishedWxss = await readFile(path.join(server.config.build.outDir, globalWxssFileName), 'utf8')
|
|
307
|
-
await rotateBuildSession()
|
|
308
|
-
}
|
|
309
361
|
}
|
|
310
362
|
|
|
311
363
|
/** The bound HTTP port, or undefined before Vite's server is listening. */
|
|
@@ -340,6 +392,12 @@ async function publishBuildMetadata(server: ViteDevServer, buildId: string, port
|
|
|
340
392
|
|
|
341
393
|
/** Replaces browser server URLs with the physical project directory consumed by WeChat DevTools. */
|
|
342
394
|
function installDevToolsPrinter(server: ViteDevServer): void {
|
|
395
|
+
/*
|
|
396
|
+
* Vite exposes printing as a mutable server callback because URLs are unknown until startup. WX serves control reports over
|
|
397
|
+
* HTTP but users open the physical output directory, so retaining the browser printer advertises unusable navigation URLs.
|
|
398
|
+
* Replacing only this presentation callback leaves resolved URLs and server routing untouched and naturally dies with the
|
|
399
|
+
* server instance; logging elsewhere would duplicate Vite's one readiness notification.
|
|
400
|
+
*/
|
|
343
401
|
server.printUrls = () => {
|
|
344
402
|
server.config.logger.info(
|
|
345
403
|
` ${colors.green('➜')} ${colors.bold('WeChat DevTools')}: ${colors.cyan(relativeToViteConfig(server.config.build.outDir, server.config.configFile, server.config.root))}`
|
|
@@ -371,6 +429,12 @@ const maximumBodyBytes = 64 * 1024
|
|
|
371
429
|
|
|
372
430
|
function readBody(req: Connect.IncomingMessage): Promise<string> {
|
|
373
431
|
return new Promise((resolve, reject) => {
|
|
432
|
+
/*
|
|
433
|
+
* Node delivers one HTTP body through multiple callback invocations, so this request-local mutable string is the minimal
|
|
434
|
+
* state that joins chunks in wire order. The 64 KiB bound caps both retained memory and repeated-concatenation work; the
|
|
435
|
+
* value is parsed once on end and then discarded. Parsing per chunk is invalid because JSON tokens may cross boundaries,
|
|
436
|
+
* while retaining request bodies in host state would couple unrelated Page reports and leak across the server lifecycle.
|
|
437
|
+
*/
|
|
374
438
|
let body = ''
|
|
375
439
|
req.on('data', (chunk: Buffer) => {
|
|
376
440
|
body += chunk.toString('utf8')
|
|
@@ -4,6 +4,7 @@ import path from 'node:path'
|
|
|
4
4
|
import type { BindingClientHmrUpdate } from 'rolldown/experimental'
|
|
5
5
|
|
|
6
6
|
export const developmentAppWxssFileName = 'app.wxss'
|
|
7
|
+
export const globalWxssFileName = 'assets/global.wxss'
|
|
7
8
|
export const hmrInfoFileName = 'hmr/info.js'
|
|
8
9
|
export const hmrPatchesFileName = 'hmr/patches.js'
|
|
9
10
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { catchError, concatMap, defer, EMPTY, filter, firstValueFrom, lastValueFrom, map, Subject } from 'rxjs'
|
|
2
|
+
|
|
3
|
+
type ActionEnvelope<Action> =
|
|
4
|
+
| Readonly<{ kind: 'action'; action: Action }>
|
|
5
|
+
| Readonly<{ kind: 'barrier'; resolve: () => void }>
|
|
6
|
+
|
|
7
|
+
export type HostActions<Action> = Readonly<{
|
|
8
|
+
complete: () => Promise<void>
|
|
9
|
+
next: (action: Action) => void
|
|
10
|
+
waitForAction: <Selected extends Action>(select: (action: Action) => action is Selected) => Promise<Selected>
|
|
11
|
+
waitForIdle: () => Promise<void>
|
|
12
|
+
}>
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Executes semantic host actions one at a time while keeping source admission synchronous.
|
|
16
|
+
*
|
|
17
|
+
* `concatMap` is the sole effect serializer: each deferred reducer call starts only after the prior Promise settles. Errors are
|
|
18
|
+
* converted to empty inner streams so one failed physical transaction is reported without terminating later host actions.
|
|
19
|
+
* Barrier envelopes participate in the same ordering but run no effect; resolving one proves every action admitted before it
|
|
20
|
+
* has settled. `waitForAction` observes this same admission edge for lifecycle gates without introducing a forwarding Subject.
|
|
21
|
+
* Completing the source drains already-admitted effects and gives shutdown one Promise for final quiescence.
|
|
22
|
+
*/
|
|
23
|
+
export function createHostActions<Action>(
|
|
24
|
+
apply: (action: Action) => void | Promise<void>,
|
|
25
|
+
reportError: (action: Action, error: unknown) => void
|
|
26
|
+
): HostActions<Action> {
|
|
27
|
+
/*
|
|
28
|
+
* This Subject is the host's only mutable admission edge. Independent callback stacks synchronously append semantic actions
|
|
29
|
+
* or barriers; concatMap owns the queued ordering and admits exactly one asynchronous effect at a time. A Promise chain would
|
|
30
|
+
* require mutation at every producer and is permanently rejected after one failure, while mergeMap could overlap physical
|
|
31
|
+
* writes and build rotation. The Subject retains no business frontier itself and is completed only after all external sources
|
|
32
|
+
* are quiescent, allowing lastValueFrom to prove the final queued effect settled.
|
|
33
|
+
*/
|
|
34
|
+
const envelopes = new Subject<ActionEnvelope<Action>>()
|
|
35
|
+
|
|
36
|
+
const completion = lastValueFrom(
|
|
37
|
+
envelopes.pipe(
|
|
38
|
+
concatMap((envelope) => {
|
|
39
|
+
if (envelope.kind === 'barrier') {
|
|
40
|
+
return defer(() => {
|
|
41
|
+
envelope.resolve()
|
|
42
|
+
return EMPTY
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
return defer(() => Promise.resolve(apply(envelope.action))).pipe(
|
|
46
|
+
catchError((error: unknown) => {
|
|
47
|
+
reportError(envelope.action, error)
|
|
48
|
+
return EMPTY
|
|
49
|
+
})
|
|
50
|
+
)
|
|
51
|
+
})
|
|
52
|
+
),
|
|
53
|
+
{ defaultValue: undefined }
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
complete: async () => {
|
|
58
|
+
envelopes.complete()
|
|
59
|
+
await completion
|
|
60
|
+
},
|
|
61
|
+
next(action) {
|
|
62
|
+
envelopes.next({ kind: 'action', action: action })
|
|
63
|
+
},
|
|
64
|
+
waitForAction(select) {
|
|
65
|
+
/*
|
|
66
|
+
* Subscribe to the multicast admission Subject rather than creating another lifecycle source. Barrier envelopes are
|
|
67
|
+
* internal ordering controls, so expose only semantic actions and let the caller's type guard select its milestone.
|
|
68
|
+
* firstValueFrom unsubscribes after the match; concatMap's independent subscription still applies the same action.
|
|
69
|
+
*/
|
|
70
|
+
return firstValueFrom(
|
|
71
|
+
envelopes.pipe(
|
|
72
|
+
filter((envelope) => envelope.kind === 'action'),
|
|
73
|
+
map((envelope) => envelope.action),
|
|
74
|
+
filter(select)
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
},
|
|
78
|
+
waitForIdle() {
|
|
79
|
+
/*
|
|
80
|
+
* The resolver is one transaction-local mutable latch. Enqueuing it behind ordinary actions makes resolution mean
|
|
81
|
+
* every earlier effect settled, without completing the shared source needed by final DevEngine output callbacks.
|
|
82
|
+
*/
|
|
83
|
+
const barrier = Promise.withResolvers<void>()
|
|
84
|
+
envelopes.next({ kind: 'barrier', resolve: barrier.resolve })
|
|
85
|
+
return barrier.promise
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto'
|
|
|
2
2
|
import { type PatchUpdate, renderHmrPatches } from './hmr-files.ts'
|
|
3
3
|
|
|
4
4
|
/** Abstracts the physical patches write; the engine owns the file destination. */
|
|
5
|
-
|
|
5
|
+
type WritePatches = (content: string) => Promise<void>
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Owns the cumulative sequence range between the host's published frontier and the runtime's applied frontier.
|
|
@@ -19,9 +19,22 @@ export type WritePatches = (content: string) => Promise<void>
|
|
|
19
19
|
*/
|
|
20
20
|
export class PatchPublisher {
|
|
21
21
|
private readonly writePatches: WritePatches
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* This mutable identity is the sole session authority for runtime reports and per-client Rolldown updates. startBuild rotates
|
|
25
|
+
* it only after a successful complete output reaches the serialized host reducer. Delayed reports retain their original ID
|
|
26
|
+
* and therefore fail isCurrentBuild instead of pruning or rebuilding the new session. An immutable constructor value cannot
|
|
27
|
+
* represent full-build rotation, while deriving identity from patches would leave empty builds without a client ID.
|
|
28
|
+
*/
|
|
22
29
|
private buildId: string | undefined
|
|
23
30
|
|
|
24
|
-
|
|
31
|
+
/*
|
|
32
|
+
* This mutable ordered suffix bridges two independent frontiers: Rolldown payloads become published immediately after the
|
|
33
|
+
* cumulative file is durable, but entries remain here until the runtime acknowledges applying their sequence. Appending is
|
|
34
|
+
* lossless because later patches are deltas; prefix removal is the only valid mutation because acknowledgements are monotonic
|
|
35
|
+
* frontiers. A Set, latest-only value, or immutable reconstruction would either lose duplicate filenames/order or copy the
|
|
36
|
+
* complete unapplied history for every burst.
|
|
37
|
+
*/
|
|
25
38
|
private readonly pendingPatches: PatchUpdate[] = []
|
|
26
39
|
|
|
27
40
|
// Explicit field assignment: node --test strips types and does not support parameter properties.
|
|
@@ -64,6 +77,10 @@ export class PatchPublisher {
|
|
|
64
77
|
* retaining the gap between published and applied frontiers is what lets DevTools safely miss intermediate file events.
|
|
65
78
|
*/
|
|
66
79
|
acknowledge(seq: number): void {
|
|
80
|
+
/*
|
|
81
|
+
* This local counter scans the monotonic prefix without allocating or invoking a callback for each patch. It exists only
|
|
82
|
+
* long enough to supply splice's prefix length; pendingPatches remains the sole durable frontier and is mutated once.
|
|
83
|
+
*/
|
|
67
84
|
let appliedCount = 0
|
|
68
85
|
|
|
69
86
|
while (appliedCount < this.pendingPatches.length && this.pendingPatches[appliedCount].seq <= seq) {
|