vite-plugin-taro 0.6.20-beta.2 → 0.6.20

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.
Files changed (28) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +119 -120
  3. package/dist/node/plugins/wx/dev/dev-host.d.ts +15 -6
  4. package/dist/node/plugins/wx/dev/hmr-files.d.ts +10 -22
  5. package/dist/node/plugins/wx/dev/hmr-mode.d.ts +31 -0
  6. package/dist/node/plugins/wx/dev/hmr-protocol.d.ts +16 -0
  7. package/dist/node/plugins/wx/dev/modes/devtools/devtools-hmr-mode.d.ts +31 -0
  8. package/dist/node/plugins/wx/dev/modes/interpreter/interpreter-hmr-mode.d.ts +3 -0
  9. package/dist/node/plugins/wx/dev/patch-journal.d.ts +45 -0
  10. package/dist/node/plugins/wx/dev/plugins.d.ts +6 -10
  11. package/dist/node/plugins/wx/dev/wx-dev-options.d.ts +3 -1
  12. package/dist/options.d.ts +11 -0
  13. package/dist/runtime/wx/capsule/taro-runtime.js +5 -0
  14. package/dist/runtime/wx/dev/modes/devtools/devtools-runtime.js +30 -0
  15. package/dist/runtime/wx/dev/modes/devtools/page-hmr.d.ts +20 -0
  16. package/dist/runtime/wx/dev/modes/devtools/page-hmr.js +133 -0
  17. package/dist/runtime/wx/dev/modes/interpreter/interpreter-protocol.d.ts +12 -0
  18. package/dist/runtime/wx/dev/modes/interpreter/interpreter-protocol.js +1 -0
  19. package/dist/runtime/wx/dev/modes/interpreter/interpreter-runtime.d.ts +1 -0
  20. package/dist/runtime/wx/dev/modes/interpreter/interpreter-runtime.js +29 -0
  21. package/dist/runtime/wx/dev/wx-hmr-protocol.d.ts +22 -0
  22. package/dist/runtime/wx/dev/wx-hmr-protocol.js +4 -0
  23. package/dist/runtime/wx/dev/wx-hmr-runtime.d.ts +107 -0
  24. package/dist/runtime/wx/dev/{dev-runtime.js → wx-hmr-runtime.js} +83 -130
  25. package/package.json +5 -4
  26. package/dist/node/plugins/wx/dev/patch-publisher.d.ts +0 -42
  27. package/dist/node/plugins/wx/dev/runtime-reports.d.ts +0 -27
  28. /package/dist/runtime/wx/dev/{dev-runtime.d.ts → modes/devtools/devtools-runtime.d.ts} +0 -0
@@ -1,20 +1,29 @@
1
1
  import type { ViteDevServer } from 'vite';
2
2
  import type { VptOptions } from '../../../../options.ts';
3
+ import { type RuntimeControlMessage, type RuntimeReport } from '../../../../runtime/wx/dev/wx-hmr-protocol.ts';
3
4
  import type { WxStylePlugin } from '../styles/plugins.ts';
5
+ import type { WxHmrMode } from './hmr-mode.ts';
6
+ declare module 'vite' {
7
+ interface CustomEventMap {
8
+ 'vpt:wx-hmr:control': RuntimeControlMessage;
9
+ 'vpt:wx-hmr:report': RuntimeReport;
10
+ }
11
+ }
4
12
  export type WxDevHost = Readonly<{
5
13
  close: () => Promise<void>;
6
14
  }>;
7
15
  /**
8
- * Creates the wx dev host: the adapter that owns the physical Rolldown DevEngine (created
9
- * with dev(...)) and the patch publisher, and replaces Vite's bundledDev.listen so the
10
- * engine writes directly to the Mini Program output directory instead of serving browser
11
- * HMR over HTTP.
16
+ * Creates the WX dev host: the adapter that owns the physical Rolldown DevEngine, shared patch journal, and selected mode effects.
17
+ * It replaces Vite's browser-oriented `bundledDev.listen()` because a Mini Program cannot execute Vite's browser module graph;
18
+ * its engine must write a complete native project while the selected mode supplies an environment-valid patch mechanism.
12
19
  *
13
20
  * The shared style plugin carries the resolver's immutable App/Page cascade policy while Rolldown remains authoritative for
14
- * every live import edge.
21
+ * every live import edge. Keeping those responsibilities in the same serialized host transaction prevents JavaScript factories,
22
+ * physical WXSS, and Rolldown's published frontier from describing different source generations.
15
23
  */
16
- export declare function createWxDevHost({ server, options, styles }: {
24
+ export declare function createWxDevHost({ server, options, styles, hmrMode }: {
17
25
  server: ViteDevServer;
18
26
  options: VptOptions;
19
27
  styles: WxStylePlugin;
28
+ hmrMode: WxHmrMode;
20
29
  }): Promise<WxDevHost>;
@@ -1,23 +1,12 @@
1
- import type { BindingClientHmrUpdate } from 'rolldown/experimental';
1
+ import type { HmrInfo } from './hmr-protocol.ts';
2
2
  export declare const developmentAppWxssFileName = "app.wxss";
3
3
  export declare const globalWxssFileName = "assets/global.wxss";
4
4
  export declare const hmrInfoFileName = "hmr/info.js";
5
- export declare const hmrPatchesFileName = "hmr/patches.js";
6
- /** Report endpoint path served by the wx dev control edge. */
7
- export declare const hmrControlPath = "/__vpt_hmr__";
8
- /** Immutable App metadata for the current complete physical build. */
9
- export type HmrInfo = Readonly<{
10
- buildId: string;
11
- endpoint: string;
12
- }>;
13
- /** The Patch variant of Rolldown's per-client HMR update, admitted into the patch history. */
14
- export type PatchUpdate = Extract<BindingClientHmrUpdate['update'], {
15
- type: 'Patch';
16
- }>;
17
- /** Renders the synchronous CommonJS metadata module loaded by the runtime chunk. */
5
+ /**
6
+ * Renders immutable CommonJS metadata because App startup must initialize the runtime synchronously before any entry capsule.
7
+ * Freezing also prevents application code from accidentally changing the build identity or socket endpoint for the App heap.
8
+ */
18
9
  export declare function renderHmrInfo(info: HmrInfo): string;
19
- /** Provides a valid dependency before the host has a patch range to publish. */
20
- export declare function renderInitialHmrPatches(): string;
21
10
  /**
22
11
  * Renders the native App style entry for one complete development build.
23
12
  *
@@ -27,11 +16,10 @@ export declare function renderInitialHmrPatches(): string;
27
16
  */
28
17
  export declare function renderDevelopmentAppWxss(buildId: string): string;
29
18
  /**
30
- * Renders the cumulative patch suffix as inert CommonJS data.
19
+ * Atomically publishes one physical development file so DevTools can observe only complete generations.
31
20
  *
32
- * DevTools re-executes the Page because this dependency changed. The Page entry passes the exported payload to the persistent
33
- * App runtime synchronously before importing its capsule, keeping delivery explicit and leaving this file free of side effects.
21
+ * Writing directly to the watched JavaScript path exposes truncate and partial-write states as separate filesystem events. A
22
+ * sibling temporary file plus same-filesystem rename makes the destination replacement atomic while remaining reusable for
23
+ * metadata and styles; the `.txt` suffix keeps DevTools from compiling the temporary source as another Mini Program module.
34
24
  */
35
- export declare function renderHmrPatches(buildId: string, patches: readonly PatchUpdate[]): string;
36
- /** Atomically publishes one physical HMR module so DevTools can observe only complete JavaScript generations. */
37
- export declare function writeHmrFile(outDir: string, fileName: string, source: string): Promise<void>;
25
+ export declare function writeDevelopmentFile(outDir: string, fileName: string, source: string): Promise<void>;
@@ -0,0 +1,31 @@
1
+ import type { Plugin } from 'vite';
2
+ import type { VptHmrOptions } from '../../../../options.ts';
3
+ import type { PatchPublication } from './hmr-protocol.ts';
4
+ /** One mode-selected effect executed by the development host. */
5
+ export type WxHmrAction = Readonly<{
6
+ kind: 'event';
7
+ event: string;
8
+ data: unknown;
9
+ }> | Readonly<{
10
+ kind: 'write';
11
+ fileName: string;
12
+ source: string;
13
+ }>;
14
+ /**
15
+ * The behavior that genuinely differs between WX HMR implementations.
16
+ *
17
+ * The descriptor is selected once during plugin composition and returns declarative effects. The shared host alone performs
18
+ * physical writes and event transport, so neither mode receives a Vite server or owns asynchronous infrastructure.
19
+ */
20
+ export type WxHmrMode = Readonly<{
21
+ runtimeFile: string;
22
+ plugins: readonly Plugin[];
23
+ reset: () => WxHmrAction | undefined;
24
+ publish: (publication: PatchPublication) => WxHmrAction;
25
+ createEntryBanner: (pageFiles: ReadonlySet<string>) => (chunk: Readonly<{
26
+ name: string;
27
+ fileName: string;
28
+ }>) => string;
29
+ }>;
30
+ /** Resolves exactly one implementation before the WX development host is created. */
31
+ export declare function createWxHmrMode(options: VptHmrOptions | undefined): WxHmrMode;
@@ -0,0 +1,16 @@
1
+ import type { BindingClientHmrUpdate } from 'rolldown/experimental';
2
+ /** Shared Vite WebSocket path for runtime reports and mode-specific HMR events. */
3
+ export declare const hmrEndpointPath = "/__vpt_hmr__";
4
+ export type { HmrInfo, RuntimeReport } from '../../../../runtime/wx/dev/wx-hmr-protocol.ts';
5
+ /** The Patch variant of Rolldown's per-client HMR update admitted into the patch journal. */
6
+ export type PatchUpdate = Extract<BindingClientHmrUpdate['update'], {
7
+ type: 'Patch';
8
+ }>;
9
+ /**
10
+ * One cumulative patch suffix coupled to its build identity. Delivery receives this structured value instead of reading journal
11
+ * state, keeping physical rendering stateless and making stale-build rejection part of the payload the runtime actually sees.
12
+ */
13
+ export type PatchPublication = Readonly<{
14
+ buildId: string;
15
+ patches: readonly PatchUpdate[];
16
+ }>;
@@ -0,0 +1,31 @@
1
+ import type { WxHmrMode } from '../../hmr-mode.ts';
2
+ import type { PatchUpdate } from '../../hmr-protocol.ts';
3
+ export declare const devtoolsPatchesFileName = "hmr/patches.js";
4
+ /**
5
+ * Collects the behavior that depends on WeChat DevTools project-file compilation.
6
+ *
7
+ * Runtime entry, Page transform, entry banners, and patch delivery stay in one descriptor because they implement one protocol:
8
+ * DevTools observes a changed Page dependency, re-executes the Page shell, and that shell synchronously gives the cumulative
9
+ * native factory payload to the persistent App runtime. Mixing any one of these pieces with another mode would break that chain.
10
+ */
11
+ export declare function createDevtoolsHmrMode(): WxHmrMode;
12
+ /** Provides a valid, inert CommonJS dependency before the host has a patch range to publish. */
13
+ export declare function renderInitialDevtoolsPatches(): string;
14
+ /**
15
+ * Renders the cumulative patch suffix as inert CommonJS data.
16
+ *
17
+ * DevTools re-executes a live Page because this physical dependency changed. The Page banner passes the exported payload to the
18
+ * persistent App runtime before importing its capsule. Keeping the module inert—rather than applying factories at top level—
19
+ * makes that ordering explicit, lets the runtime reject stale build IDs, and makes replay by several live Pages safe.
20
+ */
21
+ export declare function renderDevtoolsPatches(buildId: string, patches: readonly PatchUpdate[]): string;
22
+ /**
23
+ * Injects Page handoff at the exact native registration edge rather than wrapping arbitrary user `Page` calls.
24
+ *
25
+ * The native shell has one stable `Page(pageConfig)` contract. Failing when that contract changes is intentional: silently
26
+ * skipping this transform would let DevTools unload the live Taro Page and destroy React state while appearing to support HMR.
27
+ */
28
+ export declare function injectPageShellHmr(code: string): {
29
+ code: string;
30
+ map: null;
31
+ };
@@ -0,0 +1,3 @@
1
+ import type { WxHmrMode } from '../../hmr-mode.ts';
2
+ /** Creates the pure interpreter adapter whose events are dispatched by the shared development host. */
3
+ export declare function createInterpreterHmrMode(): WxHmrMode;
@@ -0,0 +1,45 @@
1
+ import type { PatchPublication, PatchUpdate } from './hmr-protocol.ts';
2
+ type PublishPatches = (publication: PatchPublication) => Promise<void>;
3
+ /**
4
+ * Owns the cumulative sequence range between the host's published frontier and the runtime's applied frontier.
5
+ *
6
+ * For example, if the runtime has applied sequence 4:
7
+ *
8
+ * 1. produce([5, 6]) publishes [5, 6];
9
+ * 2. produce([7]) before an application report publishes [5, 6, 7], so a mode may miss the first delivery safely;
10
+ * 3. acknowledge(6) retains [7];
11
+ * 4. produce([8]) then publishes [7, 8], exactly the suffix the runtime still needs.
12
+ *
13
+ * The host may advance Rolldown's published frontier immediately after each durable publication, but the journal prunes only
14
+ * through the runtime's last successful ACK. Published and applied sequences are deliberately distinct: collapsing them would
15
+ * make one missed delivery unrecoverable. The journal owns that gap and build identity while the mode owns representation.
16
+ */
17
+ export declare class PatchJournal {
18
+ private readonly publishPatches;
19
+ private buildId;
20
+ private readonly pendingPatches;
21
+ constructor(publishPatches: PublishPatches);
22
+ /** True when the buildId belongs to the current complete build. */
23
+ isCurrentBuild(buildId: string): boolean;
24
+ /** Begins a fresh build and returns both sides of the client-session rotation. */
25
+ startBuild(): Readonly<{
26
+ buildId: string;
27
+ previousBuildId: string | undefined;
28
+ }>;
29
+ /**
30
+ * Appends a Rolldown batch and publishes the complete range not yet applied by the runtime.
31
+ *
32
+ * Host action serialization guarantees this readonly view cannot be mutated by an ACK, build rotation, or later production
33
+ * until publishPatches settles. Resolution proves only that the mode made the suffix durable, not that the App applied it; a
34
+ * failed publication therefore leaves the appended range intact, and a successful one remains until an explicit ACK.
35
+ */
36
+ produce(patches: readonly PatchUpdate[]): Promise<void>;
37
+ /**
38
+ * Removes only the prefix covered by the runtime's successful application frontier.
39
+ *
40
+ * Publication must never call this method: retaining the gap between published and applied frontiers is what lets a mode
41
+ * safely coalesce, replay, or miss an intermediate delivery without losing a factory required by the live App generation.
42
+ */
43
+ acknowledge(seq: number): void;
44
+ }
45
+ export {};
@@ -6,8 +6,8 @@ export declare function isWxClientEnvironment(environment: Readonly<{
6
6
  name: string;
7
7
  }>): boolean;
8
8
  /**
9
- * Adds the serve-only bundled-development plugin set for the wx target: the dev adapter,
10
- * Page HMR activation, and React Refresh adaptation transforms.
9
+ * Adds the serve-only bundled-development plugin set for the wx target: the shared dev adapter, selected HMR mode, and React
10
+ * Refresh adaptation transforms.
11
11
  *
12
12
  * The shared style pipeline already owns the resolver's ordered App/Page cascade policy, so the host does not reconstruct it
13
13
  * from unrelated Rolldown shell and transport entries.
@@ -24,13 +24,9 @@ export declare function createWxDevelopmentPlugin(options: VptOptions, styles: W
24
24
  * 3. let the host replace the wrapper again with the new build ID, causing a second App reload.
25
25
  *
26
26
  * Deleting only the in-memory bundle entry avoids both premature writes. Development sets `emptyOutDir: false`, so the prior
27
- * physical `app.wxss` remains available while the complete output is written. The host replaces it exactly once after
28
- * `hmr/patches.js` and `hmr/info.js` are durable. Incremental HMR never enters this complete-output hook and continues changing
29
- * only `assets/global.wxss`, which preserves the App heap. The serve-only plugin leaves production output unchanged.
27
+ * physical `app.wxss` remains available while the complete output is written. The host replaces it exactly once after the
28
+ * selected mode has reset and the matching `hmr/info.js` identity is durable. Incremental HMR never enters this complete-
29
+ * output hook and changes only `assets/global.wxss`; rewriting the App root would reload the heap while a JavaScript patch is
30
+ * awaiting acknowledgement. The serve-only plugin leaves production output unchanged.
30
31
  */
31
32
  export declare function removeDevelopmentAppWxss(bundle: Record<string, unknown>): void;
32
- /** Injects the retained snapshot at the exact native Page registration edge. */
33
- export declare function injectPageShellHmr(code: string): {
34
- code: string;
35
- map: null;
36
- };
@@ -2,6 +2,7 @@ import type { InputOptions, OutputOptions } from 'rolldown';
2
2
  import { type DevEngine } from 'rolldown/experimental';
3
3
  import type { ViteDevServer } from 'vite';
4
4
  import type { VptOptions } from '../../../../options.ts';
5
+ import type { WxHmrMode } from './hmr-mode.ts';
5
6
  type BundledDevRolldownOptions = InputOptions & {
6
7
  experimental?: {
7
8
  [key: string]: unknown;
@@ -19,10 +20,11 @@ export type BundledDev = {
19
20
  * Installs physical WX output and runtime conventions over Vite's browser-oriented bundled-development options.
20
21
  * Build completion deliberately remains outside this options adapter: DevEngine onOutput is the single lifecycle authority.
21
22
  */
22
- export declare function installWxDevOptions({ bundledDev, server, options }: {
23
+ export declare function installWxDevOptions({ bundledDev, server, options, hmrMode }: {
23
24
  bundledDev: BundledDev;
24
25
  server: ViteDevServer;
25
26
  options: VptOptions;
27
+ hmrMode: WxHmrMode;
26
28
  }): void;
27
29
  /** Returns the configured output after rejecting states unsupported by the physical WX engine. */
28
30
  export declare function requireSingleOutput(rolldownOptions: BundledDevRolldownOptions): OutputOptions;
package/dist/options.d.ts CHANGED
@@ -2,6 +2,11 @@
2
2
  export type VptJsonObject = Record<string, unknown>;
3
3
  /** Build target handled by this plugin. */
4
4
  export type VptTarget = 'wx' | 'h5';
5
+ /** Selects one implemented WX development HMR delivery and execution mechanism. */
6
+ export type VptHmrOptions = Readonly<{
7
+ /** `devtools` executes native patch files; `interpreter` fetches source and evaluates it without native Page reload. */
8
+ mode: 'devtools' | 'interpreter';
9
+ }>;
5
10
  /** Configures one Taro page. */
6
11
  export type VptPageOption = {
7
12
  /**
@@ -69,4 +74,10 @@ export interface VptOptions {
69
74
  * The file is emitted only when this value is provided for a `wx` build. It is ignored for `h5`.
70
75
  */
71
76
  sitemapJson?: VptJsonObject;
77
+ /**
78
+ * Selects the WX development HMR mode. Omission uses `devtools`.
79
+ *
80
+ * This option affects only `vite serve` for the `wx` target and never changes H5 or production output.
81
+ */
82
+ hmr?: VptHmrOptions;
72
83
  }
@@ -4,3 +4,8 @@ import '@tarojs/plugin-platform-weapp/dist/runtime.js';
4
4
  export { createReactApp } from '@tarojs/plugin-framework-react/dist/runtime';
5
5
  export { default as ReactDOM } from '@tarojs/react';
6
6
  export { createPageConfig, createRecursiveComponentConfig } from '@tarojs/runtime';
7
+ import { customWrapperCache } from '@tarojs/runtime';
8
+ // DevTools HMR runs in the bootstrap chunk, so publish the application graph's cache once on their shared App global.
9
+ if (process.env.NODE_ENV === 'development') {
10
+ Reflect.set(global, Symbol.for('customWrapperCache'), customWrapperCache);
11
+ }
@@ -0,0 +1,30 @@
1
+ /*
2
+ * DevTools adapter injected into the App-global Rolldown runtime chunk. Every Page evaluates the inert `hmr/patches.js` payload
3
+ * before its route capsule, while this singleton survives Page JavaScript hot reload in the App heap. That lifetime difference is
4
+ * what allows module and React state to survive repeated native Page registration.
5
+ */
6
+ import { WxHmrRuntime } from '../../wx-hmr-runtime.js';
7
+ import { injectPageHmr as injectDevtoolsPageHmr } from './page-hmr.js';
8
+ /** Adapts physical DevTools patch modules and Page replacement onto the shared WX HMR runtime. */
9
+ class DevtoolsHmrRuntime extends WxHmrRuntime {
10
+ /** Applies one Page-delivered payload before the native shell imports its route capsule. */
11
+ applyPatches(payload) {
12
+ // The initial physical dependency exports undefined until the host has a patch range. Once present, apply synchronously:
13
+ // imports below the Page banner must resolve against the new registry during this same native Page evaluation.
14
+ if (!payload)
15
+ return;
16
+ this.applyPatchPayload(payload, installDevtoolsPatch);
17
+ }
18
+ /** Preserves the mounted Page connection while DevTools re-registers its static config. */
19
+ injectPageHmr(config) {
20
+ return injectDevtoolsPageHmr(config);
21
+ }
22
+ }
23
+ /** Executes only the native registration program; graph application remains owned by `WxHmrRuntime`. */
24
+ function installDevtoolsPatch(patch) {
25
+ patch.factory();
26
+ }
27
+ // Generated modules already address this App-global name. Install exactly one adapter with the runtime chunk rather than one per
28
+ // Page, because Page hot reload re-evaluates shells while the App heap and its module registry remain alive.
29
+ const runtime = new DevtoolsHmrRuntime();
30
+ globalThis.__rolldown_runtime__ = runtime;
@@ -0,0 +1,20 @@
1
+ type NativePage = {
2
+ data: Record<string, unknown>;
3
+ };
4
+ declare const pageHmrStateKey: unique symbol;
5
+ type PageHmrState = {
6
+ /** True only across the unload/load/show sequence triggered by one native re-registration. */
7
+ isReregistering: boolean;
8
+ /** The Page bound to `this` by ordinary onLoad, retained until ordinary onUnload. */
9
+ mountedPage: NativePage | undefined;
10
+ };
11
+ type HmrPageConfig = {
12
+ data: Record<string, unknown>;
13
+ onUnload?: unknown;
14
+ onLoad?: unknown;
15
+ onShow?: unknown;
16
+ [pageHmrStateKey]?: PageHmrState;
17
+ };
18
+ /** Tracks the mounted native Page and prepares its static config for DevTools re-registration. */
19
+ export declare function injectPageHmr(config: HmrPageConfig): HmrPageConfig;
20
+ export {};
@@ -0,0 +1,133 @@
1
+ // A symbol cannot collide with WeChat or Taro's string-named Page options, and it lets a later injection recover the exact
2
+ // config-local state without a route map or runtime-global WeakMap.
3
+ const pageHmrStateKey = Symbol('vpt.pageHmrState');
4
+ /** Calls a native lifecycle with the same Page bound to `this` and the same arguments. */
5
+ function forward(handler, page, args) {
6
+ if (typeof handler === 'function')
7
+ handler.apply(page, args);
8
+ }
9
+ /** Tracks the mounted native Page and prepares its static config for DevTools re-registration. */
10
+ export function injectPageHmr(config) {
11
+ const existingState = config[pageHmrStateKey];
12
+ if (existingState) {
13
+ const mountedPage = existingState.mountedPage;
14
+ /*
15
+ * The static config can outlive a native Page instance. Before its first ordinary onLoad, or after a real onUnload,
16
+ * there is no mounted Page or current view-model to carry into another registration. Leave the lifecycle gate unarmed
17
+ * and preserve the config's existing initial data so a future real onLoad still enters Taro normally.
18
+ */
19
+ if (!mountedPage) {
20
+ return config;
21
+ }
22
+ /*
23
+ * Arm the lifecycle wrappers on this exact static config before it is passed back to `Page(config)`. DevTools then
24
+ * triggers an unload/load/show sequence for that native re-registration: unload and load observe `true` and return before
25
+ * entering Taro, preserving the mounted React tree and its original Page connection; show consumes the one-shot gate by
26
+ * restoring `false`. Ordinary navigation never enters this branch, and every Page config owns an independent state, so
27
+ * no route map, global phase, or Page identity comparison participates in the decision.
28
+ */
29
+ existingState.isReregistering = true;
30
+ /*
31
+ * `Page(config)` reads `config.data` as the initial native view-model for this registration. The Page owns current app
32
+ * and ordinary Page fields, while each mounted CustomWrapper owns the current snapshot below its native boundary. Join
33
+ * those already-serialized snapshots before registration so WeChat never publishes the stale initial placeholder tree.
34
+ * This lifecycle handoff performs no setData call and runs before React Refresh can publish another logical tree.
35
+ */
36
+ config.data = mountedPage.data;
37
+ applyCustomWrapperSnapshots(config.data);
38
+ return config;
39
+ }
40
+ const originalOnUnload = config.onUnload;
41
+ const originalOnLoad = config.onLoad;
42
+ const originalOnShow = config.onShow;
43
+ /*
44
+ * This mutable config-local state spans ordinary Page mount/unmount and one DevTools re-registration lifecycle. Lifecycle
45
+ * wrappers close over this exact object, while a later `injectPageHmr(config)` retrieves it through the symbol and avoids
46
+ * wrapping twice. Config-local ownership avoids route maps, runtime-global phase flags, and state shared by independent Page
47
+ * registrations; the state becomes unreachable together with its static config.
48
+ */
49
+ const state = {
50
+ isReregistering: false,
51
+ mountedPage: undefined
52
+ };
53
+ config[pageHmrStateKey] = state;
54
+ config.onUnload = function (...args) {
55
+ if (state.isReregistering) {
56
+ return;
57
+ }
58
+ forward(originalOnUnload, this, args);
59
+ state.mountedPage = undefined;
60
+ };
61
+ config.onLoad = function (...args) {
62
+ if (state.isReregistering) {
63
+ return;
64
+ }
65
+ forward(originalOnLoad, this, args);
66
+ state.mountedPage = this;
67
+ };
68
+ config.onShow = function (...args) {
69
+ if (state.isReregistering) {
70
+ // DevTools' synthetic unload/load/show cycle ends here. Consume the gate exactly once so the next real navigation
71
+ // forwards ordinary Taro lifecycles instead of being mistaken for the same registration.
72
+ state.isReregistering = false;
73
+ return;
74
+ }
75
+ forward(originalOnShow, this, args);
76
+ };
77
+ return config;
78
+ }
79
+ const customWrapperCacheKey = Symbol.for('customWrapperCache');
80
+ /**
81
+ * Replaces stale Page.data placeholders with each mounted CustomWrapper's current native snapshot in at most one O(n)
82
+ * traversal. This mutates only the registration data before WeChat reads it; it performs no setData call and does not
83
+ * inspect React.
84
+ */
85
+ function applyCustomWrapperSnapshots(data) {
86
+ const customWrapperCache = Reflect.get(global, customWrapperCacheKey);
87
+ if (!(customWrapperCache instanceof Map)) {
88
+ throw new Error('WX CustomWrapper cache is not installed in the App global.');
89
+ }
90
+ if (customWrapperCache.size === 0) {
91
+ return;
92
+ }
93
+ // This local mutable set lets the lazy traversal stop after every mounted wrapper snapshot has been consumed. Cache
94
+ // entries owned by another mounted Page remain, naturally falling back to a complete traversal of this Page.
95
+ const remainingSids = new Set(customWrapperCache.keys());
96
+ for (const sid of materializeSnapshots(data, customWrapperCache)) {
97
+ remainingSids.delete(sid);
98
+ if (remainingSids.size === 0) {
99
+ return;
100
+ }
101
+ }
102
+ }
103
+ /** Lazily traverses the Page snapshot and yields each mounted CustomWrapper sid after replacing its stale node. */
104
+ function* materializeSnapshots(value, customWrapperCache) {
105
+ if (Array.isArray(value)) {
106
+ for (let index = 0; index < value.length; index++) {
107
+ yield* replaceCustomWrapperSnapshot(value, index, customWrapperCache);
108
+ yield* materializeSnapshots(value[index], customWrapperCache);
109
+ }
110
+ }
111
+ else if (isRecord(value)) {
112
+ for (const key in value) {
113
+ yield* replaceCustomWrapperSnapshot(value, key, customWrapperCache);
114
+ yield* materializeSnapshots(value[key], customWrapperCache);
115
+ }
116
+ }
117
+ }
118
+ /** Assigns a snapshot before yielding its sid, so closing the generator cannot discard the final replacement. */
119
+ function* replaceCustomWrapperSnapshot(container, key, customWrapperCache) {
120
+ const value = container[key];
121
+ if (!isRecord(value) || value.nn !== 'custom-wrapper' || typeof value.sid !== 'string') {
122
+ return;
123
+ }
124
+ const snapshot = customWrapperCache.get(value.sid)?.data.i;
125
+ if (!snapshot) {
126
+ return;
127
+ }
128
+ container[key] = snapshot;
129
+ yield value.sid;
130
+ }
131
+ function isRecord(value) {
132
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
133
+ }
@@ -0,0 +1,12 @@
1
+ import type { RuntimePatch } from '../../wx-hmr-runtime.ts';
2
+ export declare const interpreterServerEvent = "vpt:wx-interpreter:source";
3
+ /** One Rolldown patch whose existing code field is interpreted instead of wrapped in a native factory. */
4
+ export type InterpreterPatch = RuntimePatch & {
5
+ readonly code: string;
6
+ };
7
+ /** Messages published by the host over Vite's existing WebSocket. */
8
+ export type InterpreterServerMessage = Readonly<{
9
+ kind: 'patches';
10
+ buildId: string;
11
+ patches: readonly InterpreterPatch[];
12
+ }>;
@@ -0,0 +1 @@
1
+ export const interpreterServerEvent = 'vpt:wx-interpreter:source';
@@ -0,0 +1,29 @@
1
+ /*
2
+ * Interpreter adapter injected into the App-global Rolldown runtime chunk. Vite's shared WebSocket publishes cumulative
3
+ * registration source, Sval evaluates it, and the shared runtime owns the retained socket, graph application, and reports.
4
+ */
5
+ import Sval from 'sval';
6
+ import { WxHmrRuntime } from '../../wx-hmr-runtime.js';
7
+ import { interpreterServerEvent } from './interpreter-protocol.js';
8
+ /** Interprets cumulative patch source received by the shared App-level HMR socket. */
9
+ class InterpreterHmrRuntime extends WxHmrRuntime {
10
+ /** One installer and Sval scope retain interpreted factory closures for this App heap. */
11
+ installPatch;
12
+ constructor() {
13
+ super();
14
+ const interpreter = new Sval();
15
+ interpreter.import('__rolldown_runtime__', this);
16
+ this.installPatch = (patch) => interpreter.run(patch.code);
17
+ }
18
+ onSocketEvent(info, event, data) {
19
+ if (event !== interpreterServerEvent)
20
+ return;
21
+ const message = data;
22
+ if (message.buildId !== info.buildId || !this.applyPatchPayload(message, this.installPatch)) {
23
+ this.stopSocket('patch application stopped');
24
+ }
25
+ }
26
+ }
27
+ // The App-global singleton owns interpreted factory closures while its base owns socket, module cache, and Refresh boundaries.
28
+ const runtime = new InterpreterHmrRuntime();
29
+ globalThis.__rolldown_runtime__ = runtime;
@@ -0,0 +1,22 @@
1
+ /** Host-to-App terminal control over the App heap's authenticated Vite socket. */
2
+ export declare const runtimeControlEvent = "vpt:wx-hmr:control";
3
+ /** App-to-host application frontier and rebuild reports over the same socket. */
4
+ export declare const runtimeReportEvent = "vpt:wx-hmr:report";
5
+ /** Build identity and authenticated socket endpoint fixed for one App heap. */
6
+ export type HmrInfo = Readonly<{
7
+ buildId: string;
8
+ endpoint: string;
9
+ }>;
10
+ export type RuntimeReport = Readonly<{
11
+ kind: 'applied';
12
+ buildId: string;
13
+ seq: number;
14
+ }> | Readonly<{
15
+ kind: 'rebuild';
16
+ buildId: string;
17
+ reason: string;
18
+ }>;
19
+ export type RuntimeControlMessage = Readonly<{
20
+ kind: 'close';
21
+ reason: string;
22
+ }>;
@@ -0,0 +1,4 @@
1
+ /** Host-to-App terminal control over the App heap's authenticated Vite socket. */
2
+ export const runtimeControlEvent = 'vpt:wx-hmr:control';
3
+ /** App-to-host application frontier and rebuild reports over the same socket. */
4
+ export const runtimeReportEvent = 'vpt:wx-hmr:report';