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.
@@ -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 { Current, createPageConfig, document, injectPageInstance } from './taro-runtime.ts'
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
- /** Registered self-accept callbacks; a bare accept is represented by a no-op callback. */
54
- private readonly acceptCallbacks: AcceptCallback[] = []
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
- /** Set when a callback rejects a Refresh boundary; the current update then rebuilds. */
57
- private invalidationReason: string | undefined
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
- accept(callback?: AcceptCallback): void {
60
- this.acceptCallbacks.push(callback ?? (() => {}))
131
+ constructor(moduleId: string, acceptingContexts: Map<string, WxHotContext>) {
132
+ this.moduleId = moduleId
133
+ this.acceptingContexts = acceptingContexts
61
134
  }
62
135
 
63
- hasAccepts(): boolean {
64
- return this.acceptCallbacks.length > 0
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 and returns any requested invalidation. */
68
- runAccept(moduleExports: unknown): string | undefined {
69
- for (const callback of this.acceptCallbacks) {
70
- callback(moduleExports)
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): void {
76
- this.invalidationReason = reason ?? 'the accepting module invalidated the update'
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
- private hmrInfo: HmrInfo | undefined
87
-
88
- /** Highest Rolldown sequence successfully applied. */
89
- private appliedSeq = 0
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
- /** Active hot contexts per module id; the propagation invokes their callbacks. */
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. Mirrors Rolldown's web runtime: each execution immediately replaces the
103
- * module's hot context; the apply plan has already captured the previous callbacks.
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
- const hotContext = new WxHotContext()
107
- this.moduleHotContexts.set(moduleId, hotContext)
108
- return hotContext
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.get(moduleId)?.hasAccepts()) {
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
- const invalidationReason = hotContext?.runAccept(this.initModule(moduleId))
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.hmrInfo) {
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
- this.hmrInfo = info
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
- /** True between a patch delivery and the next page show: a hot reload is in progress. */
206
- private hotReloading = false
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
- /** The capsule wrapper asks this during the synthetic lifecycle of a hot reload. */
209
- isHotReloading(): boolean {
210
- return this.hotReloading
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
- /** The wrapped onShow ends the hot reload after the replacement cycle. */
214
- clearHotReloading(): void {
215
- this.hotReloading = false
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 before that Page imports its capsule. */
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 info = this.hmrInfo
224
- if (!info || payload.buildId !== info.buildId) {
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 delivered patch means DevTools is about to replay the page lifecycle on the
230
- // re-executing Pages; the capsule wrapper suppresses the synthetic unmount/mount so
231
- // the React tree survives and Refresh swaps the code in place.
232
- this.hotReloading = true
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: this.appliedSeq })
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 = this.appliedSeq
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
- this.appliedSeq = nextSeq - 1
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 info = this.hmrInfo
318
- if (!info) {
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: info.endpoint,
497
+ url: session.endpoint,
326
498
  method: 'POST',
327
- data: { buildId: info.buildId, ...data },
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
- isHotReloading(): boolean
15
- clearHotReloading(): void
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
- });