vite-plugin-taro 0.5.6 → 0.5.7
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/wx/dev/plugins.d.ts +22 -0
- package/dist/node/plugins/wx/dev/plugins.js +158 -0
- package/dist/node/plugins/wx/dev/react-refresh.d.ts +1 -2
- package/dist/node/plugins/wx/dev/react-refresh.js +12 -14
- package/dist/node/plugins/wx/dev/wx-dev-options.js +2 -1
- package/dist/node/plugins/wx/module.d.ts +2 -0
- package/dist/node/plugins/wx/module.js +2 -0
- package/dist/node/plugins/wx/plugins.js +1 -1
- package/dist/runtime/wx/capsule/page.js +1 -71
- package/dist/runtime/wx/dev/dev-runtime.js +161 -84
- package/package.json +3 -3
- package/src/node/plugins/wx/dev/plugins.ts +170 -0
- package/src/node/plugins/wx/dev/react-refresh.ts +12 -15
- package/src/node/plugins/wx/dev/wx-dev-options.ts +2 -1
- package/src/node/plugins/wx/module.ts +3 -0
- package/src/node/plugins/wx/plugins.ts +1 -1
- package/src/runtime/wx/capsule/page.ts +1 -93
- package/src/runtime/wx/dev/dev-runtime.ts +233 -89
- package/src/runtime/wx/wechat.d.ts +3 -6
- package/dist/node/plugins/wx/dev/plugin.d.ts +0 -7
- package/dist/node/plugins/wx/dev/plugin.js +0 -73
- package/src/node/plugins/wx/dev/plugin.ts +0 -82
|
@@ -3,7 +3,7 @@ import './app.ts'
|
|
|
3
3
|
|
|
4
4
|
// @ts-expect-error: The wx build replaces this private import with the configured Page component.
|
|
5
5
|
import PageComponent from '\0vpt:page-component'
|
|
6
|
-
import {
|
|
6
|
+
import { createPageConfig } from './taro-runtime.ts'
|
|
7
7
|
|
|
8
8
|
declare const __VITE_PLUGIN_TARO_PAGE_PATH__: string
|
|
9
9
|
declare const __VITE_PLUGIN_TARO_PAGE_CONFIG__: Record<string, unknown>
|
|
@@ -16,95 +16,3 @@ const config = createPageConfig(
|
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
export default config
|
|
19
|
-
|
|
20
|
-
// HMR
|
|
21
|
-
|
|
22
|
-
type LifecycleHandler = (this: unknown, ...args: unknown[]) => void
|
|
23
|
-
|
|
24
|
-
/** Forwards a call with the native page instance as `this`, matching Taro's invocation. */
|
|
25
|
-
function forward(handler: unknown, receiver: unknown, args: unknown[]): void {
|
|
26
|
-
;(handler as LifecycleHandler | undefined)?.apply(receiver, args)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** The old native projection retained while DevTools replaces its Page instance. */
|
|
30
|
-
type PageSnapshot = {
|
|
31
|
-
$taroPath: string
|
|
32
|
-
$taroParams: Record<string, unknown>
|
|
33
|
-
data: Record<string, unknown>
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
type NativePage = PageSnapshot & {
|
|
37
|
-
setData(data: Record<string, unknown>): void
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// DevTools re-executes the page and replays the replacement lifecycle on every edit. Taro's
|
|
41
|
-
// onUnload unmounts the React tree and onLoad mounts a fresh one, destroying state. While
|
|
42
|
-
// the runtime says a patch was just delivered, the pair is intercepted instead: onUnload
|
|
43
|
-
// captures the surviving page identity and its current native render data, and onLoad
|
|
44
|
-
// immediately paints that snapshot before rebinding and fully synchronizing the retained
|
|
45
|
-
// tree. The capsule module is cached across re-executions, so this closure holds the capture.
|
|
46
|
-
// Ordinary navigation (no patch) passes through unchanged. The wrappers are regular
|
|
47
|
-
// functions so the native page instance (`this`) reaches the original handlers.
|
|
48
|
-
if (typeof __rolldown_runtime__ !== 'undefined') {
|
|
49
|
-
const originalOnUnload = config.onUnload
|
|
50
|
-
const originalOnLoad = config.onLoad
|
|
51
|
-
const originalOnShow = config.onShow
|
|
52
|
-
|
|
53
|
-
// This is the sole cross-instance handoff. It is mutable because DevTools destroys the
|
|
54
|
-
// old native Page before creating its replacement; ordinary navigation never reads it.
|
|
55
|
-
let captured: PageSnapshot | undefined
|
|
56
|
-
|
|
57
|
-
config.onUnload = function (this: unknown, ...args: unknown[]) {
|
|
58
|
-
if (__rolldown_runtime__.isHotReloading()) {
|
|
59
|
-
const instance = this as unknown as NativePage
|
|
60
|
-
captured = {
|
|
61
|
-
$taroPath: instance.$taroPath,
|
|
62
|
-
$taroParams: instance.$taroParams,
|
|
63
|
-
// WeChat owns this serializable view-model snapshot. Retaining it is cheaper
|
|
64
|
-
// than deep-cloning the complete recursive Taro node tree before every edit.
|
|
65
|
-
data: instance.data
|
|
66
|
-
}
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
forward(originalOnUnload, this, args)
|
|
70
|
-
}
|
|
71
|
-
config.onLoad = function (this: unknown, ...args: unknown[]) {
|
|
72
|
-
if (__rolldown_runtime__.isHotReloading() && captured) {
|
|
73
|
-
const instance = this as unknown as NativePage
|
|
74
|
-
// Paint the last native projection as the first bridge operation. Taro's full
|
|
75
|
-
// performUpdate below runs in a timer, so this direct setData removes most of the
|
|
76
|
-
// empty-page interval without delaying synchronization to the current tree.
|
|
77
|
-
instance.setData(captured.data)
|
|
78
|
-
// Restore the surviving page identity onto the replacement native instance: the
|
|
79
|
-
// re-loaded $taroPath embeds a fresh timestamp, so without this the tree and the
|
|
80
|
-
// native side would disagree on the page key.
|
|
81
|
-
instance.$taroPath = captured.$taroPath
|
|
82
|
-
instance.$taroParams = captured.$taroParams
|
|
83
|
-
// Rebind the retained tree to the replacement receiver and resync the native
|
|
84
|
-
// data: updateChildNodes enqueues the full hydrated node tree (root.cn) — the
|
|
85
|
-
// surviving tree's mutations were consumed long ago, so without it the payload
|
|
86
|
-
// queue is empty and the new receiver renders nothing.
|
|
87
|
-
injectPageInstance(instance as unknown as Parameters<typeof injectPageInstance>[0], captured.$taroPath)
|
|
88
|
-
Current.page = instance as unknown as typeof Current.page
|
|
89
|
-
const pageElement = document.getElementById(captured.$taroPath) as unknown as {
|
|
90
|
-
ctx: unknown
|
|
91
|
-
updateChildNodes: () => void
|
|
92
|
-
performUpdate: (initRender?: boolean) => void
|
|
93
|
-
}
|
|
94
|
-
pageElement.ctx = instance
|
|
95
|
-
pageElement.updateChildNodes()
|
|
96
|
-
pageElement.performUpdate(true)
|
|
97
|
-
return
|
|
98
|
-
}
|
|
99
|
-
forward(originalOnLoad, this, args)
|
|
100
|
-
}
|
|
101
|
-
config.onShow = function (this: unknown, ...args: unknown[]) {
|
|
102
|
-
if (__rolldown_runtime__.isHotReloading()) {
|
|
103
|
-
// The window ends at the first show; the user's onShow must not re-run on a
|
|
104
|
-
// synthetic re-execution (it could reset state).
|
|
105
|
-
__rolldown_runtime__.clearHotReloading()
|
|
106
|
-
return
|
|
107
|
-
}
|
|
108
|
-
forward(originalOnShow, this, args)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
@@ -19,6 +19,20 @@ type HmrInfo = Readonly<{
|
|
|
19
19
|
endpoint: string
|
|
20
20
|
}>
|
|
21
21
|
|
|
22
|
+
/** App-heap HMR identity; only the committed application frontier mutates. */
|
|
23
|
+
type HmrSession = {
|
|
24
|
+
/** Rejects delayed patch files and identifies reports after a newer full build exists. */
|
|
25
|
+
readonly buildId: string
|
|
26
|
+
/** Fixed control URL discovered from the Vite server that produced this full build. */
|
|
27
|
+
readonly endpoint: string
|
|
28
|
+
/**
|
|
29
|
+
* Highest contiguous Rolldown sequence whose factories, graph propagation, and accept
|
|
30
|
+
* callbacks all succeeded. Replayed Page shells read this watermark; it advances only
|
|
31
|
+
* after an atomic batch succeeds and resets only with a new App heap.
|
|
32
|
+
*/
|
|
33
|
+
appliedSeq: number
|
|
34
|
+
}
|
|
35
|
+
|
|
22
36
|
/** One physical hmr/patches.js payload: the build identity plus one patch program per update. */
|
|
23
37
|
type PatchPayload = Readonly<{
|
|
24
38
|
buildId: string
|
|
@@ -40,40 +54,108 @@ type HmrUpdate = Readonly<{
|
|
|
40
54
|
|
|
41
55
|
type AcceptCallback = (moduleExports: unknown) => void
|
|
42
56
|
|
|
57
|
+
type PageSnapshot = Readonly<{
|
|
58
|
+
$taroPath: string
|
|
59
|
+
$taroParams: Record<string, unknown>
|
|
60
|
+
data: Record<string, unknown>
|
|
61
|
+
}>
|
|
62
|
+
|
|
63
|
+
type NativePage = {
|
|
64
|
+
$taroPath: string
|
|
65
|
+
$taroParams: Record<string, unknown>
|
|
66
|
+
data: Record<string, unknown>
|
|
67
|
+
setData(data: Record<string, unknown>): void
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type HmrPageConfig = {
|
|
71
|
+
onUnload?: unknown
|
|
72
|
+
onLoad?: unknown
|
|
73
|
+
onShow?: unknown
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
type TaroRoot = {
|
|
77
|
+
ctx: unknown
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Immutable bridge references plus replacement transactions owned by one Taro singleton. */
|
|
81
|
+
type TaroState = {
|
|
82
|
+
/** Taro's existing current-page source of truth; replacement onLoad switches its receiver. */
|
|
83
|
+
readonly current: { page: NativePage | null }
|
|
84
|
+
/** Taro's virtual document, used to find the retained root by its preserved unique path. */
|
|
85
|
+
readonly document: { getElementById(path: string): unknown }
|
|
86
|
+
/** Replaces Taro's native lifecycle receiver without remounting the retained React subtree. */
|
|
87
|
+
readonly injectPageInstance: (instance: unknown, path: string) => void
|
|
88
|
+
/**
|
|
89
|
+
* Short-lived route transactions. Absence means ordinary lifecycle; null means armed
|
|
90
|
+
* before onUnload or snapshot-consumed before onShow; a snapshot exists only between
|
|
91
|
+
* replacement onUnload and onLoad. The large data reference is therefore released in
|
|
92
|
+
* onLoad, while the null marker remains just long enough to suppress synthetic onShow.
|
|
93
|
+
*/
|
|
94
|
+
readonly pageReplacements: Map<string, PageSnapshot | null>
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Forwards a native lifecycle with its original Page receiver. */
|
|
98
|
+
function forward(handler: unknown, receiver: unknown, args: unknown[]): void {
|
|
99
|
+
if (typeof handler === 'function') handler.apply(receiver, args)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Narrows the untyped element returned across the Taro connection boundary. */
|
|
103
|
+
function isTaroRoot(value: unknown): value is TaroRoot {
|
|
104
|
+
return typeof value === 'object' && value !== null && 'ctx' in value
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Shared no-op CSS contract because physical rebuilds replace styles wholesale. */
|
|
108
|
+
const hotContextInternals = Object.freeze({
|
|
109
|
+
updateStyle(): void {},
|
|
110
|
+
removeStyle(): void {}
|
|
111
|
+
})
|
|
112
|
+
|
|
43
113
|
/**
|
|
44
114
|
* Per-module hot state, mirroring Rolldown's web runtime: accept is a passive registration;
|
|
45
115
|
* the propagation invokes the previous execution's callbacks with the fresh exports.
|
|
46
116
|
*/
|
|
47
117
|
class WxHotContext {
|
|
48
|
-
readonly _internal =
|
|
49
|
-
updateStyle(): void {},
|
|
50
|
-
removeStyle(): void {}
|
|
51
|
-
}
|
|
118
|
+
readonly _internal = hotContextInternals
|
|
52
119
|
|
|
53
|
-
/**
|
|
54
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Callbacks registered by this module execution. The array is allocated on first accept,
|
|
122
|
+
* remains attached to the old execution across cache eviction, and is invoked by the next
|
|
123
|
+
* boundary execution. Undefined means this context stays passive.
|
|
124
|
+
*/
|
|
125
|
+
private acceptCallbacks: Array<AcceptCallback | undefined> | undefined
|
|
55
126
|
|
|
56
|
-
/**
|
|
57
|
-
private
|
|
127
|
+
/** Stable module identity and shared sparse index used only when this context accepts. */
|
|
128
|
+
private readonly moduleId: string
|
|
129
|
+
private readonly acceptingContexts: Map<string, WxHotContext>
|
|
58
130
|
|
|
59
|
-
|
|
60
|
-
this.
|
|
131
|
+
constructor(moduleId: string, acceptingContexts: Map<string, WxHotContext>) {
|
|
132
|
+
this.moduleId = moduleId
|
|
133
|
+
this.acceptingContexts = acceptingContexts
|
|
61
134
|
}
|
|
62
135
|
|
|
63
|
-
|
|
64
|
-
|
|
136
|
+
accept(callback?: AcceptCallback): void {
|
|
137
|
+
if (!this.acceptCallbacks) {
|
|
138
|
+
this.acceptCallbacks = [callback]
|
|
139
|
+
this.acceptingContexts.set(this.moduleId, this)
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
this.acceptCallbacks.push(callback)
|
|
65
143
|
}
|
|
66
144
|
|
|
67
|
-
/** Invokes this old context's callbacks
|
|
68
|
-
runAccept(moduleExports: unknown):
|
|
69
|
-
|
|
70
|
-
|
|
145
|
+
/** Invokes this old context's callbacks; invalidation aborts the update synchronously. */
|
|
146
|
+
runAccept(moduleExports: unknown): void {
|
|
147
|
+
const callbacks = this.acceptCallbacks
|
|
148
|
+
if (!callbacks) {
|
|
149
|
+
throw new Error(`passive hot context reached as boundary: ${this.moduleId}`)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for (const callback of callbacks) {
|
|
153
|
+
callback?.(moduleExports)
|
|
71
154
|
}
|
|
72
|
-
return this.invalidationReason
|
|
73
155
|
}
|
|
74
156
|
|
|
75
|
-
invalidate(reason?: string):
|
|
76
|
-
|
|
157
|
+
invalidate(reason?: string): never {
|
|
158
|
+
throw new Error(reason ?? 'the accepting module invalidated the update')
|
|
77
159
|
}
|
|
78
160
|
|
|
79
161
|
// Vite's generated CSS module calls hot.prune with its style teardown; the physical
|
|
@@ -83,12 +165,18 @@ class WxHotContext {
|
|
|
83
165
|
|
|
84
166
|
/** The WX host: extends the Rolldown contract instead of reimplementing it. */
|
|
85
167
|
class WxDevRuntime extends DevRuntime {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
168
|
+
/**
|
|
169
|
+
* One session for this App heap. Undefined only before app.js consumes hmr/info.js; its
|
|
170
|
+
* identity and endpoint then stay fixed while appliedSeq records the committed frontier.
|
|
171
|
+
*/
|
|
172
|
+
private session: HmrSession | undefined
|
|
90
173
|
|
|
91
|
-
/**
|
|
174
|
+
/**
|
|
175
|
+
* Sparse current-generation accepting boundaries keyed by module id. Entries alone must
|
|
176
|
+
* outlive Rolldown module-cache eviction so old callbacks can receive fresh exports.
|
|
177
|
+
* createModuleHotContext removes the prior generation immediately; first accept inserts
|
|
178
|
+
* the new one. Passive contexts are never retained, so space is O(number of boundaries).
|
|
179
|
+
*/
|
|
92
180
|
private readonly moduleHotContexts = new Map<string, WxHotContext>()
|
|
93
181
|
|
|
94
182
|
constructor() {
|
|
@@ -99,13 +187,13 @@ class WxDevRuntime extends DevRuntime {
|
|
|
99
187
|
|
|
100
188
|
/**
|
|
101
189
|
* Generated code always calls this before registerModule and reads `_internal` from the
|
|
102
|
-
* return value.
|
|
103
|
-
*
|
|
190
|
+
* return value. Each execution immediately retires its previous accepting boundary;
|
|
191
|
+
* calling accept registers the new context after the apply plan captured old callbacks.
|
|
104
192
|
*/
|
|
105
193
|
override createModuleHotContext(moduleId: string): WxHotContext {
|
|
106
|
-
|
|
107
|
-
this.moduleHotContexts.
|
|
108
|
-
return
|
|
194
|
+
// A new execution supersedes the old boundary before it decides whether to accept.
|
|
195
|
+
this.moduleHotContexts.delete(moduleId)
|
|
196
|
+
return new WxHotContext(moduleId, this.moduleHotContexts)
|
|
109
197
|
}
|
|
110
198
|
|
|
111
199
|
/** Computes accepting boundaries and every executed module that must be re-armed. */
|
|
@@ -138,7 +226,7 @@ class WxDevRuntime extends DevRuntime {
|
|
|
138
226
|
if (updateSet.has(moduleId)) return undefined
|
|
139
227
|
updateSet.add(moduleId)
|
|
140
228
|
|
|
141
|
-
if (this.moduleHotContexts.
|
|
229
|
+
if (this.moduleHotContexts.has(moduleId)) {
|
|
142
230
|
boundaries.push(moduleId)
|
|
143
231
|
return undefined
|
|
144
232
|
}
|
|
@@ -187,56 +275,140 @@ class WxDevRuntime extends DevRuntime {
|
|
|
187
275
|
}
|
|
188
276
|
|
|
189
277
|
for (const { moduleId, hotContext } of applies) {
|
|
190
|
-
|
|
191
|
-
if (invalidationReason) {
|
|
192
|
-
throw new Error(`${moduleId}: ${invalidationReason}`)
|
|
193
|
-
}
|
|
278
|
+
hotContext?.runAccept(this.initModule(moduleId))
|
|
194
279
|
}
|
|
195
280
|
}
|
|
196
281
|
|
|
197
282
|
/** Consumed once per App heap from hmr/info.js; the host buildId identifies its cumulative patch history. */
|
|
198
283
|
initialize(info: HmrInfo): void {
|
|
199
|
-
if (this.
|
|
284
|
+
if (this.session) return
|
|
285
|
+
this.session = { ...info, appliedSeq: 0 }
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* One-shot bridge to the application's Taro singleton. It cannot be imported into this
|
|
290
|
+
* separately bundled global runtime without creating a second Taro identity. Undefined
|
|
291
|
+
* only before the serve-only facade connection; route transactions share its lifetime.
|
|
292
|
+
*/
|
|
293
|
+
private taro: TaroState | undefined
|
|
294
|
+
|
|
295
|
+
/** Connects HMR to the same Taro singleton used by the application module graph. */
|
|
296
|
+
connectTaro(
|
|
297
|
+
current: TaroState['current'],
|
|
298
|
+
document: TaroState['document'],
|
|
299
|
+
injectPageInstance: TaroState['injectPageInstance']
|
|
300
|
+
): void {
|
|
301
|
+
if (this.taro) {
|
|
200
302
|
return
|
|
201
303
|
}
|
|
202
|
-
|
|
304
|
+
|
|
305
|
+
this.taro = {
|
|
306
|
+
current,
|
|
307
|
+
document,
|
|
308
|
+
injectPageInstance,
|
|
309
|
+
pageReplacements: new Map()
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Injects snapshot-preserving behavior into one route-specific Taro Page configuration. */
|
|
314
|
+
injectPageHmr(config: HmrPageConfig, route: string): void {
|
|
315
|
+
const originalOnUnload = config.onUnload
|
|
316
|
+
const originalOnLoad = config.onLoad
|
|
317
|
+
const originalOnShow = config.onShow
|
|
318
|
+
const runtime = this
|
|
319
|
+
|
|
320
|
+
config.onUnload = function (this: NativePage, ...args: unknown[]) {
|
|
321
|
+
const taro = runtime.requireTaro()
|
|
322
|
+
if (taro.pageReplacements.has(route)) {
|
|
323
|
+
taro.pageReplacements.set(route, {
|
|
324
|
+
$taroPath: this.$taroPath,
|
|
325
|
+
$taroParams: this.$taroParams,
|
|
326
|
+
// WeChat owns this serializable view-model. Keeping its reference is O(1),
|
|
327
|
+
// unlike cloning the complete recursive projection before every edit.
|
|
328
|
+
data: this.data
|
|
329
|
+
})
|
|
330
|
+
return
|
|
331
|
+
}
|
|
332
|
+
forward(originalOnUnload, this, args)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
config.onLoad = function (this: NativePage, ...args: unknown[]) {
|
|
336
|
+
const taro = runtime.requireTaro()
|
|
337
|
+
const snapshot = taro.pageReplacements.get(route)
|
|
338
|
+
if (snapshot) {
|
|
339
|
+
// Replace the transaction before native work so exceptions cannot retain the
|
|
340
|
+
// large data snapshot while the route waits for its synthetic onShow.
|
|
341
|
+
taro.pageReplacements.set(route, null)
|
|
342
|
+
|
|
343
|
+
// Snapshot paint is the first bridge operation and removes the empty-page gap.
|
|
344
|
+
this.setData(snapshot.data)
|
|
345
|
+
this.$taroPath = snapshot.$taroPath
|
|
346
|
+
this.$taroParams = snapshot.$taroParams
|
|
347
|
+
runtime.bindPage(this, snapshot.$taroPath)
|
|
348
|
+
return
|
|
349
|
+
}
|
|
350
|
+
forward(originalOnLoad, this, args)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
config.onShow = function (this: NativePage, ...args: unknown[]) {
|
|
354
|
+
if (runtime.requireTaro().pageReplacements.delete(route)) {
|
|
355
|
+
// Synthetic shows must not repeat requests or reset application state.
|
|
356
|
+
return
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
forward(originalOnShow, this, args)
|
|
360
|
+
}
|
|
203
361
|
}
|
|
204
362
|
|
|
205
|
-
/**
|
|
206
|
-
private
|
|
363
|
+
/** Returns the Taro connection or fails at the first incorrectly ordered use. */
|
|
364
|
+
private requireTaro(): TaroState {
|
|
365
|
+
if (!this.taro) throw new Error('[vpt] WX HMR used before the Taro runtime was connected')
|
|
366
|
+
return this.taro
|
|
367
|
+
}
|
|
207
368
|
|
|
208
|
-
/**
|
|
209
|
-
|
|
210
|
-
|
|
369
|
+
/** Returns a retained Taro root after normal Page mount has created it. */
|
|
370
|
+
private findRoot(path: string): TaroRoot | undefined {
|
|
371
|
+
const root = this.requireTaro().document.getElementById(path)
|
|
372
|
+
return isTaroRoot(root) ? root : undefined
|
|
211
373
|
}
|
|
212
374
|
|
|
213
|
-
/**
|
|
214
|
-
|
|
215
|
-
|
|
375
|
+
/** Rebinds a retained Taro tree to one replacement native Page without repainting. */
|
|
376
|
+
private bindPage(instance: NativePage, path: string): void {
|
|
377
|
+
const taro = this.requireTaro()
|
|
378
|
+
taro.injectPageInstance(instance, path)
|
|
379
|
+
taro.current.page = instance
|
|
380
|
+
|
|
381
|
+
const pageElement = this.findRoot(path)
|
|
382
|
+
if (!pageElement) {
|
|
383
|
+
throw new Error(`[vpt] retained Taro page not found: ${path}`)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
pageElement.ctx = instance
|
|
216
387
|
}
|
|
217
388
|
|
|
218
|
-
/** Applies one Page-delivered payload
|
|
219
|
-
applyPatches(payload: PatchPayload | undefined): void {
|
|
389
|
+
/** Applies one Page-delivered payload and arms its route for native replacement. */
|
|
390
|
+
applyPatches(payload: PatchPayload | undefined, route?: string): void {
|
|
220
391
|
// The initial physical dependency exports undefined until the host has a patch range.
|
|
221
392
|
if (!payload) return
|
|
222
393
|
|
|
223
|
-
const
|
|
224
|
-
if (!
|
|
394
|
+
const session = this.session
|
|
395
|
+
if (!session || payload.buildId !== session.buildId) {
|
|
225
396
|
console.warn('[vpt] patches for a stale build')
|
|
226
397
|
return
|
|
227
398
|
}
|
|
228
399
|
|
|
229
|
-
// A
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
400
|
+
// A replayed payload still causes DevTools to replace this physical Page. Arm the
|
|
401
|
+
// route independently of whether this App heap already applied its patch sequence.
|
|
402
|
+
if (route) {
|
|
403
|
+
this.requireTaro().pageReplacements.set(route, null)
|
|
404
|
+
}
|
|
233
405
|
|
|
234
406
|
// Apply synchronously: the page's imports below the require resolve against the
|
|
235
407
|
// freshly registered modules, so the re-executed Page evaluates with the new code.
|
|
236
|
-
if (this.applyPatchBatch(payload.patches)) {
|
|
408
|
+
if (this.applyPatchBatch(session, payload.patches)) {
|
|
237
409
|
// The host may publish later generations while this synchronous apply runs. Reporting only afterward makes this
|
|
238
410
|
// the application frontier: publisher history is never pruned merely because its JavaScript file was observed.
|
|
239
|
-
void this.sendReport({ kind: 'applied', seq:
|
|
411
|
+
void this.sendReport({ kind: 'applied', seq: session.appliedSeq })
|
|
240
412
|
}
|
|
241
413
|
}
|
|
242
414
|
|
|
@@ -253,10 +425,10 @@ class WxDevRuntime extends DevRuntime {
|
|
|
253
425
|
* - a second Page evaluating [5, 6, 7] skips the complete replay and leaves the latest factories untouched;
|
|
254
426
|
* - [5, 7] fails at the expected sequence 6, keeps appliedSeq 4, and requests a full rebuild because factory 6 is unrecoverable.
|
|
255
427
|
*/
|
|
256
|
-
private applyPatchBatch(patches: readonly PatchProgram[]): boolean {
|
|
428
|
+
private applyPatchBatch(session: HmrSession, patches: readonly PatchProgram[]): boolean {
|
|
257
429
|
// Keep the initial watermark immutable throughout the fold. Comparing replays against a moving watermark would allow
|
|
258
430
|
// a duplicate new sequence in the same payload to masquerade as an already-applied patch.
|
|
259
|
-
const previousSeq =
|
|
431
|
+
const previousSeq = session.appliedSeq
|
|
260
432
|
|
|
261
433
|
// Mutable only during this synchronous pass. `nextSeq` validates continuity while `changedIds` unions every incremental
|
|
262
434
|
// patch's roots for the one final graph traversal; neither value escapes into persistent runtime state.
|
|
@@ -300,7 +472,7 @@ class WxDevRuntime extends DevRuntime {
|
|
|
300
472
|
|
|
301
473
|
// Commit application only after graph propagation and boundary callbacks succeed. A failure leaves the old watermark
|
|
302
474
|
// intact and requests a full rebuild below, so a partially applied batch is never acknowledged as healthy.
|
|
303
|
-
|
|
475
|
+
session.appliedSeq = nextSeq - 1
|
|
304
476
|
return true
|
|
305
477
|
} catch (error) {
|
|
306
478
|
console.warn('[vpt] patch batch failed; apply stopped', error)
|
|
@@ -314,17 +486,17 @@ class WxDevRuntime extends DevRuntime {
|
|
|
314
486
|
|
|
315
487
|
/** Sends one metadata-only report to the host; executable code never travels over HTTP. */
|
|
316
488
|
private sendReport(data: Record<string, unknown>): Promise<void> {
|
|
317
|
-
const
|
|
318
|
-
if (!
|
|
489
|
+
const session = this.session
|
|
490
|
+
if (!session) {
|
|
319
491
|
// A report without initialize is a programming error; fail loudly instead of
|
|
320
492
|
// silently dropping the sync traffic.
|
|
321
493
|
throw new Error('WX dev runtime is not initialized')
|
|
322
494
|
}
|
|
323
495
|
return new Promise((resolve, reject) => {
|
|
324
496
|
wx.request({
|
|
325
|
-
url:
|
|
497
|
+
url: session.endpoint,
|
|
326
498
|
method: 'POST',
|
|
327
|
-
data: { buildId:
|
|
499
|
+
data: { buildId: session.buildId, ...data },
|
|
328
500
|
header: { 'content-type': 'application/json' },
|
|
329
501
|
success(): void {
|
|
330
502
|
resolve()
|
|
@@ -339,31 +511,3 @@ class WxDevRuntime extends DevRuntime {
|
|
|
339
511
|
|
|
340
512
|
const runtime = new WxDevRuntime()
|
|
341
513
|
;(globalThis as { __rolldown_runtime__?: WxDevRuntime }).__rolldown_runtime__ = runtime
|
|
342
|
-
|
|
343
|
-
// Install the React DevTools hook before the Taro renderer injects itself: the runtime chunk
|
|
344
|
-
// is the first module of the App heap, and the renderer checks the hook when it evaluates at
|
|
345
|
-
// App mount. The refresh runtime's own injection (see react-refresh.ts) replays this hook's
|
|
346
|
-
// renderers, so the hook must store what inject receives — a real DevTools hook keeps the
|
|
347
|
-
// renderer in the renderers Map; without the stored renderer the replay captures nothing and
|
|
348
|
-
// Refresh has no renderer helpers to schedule re-renders on.
|
|
349
|
-
//
|
|
350
|
-
// The hook lives on `global`, and every free `__REACT_DEVTOOLS_GLOBAL_HOOK__` reference in
|
|
351
|
-
// react-family modules is rewritten to `global.__REACT_DEVTOOLS_GLOBAL_HOOK__`: the
|
|
352
|
-
// The WeChat runtime scope does not resolve free variables against `global` (verified: the free
|
|
353
|
-
// lookup is undefined while the member access exists), so the renderer would never inject
|
|
354
|
-
// otherwise. `??=` keeps a pre-installed hook (e.g. a real DevTools integration) intact; the
|
|
355
|
-
// runtime chunk's own lowering keeps the operator es2018-compatible.
|
|
356
|
-
const reactDevtoolsHook = {
|
|
357
|
-
renderers: new Map<number, unknown>(),
|
|
358
|
-
supportsFiber: true,
|
|
359
|
-
inject: (injected: unknown) => {
|
|
360
|
-
const id = reactDevtoolsHook.renderers.size
|
|
361
|
-
reactDevtoolsHook.renderers.set(id, injected)
|
|
362
|
-
return id
|
|
363
|
-
},
|
|
364
|
-
onScheduleFiberRoot: () => {},
|
|
365
|
-
onCommitFiberRoot: () => {},
|
|
366
|
-
onCommitFiberUnmount: () => {}
|
|
367
|
-
}
|
|
368
|
-
// node types declare `global` as typeof globalThis, so the WeChat global needs a cast.
|
|
369
|
-
;(global as WeChatGlobal).__REACT_DEVTOOLS_GLOBAL_HOOK__ ??= reactDevtoolsHook
|
|
@@ -3,16 +3,13 @@
|
|
|
3
3
|
* free lookup is undefined while `global.X` exists), so everything stored here must be
|
|
4
4
|
* accessed as an explicit member expression.
|
|
5
5
|
*/
|
|
6
|
-
type WeChatGlobal =
|
|
7
|
-
/** React DevTools hook installed by the dev runtime; the renderer reads it via `global.`. */
|
|
8
|
-
__REACT_DEVTOOLS_GLOBAL_HOOK__?: unknown
|
|
9
|
-
}
|
|
6
|
+
type WeChatGlobal = object
|
|
10
7
|
|
|
11
8
|
/** The App-global Rolldown dev runtime; present only in wx development builds. */
|
|
12
9
|
declare const __rolldown_runtime__:
|
|
13
10
|
| {
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
connectTaro(current: object, document: object, injectPageInstance: (...args: unknown[]) => unknown): void
|
|
12
|
+
injectPageHmr(config: object, route: string): void
|
|
16
13
|
}
|
|
17
14
|
| undefined
|
|
18
15
|
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { PluginOption } from 'vite';
|
|
2
|
-
import type { VitePluginTaroOptions } from '../../../../options.ts';
|
|
3
|
-
/**
|
|
4
|
-
* Adds the serve-only bundled-development plugin set for the wx target: the dev adapter
|
|
5
|
-
* (config, runtime lowering, dev host) plus the React Refresh adaptation transforms.
|
|
6
|
-
*/
|
|
7
|
-
export declare function createWxDevelopmentPlugin(options: VitePluginTaroOptions): PluginOption[];
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { transformWithOxc } from 'vite';
|
|
2
|
-
import { esTarget } from '../../../utils/constant.js';
|
|
3
|
-
import { rolldownRuntimeId } from '../module.js';
|
|
4
|
-
import { memoize } from '../../../utils/memoize.js';
|
|
5
|
-
import { createWxDevHost } from './dev-host.js';
|
|
6
|
-
import { createWxReactRefreshTransforms } from './react-refresh.js';
|
|
7
|
-
/**
|
|
8
|
-
* Adds the serve-only bundled-development plugin set for the wx target: the dev adapter
|
|
9
|
-
* (config, runtime lowering, dev host) plus the React Refresh adaptation transforms.
|
|
10
|
-
*/
|
|
11
|
-
export function createWxDevelopmentPlugin(options) {
|
|
12
|
-
let host = null;
|
|
13
|
-
return [
|
|
14
|
-
{
|
|
15
|
-
name: 'vpt:wx-dev',
|
|
16
|
-
apply: 'serve',
|
|
17
|
-
config() {
|
|
18
|
-
return {
|
|
19
|
-
build: {
|
|
20
|
-
// Disable maps in resolved environment config as well as final output so Oxc and Babel skip producing
|
|
21
|
-
// intermediate maps that Rolldown would discard.
|
|
22
|
-
sourcemap: false
|
|
23
|
-
},
|
|
24
|
-
experimental: {
|
|
25
|
-
// Ask Vite to resolve its bundled-development graph and expose the private adapter instance. The wx
|
|
26
|
-
// configureServer hook replaces only its startup method with the directly writing DevEngine.
|
|
27
|
-
bundledDev: true
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
},
|
|
31
|
-
transform: {
|
|
32
|
-
order: 'post',
|
|
33
|
-
// The dev-mode transform assembles the runtime chunk (Rolldown's base runtime
|
|
34
|
-
// plus our injected implement) as this module's transform output, which
|
|
35
|
-
// bypasses the build's es2018 lowering. Real-device engines and WeChat's
|
|
36
|
-
// upload parser predate class fields and nullish operators, so the assembled
|
|
37
|
-
// runtime is lowered here — the only module that needs it. The exact id
|
|
38
|
-
// filter needs no code scan; the id must stay in sync with rolldownRuntimeId
|
|
39
|
-
// in module.ts (kept as a regex for the Rolldown-side filter).
|
|
40
|
-
filter: { id: /^\0rolldown\/runtime\.js(?:\?|$)/ },
|
|
41
|
-
handler(code, id) {
|
|
42
|
-
// The `setPublicClassFields` assumption emits plain `this.x = ...`
|
|
43
|
-
// assignments instead of external helpers, whose references the later
|
|
44
|
-
// minifier would mangle.
|
|
45
|
-
return fixRolldownRuntime(code);
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
configureServer: {
|
|
49
|
-
// Install after Vite and user plugins have finished configuring the environment, but before server.listen()
|
|
50
|
-
// asks bundledDev to create its hard-coded skip-write DevEngine.
|
|
51
|
-
order: 'post',
|
|
52
|
-
async handler(server) {
|
|
53
|
-
host = await createWxDevHost({ server, options });
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
closeBundle() {
|
|
57
|
-
return host?.close();
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
...createWxReactRefreshTransforms()
|
|
61
|
-
];
|
|
62
|
-
}
|
|
63
|
-
// The assembled runtime chunk is byte-identical on every build (the base runtime and the
|
|
64
|
-
// bundled implement are immutable for the server's lifetime), so the lowering runs once
|
|
65
|
-
// and every build reuses it.
|
|
66
|
-
const fixRolldownRuntime = memoize((code) => {
|
|
67
|
-
return transformWithOxc(code, rolldownRuntimeId, {
|
|
68
|
-
lang: 'js',
|
|
69
|
-
target: esTarget,
|
|
70
|
-
sourcemap: false,
|
|
71
|
-
assumptions: { setPublicClassFields: true }
|
|
72
|
-
});
|
|
73
|
-
});
|