vite-plugin-taro 0.5.12 → 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/plugins.js +22 -1
- package/dist/node/plugins/h5/resolver/module-resolver.js +2 -1
- package/dist/node/plugins/tailwind/tailwind-css.d.ts +1 -0
- package/dist/node/plugins/tailwind/tailwind-css.js +3 -0
- 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 +5 -1
- package/dist/node/plugins/wx/dev/dev-host.js +170 -47
- package/dist/node/plugins/wx/dev/hmr-files.d.ts +10 -0
- package/dist/node/plugins/wx/dev/hmr-files.js +12 -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 +25 -2
- package/dist/node/plugins/wx/dev/plugins.js +61 -9
- 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 +10 -5
- package/dist/node/plugins/wx/dev/wx-dev-options.js +47 -24
- 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/output/files.d.ts +1 -1
- package/dist/node/plugins/wx/output/files.js +6 -1
- package/dist/node/plugins/wx/plugins.js +13 -6
- package/dist/node/plugins/wx/resolve/resolver.d.ts +2 -1
- package/dist/node/plugins/wx/resolve/resolver.js +34 -19
- package/dist/node/plugins/wx/styles/plugins.d.ts +3 -0
- package/dist/node/plugins/wx/styles/plugins.js +111 -0
- package/dist/node/plugins/wx/styles/transform-wx-style.d.ts +8 -0
- package/dist/node/plugins/wx/styles/transform-wx-style.js +9 -0
- package/dist/node/plugins/wx/styles/utils.d.ts +39 -0
- package/dist/node/plugins/wx/styles/utils.js +95 -0
- 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/dist/node/utils/vite.d.ts +17 -0
- package/dist/node/utils/vite.js +43 -0
- package/dist/node/vite-plugin.js +0 -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/plugins.ts +22 -1
- package/src/node/plugins/h5/resolver/module-resolver.ts +2 -1
- package/src/node/plugins/tailwind/tailwind-css.ts +4 -0
- 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 +198 -63
- package/src/node/plugins/wx/dev/hmr-files.ts +13 -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 +67 -10
- 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 +53 -32
- 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/output/files.ts +6 -1
- package/src/node/plugins/wx/plugins.ts +16 -6
- package/src/node/plugins/wx/resolve/resolver.ts +36 -19
- package/src/node/plugins/wx/styles/plugins.ts +125 -0
- package/src/node/plugins/wx/styles/transform-wx-style.ts +11 -0
- package/src/node/plugins/wx/styles/utils.ts +119 -0
- package/src/node/utils/modules.ts +4 -3
- package/src/node/utils/packages.ts +2 -2
- package/src/node/utils/vite.ts +67 -0
- package/src/node/vite-plugin.ts +0 -2
- package/dist/node/plugins/css/plugins.d.ts +0 -4
- package/dist/node/plugins/css/plugins.js +0 -119
- 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/utils/serialized-task-queue.d.ts +0 -13
- package/dist/node/utils/serialized-task-queue.js +0 -25
- package/src/node/plugins/css/plugins.ts +0 -135
- package/src/node/plugins/wx/dev/create-wx-dev-mode.ts +0 -14
- package/src/node/utils/serialized-task-queue.ts +0 -32
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { GetModuleInfo, Plugin } from 'rolldown'
|
|
2
|
+
import { isCSSRequest } from 'vite'
|
|
3
|
+
import { normalizeModuleId } from '../../../utils/modules.ts'
|
|
4
|
+
import { transformWxStyle } from '../styles/transform-wx-style.ts'
|
|
5
|
+
import {
|
|
6
|
+
composeGraphStyleCss,
|
|
7
|
+
createGraphStylePlan,
|
|
8
|
+
createTailwindSidecarId,
|
|
9
|
+
extractViteCss,
|
|
10
|
+
isGlobalStyleRequest
|
|
11
|
+
} from '../styles/utils.ts'
|
|
12
|
+
import { globalWxssFileName, writeHmrFile } from './hmr-files.ts'
|
|
13
|
+
|
|
14
|
+
type ProcessedStyle = Readonly<{
|
|
15
|
+
css: string
|
|
16
|
+
isTailwindRoot: boolean
|
|
17
|
+
}>
|
|
18
|
+
|
|
19
|
+
type CompleteOutputFile =
|
|
20
|
+
| Readonly<{ type: 'asset'; fileName: string; source: string | Uint8Array }>
|
|
21
|
+
| Readonly<{ type: 'chunk'; fileName: string }>
|
|
22
|
+
|
|
23
|
+
export type StyleCaptureAction =
|
|
24
|
+
| Readonly<{ kind: 'capture-graph'; getModuleInfo: GetModuleInfo }>
|
|
25
|
+
| Readonly<{ kind: 'capture-style'; id: string; style: ProcessedStyle }>
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Owns capture hooks, the host's style projection, and its physical WXSS publication frontier.
|
|
29
|
+
*
|
|
30
|
+
* Rolldown remains authoritative for topology through its live graph reader. Plugin hooks emit typed actions so the host can
|
|
31
|
+
* serialize captures with every other effect; only the matching capture methods mutate this projection.
|
|
32
|
+
*/
|
|
33
|
+
export function createStyleCapture({
|
|
34
|
+
applicationEntryIds,
|
|
35
|
+
outDir,
|
|
36
|
+
emit,
|
|
37
|
+
transformTailwindRoot
|
|
38
|
+
}: {
|
|
39
|
+
applicationEntryIds: readonly string[]
|
|
40
|
+
outDir: string
|
|
41
|
+
emit: (action: StyleCaptureAction) => void
|
|
42
|
+
transformTailwindRoot: (rootId: string, requestId: string) => Promise<Readonly<{ code: string }> | null>
|
|
43
|
+
}): Readonly<{
|
|
44
|
+
bindOutput: (output: readonly CompleteOutputFile[]) => void
|
|
45
|
+
captureGraph: (reader: GetModuleInfo) => void
|
|
46
|
+
captureStyle: (id: string, style: ProcessedStyle) => void
|
|
47
|
+
plugin: Plugin
|
|
48
|
+
publishChanged: (changedIds: readonly string[]) => Promise<void>
|
|
49
|
+
}> {
|
|
50
|
+
/*
|
|
51
|
+
* This mutable capability is rebound by buildStart for each complete generation. Rolldown keeps the captured function live
|
|
52
|
+
* across incremental graph edits, so HMR traverses authoritative current topology without maintaining a shadow graph. It is
|
|
53
|
+
* undefined only before the first buildStart; retaining an older reader across a later complete build would bind transactions
|
|
54
|
+
* to the wrong engine generation, while snapshotting module data would require O(V + E) mutation on every edit.
|
|
55
|
+
*/
|
|
56
|
+
let getModuleInfo: GetModuleInfo | undefined
|
|
57
|
+
/*
|
|
58
|
+
* This mutable Map is the CSS projection Rolldown does not retain: successful final transform hooks replace one module's
|
|
59
|
+
* processed bytes, failed transforms intentionally leave its last valid generation, and a successful multi-root Tailwind
|
|
60
|
+
* refresh commits all replacements only after every sibling resolves. Entries are not deleted when topology changes because
|
|
61
|
+
* reachability comes from the live graph plan; eager deletion would need a duplicate reverse graph and could remove CSS still
|
|
62
|
+
* shared by another App/Page root. Its size is bounded by style module identities seen during this server lifecycle.
|
|
63
|
+
*/
|
|
64
|
+
const processedStyles = new Map<string, ProcessedStyle>()
|
|
65
|
+
/*
|
|
66
|
+
* This mutable value mirrors the WXSS bytes currently durable on disk. It has one owner but two real transition sources:
|
|
67
|
+
*
|
|
68
|
+
* 1. DevEngine physically writes complete output before onOutput, so bindOutput only observes and adopts that external
|
|
69
|
+
* commit. An omitted asset means DevEngine preserved the existing file and this value must remain unchanged.
|
|
70
|
+
* 2. Incremental HMR is physically written by this host, so publishChanged advances the value only after its atomic write
|
|
71
|
+
* succeeds (or after byte equality proves no write was needed).
|
|
72
|
+
*
|
|
73
|
+
* Combining these operations behind one generic update event would still require branching between “observe an existing
|
|
74
|
+
* write” and “perform a new write”, while hiding their different durability boundaries. A genuine single path would require
|
|
75
|
+
* preventing DevEngine from writing complete WXSS and transferring that entire output responsibility to the host, adding
|
|
76
|
+
* asset interception and ordering machinery. The two explicit methods are therefore the smallest accurate state model.
|
|
77
|
+
*/
|
|
78
|
+
let publishedWxss: string | undefined
|
|
79
|
+
|
|
80
|
+
const plugin: Plugin = {
|
|
81
|
+
name: 'vpt:wx-dev-style-capture',
|
|
82
|
+
buildStart() {
|
|
83
|
+
emit({ kind: 'capture-graph', getModuleInfo: (moduleId) => this.getModuleInfo(moduleId) })
|
|
84
|
+
},
|
|
85
|
+
transform(code, id) {
|
|
86
|
+
if (!isGlobalStyleRequest(id)) {
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const css = extractViteCss(code, id)
|
|
91
|
+
emit({
|
|
92
|
+
kind: 'capture-style',
|
|
93
|
+
id: normalizeModuleId(id),
|
|
94
|
+
style: {
|
|
95
|
+
css: css,
|
|
96
|
+
isTailwindRoot: css.includes('weapp-tailwindcss vite-generated-css:')
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
bindOutput(output) {
|
|
104
|
+
/*
|
|
105
|
+
* Complete output is one of the two real physical writers. Its emitted asset is therefore the authoritative frontier
|
|
106
|
+
* when present; omission means DevEngine preserved the existing file and this state must remain unchanged.
|
|
107
|
+
*/
|
|
108
|
+
const style = output.find(
|
|
109
|
+
(file): file is Extract<CompleteOutputFile, { type: 'asset' }> =>
|
|
110
|
+
file.type === 'asset' && file.fileName === globalWxssFileName
|
|
111
|
+
)
|
|
112
|
+
if (style) {
|
|
113
|
+
publishedWxss = typeof style.source === 'string' ? style.source : new TextDecoder().decode(style.source)
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
captureGraph(reader) {
|
|
117
|
+
getModuleInfo = reader
|
|
118
|
+
},
|
|
119
|
+
captureStyle(id, style) {
|
|
120
|
+
processedStyles.set(id, style)
|
|
121
|
+
},
|
|
122
|
+
plugin: plugin,
|
|
123
|
+
async publishChanged(changedIds) {
|
|
124
|
+
const styleChanged = changedIds.some(isGlobalStyleRequest)
|
|
125
|
+
const candidatesChanged = changedIds.some((id) => !isCSSRequest(id))
|
|
126
|
+
if (!styleChanged && !candidatesChanged) {
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
if (!getModuleInfo) {
|
|
130
|
+
throw new Error('WX style graph is unavailable before HMR publication')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Traverse topology exactly once; root refresh and final rendering consume this immutable transaction plan.
|
|
134
|
+
const styleIds = createGraphStylePlan(applicationEntryIds, getModuleInfo, (styleId) =>
|
|
135
|
+
processedStyles.has(styleId)
|
|
136
|
+
)
|
|
137
|
+
if (candidatesChanged) {
|
|
138
|
+
await refreshTailwindStyles(styleIds, processedStyles, transformTailwindRoot)
|
|
139
|
+
}
|
|
140
|
+
const css = composeGraphStyleCss(styleIds, (styleId) => requireProcessedStyle(processedStyles, styleId).css)
|
|
141
|
+
const wxss = (await transformWxStyle(css)).css
|
|
142
|
+
if (wxss !== publishedWxss) {
|
|
143
|
+
await writeHmrFile(outDir, globalWxssFileName, wxss)
|
|
144
|
+
}
|
|
145
|
+
// HMR is the second physical writer; advance the frontier only after its atomic write succeeds or was unnecessary.
|
|
146
|
+
publishedWxss = wxss
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Regenerates all reachable Tailwind roots and commits the cache only when every transform succeeds. */
|
|
152
|
+
async function refreshTailwindStyles(
|
|
153
|
+
styleIds: readonly string[],
|
|
154
|
+
processedStyles: Map<string, ProcessedStyle>,
|
|
155
|
+
transformRoot: (rootId: string, requestId: string) => Promise<Readonly<{ code: string }> | null>
|
|
156
|
+
): Promise<void> {
|
|
157
|
+
const roots = styleIds.filter((styleId) => requireProcessedStyle(processedStyles, styleId).isTailwindRoot)
|
|
158
|
+
const refreshedStyles = await Promise.all(
|
|
159
|
+
roots.map(async (rootId) => {
|
|
160
|
+
const requestId = createTailwindSidecarId(rootId)
|
|
161
|
+
const result = await transformRoot(rootId, requestId)
|
|
162
|
+
if (!result) {
|
|
163
|
+
throw new Error(`Tailwind sidecar transform produced no result: ${requestId}`)
|
|
164
|
+
}
|
|
165
|
+
return [rootId, { css: extractViteCss(result.code, requestId), isTailwindRoot: true }] as const
|
|
166
|
+
})
|
|
167
|
+
)
|
|
168
|
+
refreshedStyles.forEach(([rootId, style]) => {
|
|
169
|
+
processedStyles.set(rootId, style)
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function requireProcessedStyle(processedStyles: ReadonlyMap<string, ProcessedStyle>, styleId: string): ProcessedStyle {
|
|
174
|
+
const style = processedStyles.get(styleId)
|
|
175
|
+
if (!style) {
|
|
176
|
+
throw new Error(`WX style plan references uncaptured CSS: ${styleId}`)
|
|
177
|
+
}
|
|
178
|
+
return style
|
|
179
|
+
}
|
|
@@ -1,80 +1,140 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
1
2
|
import type { ServerResponse } from 'node:http'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
import colors from 'picocolors'
|
|
4
5
|
import { type DevEngine, type DevOptions, dev } from 'rolldown/experimental'
|
|
6
|
+
import { asyncScheduler } from 'rxjs'
|
|
5
7
|
import type { Connect, ViteDevServer } from 'vite'
|
|
6
8
|
import type { VitePluginTaroOptions } from '../../../../options.ts'
|
|
7
|
-
import {
|
|
9
|
+
import { createHmrResultsStream } from './create-hmr-results-stream.ts'
|
|
10
|
+
import { createStyleCapture, type StyleCaptureAction } from './create-style-capture.ts'
|
|
8
11
|
import {
|
|
12
|
+
developmentAppWxssFileName,
|
|
9
13
|
type HmrInfo,
|
|
10
14
|
hmrControlPath,
|
|
11
15
|
hmrInfoFileName,
|
|
12
16
|
hmrPatchesFileName,
|
|
13
17
|
type PatchUpdate,
|
|
18
|
+
renderDevelopmentAppWxss,
|
|
14
19
|
renderHmrInfo,
|
|
15
20
|
renderInitialHmrPatches,
|
|
16
21
|
writeHmrFile
|
|
17
22
|
} from './hmr-files.ts'
|
|
23
|
+
import { createHostActions } from './host-actions.ts'
|
|
18
24
|
import { PatchPublisher } from './patch-publisher.ts'
|
|
25
|
+
import { createRuntimeReportsStream, type RuntimeReport } from './runtime-reports.ts'
|
|
19
26
|
import { type BundledDev, installWxDevOptions, requireSingleOutput } from './wx-dev-options.ts'
|
|
20
27
|
|
|
21
28
|
export type WxDevHost = Readonly<{
|
|
22
29
|
close: () => Promise<void>
|
|
23
30
|
}>
|
|
24
31
|
|
|
25
|
-
type AppliedReport = Readonly<{
|
|
26
|
-
kind: 'applied'
|
|
27
|
-
buildId: string
|
|
28
|
-
seq: number
|
|
29
|
-
}>
|
|
30
|
-
|
|
31
|
-
/** The runtime hit an unrecoverable state (e.g. a corrupted patch range) and needs a full rebuild. */
|
|
32
|
-
type RebuildReport = Readonly<{
|
|
33
|
-
kind: 'rebuild'
|
|
34
|
-
buildId: string
|
|
35
|
-
reason: string
|
|
36
|
-
}>
|
|
37
|
-
|
|
38
|
-
type HmrReport = AppliedReport | RebuildReport
|
|
39
32
|
type HmrUpdatesResult = Parameters<NonNullable<DevOptions['onHmrUpdates']>>[0]
|
|
40
33
|
type HmrUpdates = Exclude<HmrUpdatesResult, Error>
|
|
41
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 = 32
|
|
42
48
|
|
|
43
49
|
/**
|
|
44
50
|
* Creates the wx dev host: the adapter that owns the physical Rolldown DevEngine (created
|
|
45
51
|
* with dev(...)) and the patch publisher, and replaces Vite's bundledDev.listen so the
|
|
46
52
|
* engine writes directly to the Mini Program output directory instead of serving browser
|
|
47
53
|
* HMR over HTTP.
|
|
54
|
+
*
|
|
55
|
+
* `applicationEntryIds` is the resolver's immutable cascade policy, not a second graph: it selects the App capsule followed
|
|
56
|
+
* by configured Page capsules from Rolldown's larger entry set. Rolldown remains the authority for every live import edge.
|
|
48
57
|
*/
|
|
49
58
|
export async function createWxDevHost({
|
|
50
59
|
server,
|
|
51
|
-
options
|
|
60
|
+
options,
|
|
61
|
+
applicationEntryIds
|
|
52
62
|
}: {
|
|
53
63
|
server: ViteDevServer
|
|
54
64
|
options: VitePluginTaroOptions
|
|
65
|
+
applicationEntryIds: readonly string[]
|
|
55
66
|
}): Promise<WxDevHost> {
|
|
56
67
|
const bundledDev = getBundledDev(server)
|
|
57
|
-
// Rolldown invokes output callbacks without awaiting their promises. This queue is the single owner of mutable HMR host
|
|
58
|
-
// state and physical metadata writes, preventing a later patch or build identity from being overwritten by older work.
|
|
59
|
-
const hostTasks = new SerializedTaskQueue((operation, error) => logWxError(server.config.logger, operation, error))
|
|
60
68
|
|
|
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
|
+
)
|
|
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
|
+
})
|
|
61
85
|
const publisher = new PatchPublisher((content) =>
|
|
62
86
|
writeHmrFile(server.config.build.outDir, hmrPatchesFileName, content)
|
|
63
87
|
)
|
|
64
88
|
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
const initialBuild = installWxDevOptions({ bundledDev, server, options })
|
|
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] })
|
|
68
91
|
const engine: DevEngine = await createEngine()
|
|
69
92
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
+
*/
|
|
74
124
|
bundledDev._devEngine = engine
|
|
75
125
|
bundledDev.triggerBundleRegenerationIfStale = async () => false
|
|
76
126
|
bundledDev.listen = async () => {
|
|
77
|
-
|
|
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
|
+
}
|
|
78
138
|
await engine.ensureCurrentBuildFinish()
|
|
79
139
|
}
|
|
80
140
|
|
|
@@ -82,7 +142,8 @@ export async function createWxDevHost({
|
|
|
82
142
|
// the actual port is not observable while onOutput runs for the first build. The App metadata
|
|
83
143
|
// is written once the port is real; later full builds rewrite it from onOutput.
|
|
84
144
|
server.httpServer?.once('listening', () => {
|
|
85
|
-
|
|
145
|
+
// Port availability is lifecycle data, so initial identity rotation follows prior output/capture actions in the reducer.
|
|
146
|
+
hostActions.next({ kind: 'listening' })
|
|
86
147
|
})
|
|
87
148
|
|
|
88
149
|
// The runtime's metadata-only reports land on the control path; the buildId in each report
|
|
@@ -93,8 +154,13 @@ export async function createWxDevHost({
|
|
|
93
154
|
|
|
94
155
|
return {
|
|
95
156
|
close: async () => {
|
|
96
|
-
|
|
97
|
-
|
|
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()
|
|
98
164
|
}
|
|
99
165
|
}
|
|
100
166
|
|
|
@@ -116,6 +182,10 @@ export async function createWxDevHost({
|
|
|
116
182
|
await publishBuildMetadata(server, buildId, port)
|
|
117
183
|
}
|
|
118
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
|
+
*/
|
|
119
189
|
async function handleReport(req: Connect.IncomingMessage, res: ServerResponse): Promise<void> {
|
|
120
190
|
if (req.method !== 'POST') {
|
|
121
191
|
res.statusCode = 404
|
|
@@ -124,8 +194,8 @@ export async function createWxDevHost({
|
|
|
124
194
|
}
|
|
125
195
|
|
|
126
196
|
try {
|
|
127
|
-
const report = JSON.parse(await readBody(req)) as
|
|
128
|
-
|
|
197
|
+
const report = JSON.parse(await readBody(req)) as RuntimeReport
|
|
198
|
+
runtimeReports.next(report)
|
|
129
199
|
res.end()
|
|
130
200
|
} catch (e) {
|
|
131
201
|
logWxError(server.config.logger, 'wx HMR report failed', e)
|
|
@@ -134,8 +204,39 @@ export async function createWxDevHost({
|
|
|
134
204
|
}
|
|
135
205
|
}
|
|
136
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
|
+
styleCapture.bindOutput(action.result.output)
|
|
232
|
+
return rotateBuildSession()
|
|
233
|
+
case 'listening':
|
|
234
|
+
return rotateBuildSession()
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
137
238
|
/** Applies one runtime receipt to the active physical patch history. */
|
|
138
|
-
function processReport(report:
|
|
239
|
+
function processReport(report: RuntimeReport): void {
|
|
139
240
|
// Delayed reports from older builds must never prune the live build's cumulative patch history.
|
|
140
241
|
if (!publisher.isCurrentBuild(report.buildId)) {
|
|
141
242
|
return
|
|
@@ -143,8 +244,7 @@ export async function createWxDevHost({
|
|
|
143
244
|
|
|
144
245
|
switch (report.kind) {
|
|
145
246
|
case 'rebuild': {
|
|
146
|
-
|
|
147
|
-
engine.triggerFullBuild()
|
|
247
|
+
requestFullBuild(report.reason)
|
|
148
248
|
return
|
|
149
249
|
}
|
|
150
250
|
case 'applied': {
|
|
@@ -160,50 +260,76 @@ export async function createWxDevHost({
|
|
|
160
260
|
const output = requireSingleOutput(rolldownOptions)
|
|
161
261
|
|
|
162
262
|
return dev(rolldownOptions, output, {
|
|
163
|
-
|
|
164
|
-
|
|
263
|
+
/**
|
|
264
|
+
* Admits Rolldown's non-awaited callback without starting asynchronous host work on the binding callback stack.
|
|
265
|
+
* The Subject retains Error values as control events and successful values as lossless patch data until its quiet edge.
|
|
266
|
+
*/
|
|
267
|
+
onHmrUpdates: (result: HmrUpdatesResult) => {
|
|
268
|
+
hmrResults.next(result)
|
|
269
|
+
},
|
|
270
|
+
// Initial and later complete builds share one admission path; startup merely observes the first OutputAction.
|
|
271
|
+
onOutput: (result: DevOutputResult) => {
|
|
272
|
+
hostActions.next({ kind: 'output', result: result })
|
|
273
|
+
},
|
|
165
274
|
rebuildStrategy: 'never',
|
|
166
|
-
watch: {
|
|
275
|
+
watch: {
|
|
276
|
+
// Rolldown must observe every source generation and emit every incremental factory: later patches do not
|
|
277
|
+
// reconstruct modules changed only by an earlier callback. RxJS conflates publication after compilation,
|
|
278
|
+
// preserving the complete patch sequence while avoiding repeated physical DevTools notifications.
|
|
279
|
+
skipWrite: false,
|
|
280
|
+
useDebounce: false
|
|
281
|
+
}
|
|
167
282
|
})
|
|
168
283
|
}
|
|
169
284
|
|
|
170
|
-
/**
|
|
171
|
-
function
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return
|
|
175
|
-
}
|
|
176
|
-
hostTasks.enqueue('wx HMR publication failed', () => publishUpdates(result))
|
|
285
|
+
/** Centralizes the one diagnostic and DevEngine command used by every rebuild authority. */
|
|
286
|
+
function requestFullBuild(reason: string | undefined): void {
|
|
287
|
+
server.config.logger.info(`[vpt] wx full rebuild required${reason ? `: ${reason}` : ''}`)
|
|
288
|
+
engine.triggerFullBuild()
|
|
177
289
|
}
|
|
178
290
|
|
|
179
|
-
/**
|
|
291
|
+
/** Selects active updates and publishes their resulting host transaction. */
|
|
180
292
|
async function publishUpdates(result: HmrUpdates): Promise<void> {
|
|
181
|
-
|
|
293
|
+
/*
|
|
294
|
+
* This transaction-local array preserves every patch in one pass and permits immediate rebuild dominance. It never escapes
|
|
295
|
+
* the serialized action. A filter/find/flatMap pipeline would scan a large burst repeatedly and allocate an intermediate.
|
|
296
|
+
*/
|
|
297
|
+
const patches: PatchUpdate[] = []
|
|
182
298
|
for (const { clientId, update } of result.updates) {
|
|
183
299
|
if (!publisher.isCurrentBuild(clientId) || update.type === 'Noop') {
|
|
184
300
|
continue
|
|
185
301
|
}
|
|
186
302
|
|
|
187
303
|
if (update.type === 'Patch') {
|
|
188
|
-
|
|
304
|
+
patches.push(update)
|
|
189
305
|
continue
|
|
190
306
|
}
|
|
191
307
|
|
|
192
|
-
|
|
193
|
-
engine.triggerFullBuild()
|
|
308
|
+
requestFullBuild(update.reason)
|
|
194
309
|
return
|
|
195
310
|
}
|
|
196
311
|
|
|
197
|
-
|
|
312
|
+
await publishPatchBatch(patches)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Publishes one coherent WXSS, patch-file, and Rolldown-frontier transaction. */
|
|
316
|
+
async function publishPatchBatch(patches: readonly PatchUpdate[]): Promise<void> {
|
|
317
|
+
if (patches.length === 0) {
|
|
198
318
|
return
|
|
199
319
|
}
|
|
200
320
|
|
|
321
|
+
// `onHmrUpdates` is the transaction boundary after every affected transform has updated graph and candidate state.
|
|
322
|
+
// Every non-CSS edit may alter imports or Tailwind classes; rendering broadly and comparing finalized bytes avoids
|
|
323
|
+
// source scanning while preventing unrelated JavaScript edits from notifying DevTools through an identical rename.
|
|
324
|
+
await styleCapture.publishChanged(patches.flatMap((patch) => patch.changedIds))
|
|
325
|
+
|
|
326
|
+
// Publish global.wxss before the matching JavaScript patch so DevTools observes a coherent HMR transaction.
|
|
201
327
|
// The physical file must exist before Rolldown advances: once committed, later patches may be generated relative to
|
|
202
328
|
// this batch even if DevTools has not observed its file event yet. PatchPublisher keeps the unapplied range cumulative,
|
|
203
329
|
// so any later file generation still carries every factory needed to bridge the runtime's older application frontier.
|
|
204
|
-
await publisher.produce(
|
|
330
|
+
await publisher.produce(patches)
|
|
205
331
|
|
|
206
|
-
await commitPublishedBatch(
|
|
332
|
+
await commitPublishedBatch(patches)
|
|
207
333
|
}
|
|
208
334
|
|
|
209
335
|
/**
|
|
@@ -221,15 +347,6 @@ export async function createWxDevHost({
|
|
|
221
347
|
await engine.notifyPayloadDelivered(patch.filename)
|
|
222
348
|
}
|
|
223
349
|
}
|
|
224
|
-
|
|
225
|
-
/** Rotates metadata after each successful complete output. */
|
|
226
|
-
function handleDevOutput(result: DevOutputResult): void {
|
|
227
|
-
if (result instanceof Error) {
|
|
228
|
-
logWxError(server.config.logger, 'wx dev build failed', result)
|
|
229
|
-
return
|
|
230
|
-
}
|
|
231
|
-
hostTasks.enqueue('wx dev build finalization failed', rotateBuildSession)
|
|
232
|
-
}
|
|
233
350
|
}
|
|
234
351
|
|
|
235
352
|
/** The bound HTTP port, or undefined before Vite's server is listening. */
|
|
@@ -245,7 +362,7 @@ function boundPort(server: ViteDevServer): number | undefined {
|
|
|
245
362
|
return address.port
|
|
246
363
|
}
|
|
247
364
|
|
|
248
|
-
/** Resets physical patches
|
|
365
|
+
/** Resets physical patches and then exposes one coherent build identity to a freshly compiled App heap. */
|
|
249
366
|
async function publishBuildMetadata(server: ViteDevServer, buildId: string, port: number): Promise<void> {
|
|
250
367
|
const info: HmrInfo = {
|
|
251
368
|
buildId,
|
|
@@ -254,10 +371,22 @@ async function publishBuildMetadata(server: ViteDevServer, buildId: string, port
|
|
|
254
371
|
|
|
255
372
|
await writeHmrFile(server.config.build.outDir, hmrPatchesFileName, renderInitialHmrPatches())
|
|
256
373
|
await writeHmrFile(server.config.build.outDir, hmrInfoFileName, renderHmrInfo(info))
|
|
374
|
+
// `removeDevelopmentAppWxss` kept the previous physical wrapper in place while the complete output was written. Replace
|
|
375
|
+
// it only now, after the empty patch frontier and matching identity are durable. DevTools treats `app.wxss` as an App root,
|
|
376
|
+
// so this write intentionally causes the one full refresh allowed at a complete-build boundary; the refreshed App reads
|
|
377
|
+
// the new info above. Incremental updates must never write this file because an App refresh could destroy the heap while
|
|
378
|
+
// its JavaScript patch is being acknowledged. They publish only the imported `assets/global.wxss` stylesheet instead.
|
|
379
|
+
await writeHmrFile(server.config.build.outDir, developmentAppWxssFileName, renderDevelopmentAppWxss(buildId))
|
|
257
380
|
}
|
|
258
381
|
|
|
259
382
|
/** Replaces browser server URLs with the physical project directory consumed by WeChat DevTools. */
|
|
260
383
|
function installDevToolsPrinter(server: ViteDevServer): void {
|
|
384
|
+
/*
|
|
385
|
+
* Vite exposes printing as a mutable server callback because URLs are unknown until startup. WX serves control reports over
|
|
386
|
+
* HTTP but users open the physical output directory, so retaining the browser printer advertises unusable navigation URLs.
|
|
387
|
+
* Replacing only this presentation callback leaves resolved URLs and server routing untouched and naturally dies with the
|
|
388
|
+
* server instance; logging elsewhere would duplicate Vite's one readiness notification.
|
|
389
|
+
*/
|
|
261
390
|
server.printUrls = () => {
|
|
262
391
|
server.config.logger.info(
|
|
263
392
|
` ${colors.green('➜')} ${colors.bold('WeChat DevTools')}: ${colors.cyan(relativeToViteConfig(server.config.build.outDir, server.config.configFile, server.config.root))}`
|
|
@@ -289,6 +418,12 @@ const maximumBodyBytes = 64 * 1024
|
|
|
289
418
|
|
|
290
419
|
function readBody(req: Connect.IncomingMessage): Promise<string> {
|
|
291
420
|
return new Promise((resolve, reject) => {
|
|
421
|
+
/*
|
|
422
|
+
* Node delivers one HTTP body through multiple callback invocations, so this request-local mutable string is the minimal
|
|
423
|
+
* state that joins chunks in wire order. The 64 KiB bound caps both retained memory and repeated-concatenation work; the
|
|
424
|
+
* value is parsed once on end and then discarded. Parsing per chunk is invalid because JSON tokens may cross boundaries,
|
|
425
|
+
* while retaining request bodies in host state would couple unrelated Page reports and leak across the server lifecycle.
|
|
426
|
+
*/
|
|
292
427
|
let body = ''
|
|
293
428
|
req.on('data', (chunk: Buffer) => {
|
|
294
429
|
body += chunk.toString('utf8')
|
|
@@ -3,6 +3,8 @@ import fs from 'node:fs/promises'
|
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import type { BindingClientHmrUpdate } from 'rolldown/experimental'
|
|
5
5
|
|
|
6
|
+
export const developmentAppWxssFileName = 'app.wxss'
|
|
7
|
+
export const globalWxssFileName = 'assets/global.wxss'
|
|
6
8
|
export const hmrInfoFileName = 'hmr/info.js'
|
|
7
9
|
export const hmrPatchesFileName = 'hmr/patches.js'
|
|
8
10
|
|
|
@@ -28,6 +30,17 @@ export function renderInitialHmrPatches(): string {
|
|
|
28
30
|
return 'module.exports = undefined;\n'
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Renders the native App style entry for one complete development build.
|
|
35
|
+
*
|
|
36
|
+
* Imported global CSS can change while the entry remains byte-identical. DevTools may then reuse an older persistent compile
|
|
37
|
+
* cache after it restarts. Tying this inert comment to the build identity invalidates that cache once per complete build,
|
|
38
|
+
* while incremental style updates continue changing only the imported file and therefore preserve the App heap.
|
|
39
|
+
*/
|
|
40
|
+
export function renderDevelopmentAppWxss(buildId: string): string {
|
|
41
|
+
return `@import "./assets/global.wxss";\n/* vpt-build:${buildId} */\n`
|
|
42
|
+
}
|
|
43
|
+
|
|
31
44
|
/**
|
|
32
45
|
* Renders the cumulative patch suffix as inert CommonJS data.
|
|
33
46
|
*
|