vite-plugin-taro 0.6.2 → 0.6.5

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 (46) hide show
  1. package/README.en.md +8 -3
  2. package/README.md +8 -3
  3. package/dist/node/plugins/wx/dev/dev-host.d.ts +5 -4
  4. package/dist/node/plugins/wx/dev/dev-host.js +19 -43
  5. package/dist/node/plugins/wx/dev/plugins.d.ts +6 -15
  6. package/dist/node/plugins/wx/dev/plugins.js +16 -70
  7. package/dist/node/plugins/wx/dev/react-refresh.d.ts +9 -3
  8. package/dist/node/plugins/wx/dev/react-refresh.js +35 -7
  9. package/dist/node/plugins/wx/dev/wx-dev-options.d.ts +2 -3
  10. package/dist/node/plugins/wx/dev/wx-dev-options.js +6 -7
  11. package/dist/node/plugins/wx/plugins.js +5 -11
  12. package/dist/node/plugins/wx/resolve/resolver.js +4 -4
  13. package/dist/node/plugins/wx/resolve/{specialize-bootstrap.d.ts → specialize-app-capsule.d.ts} +2 -2
  14. package/dist/node/plugins/wx/resolve/{specialize-bootstrap.js → specialize-app-capsule.js} +2 -2
  15. package/dist/node/plugins/wx/styles/plugins.d.ts +141 -3
  16. package/dist/node/plugins/wx/styles/plugins.js +401 -89
  17. package/dist/node/utils/vite.d.ts +4 -14
  18. package/dist/node/utils/vite.js +7 -40
  19. package/dist/runtime/wx/amphibious/bootstrap.d.ts +0 -2
  20. package/dist/runtime/wx/amphibious/bootstrap.js +0 -2
  21. package/dist/runtime/wx/capsule/app.d.ts +2 -2
  22. package/dist/runtime/wx/capsule/app.js +2 -2
  23. package/dist/runtime/wx/dev/dev-runtime.js +55 -79
  24. package/package.json +4 -3
  25. package/src/node/plugins/wx/dev/dev-host.ts +22 -44
  26. package/src/node/plugins/wx/dev/plugins.ts +17 -73
  27. package/src/node/plugins/wx/dev/react-refresh.ts +36 -7
  28. package/src/node/plugins/wx/dev/wx-dev-options.ts +7 -10
  29. package/src/node/plugins/wx/plugins.ts +5 -11
  30. package/src/node/plugins/wx/resolve/resolver.ts +4 -4
  31. package/src/node/plugins/wx/resolve/{specialize-bootstrap.ts → specialize-app-capsule.ts} +2 -2
  32. package/src/node/plugins/wx/styles/plugins.ts +518 -93
  33. package/src/node/utils/vite.ts +13 -58
  34. package/src/runtime/wx/amphibious/bootstrap.ts +0 -5
  35. package/src/runtime/wx/capsule/app.ts +5 -2
  36. package/src/runtime/wx/dev/dev-runtime.ts +71 -120
  37. package/src/runtime/wx/wechat.d.ts +1 -2
  38. package/dist/node/plugins/wx/dev/create-style-capture.d.ts +0 -54
  39. package/dist/node/plugins/wx/dev/create-style-capture.js +0 -173
  40. package/dist/node/plugins/wx/styles/transform-wx-style.d.ts +0 -8
  41. package/dist/node/plugins/wx/styles/transform-wx-style.js +0 -9
  42. package/dist/node/plugins/wx/styles/utils.d.ts +0 -39
  43. package/dist/node/plugins/wx/styles/utils.js +0 -95
  44. package/src/node/plugins/wx/dev/create-style-capture.ts +0 -248
  45. package/src/node/plugins/wx/styles/transform-wx-style.ts +0 -11
  46. package/src/node/plugins/wx/styles/utils.ts +0 -119
package/README.en.md CHANGED
@@ -1,4 +1,4 @@
1
- # vite-plugin-taro
1
+ # VPT
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/vite-plugin-taro.svg)](https://www.npmjs.com/package/vite-plugin-taro)
4
4
  ![Vite](https://img.shields.io/npm/dependency-version/vite-plugin-taro/peer/vite?label=Vite)
@@ -20,7 +20,7 @@ Continue with the [Quick Start guide](https://vpt.js.org/guides/quick-start/).
20
20
 
21
21
  ### React Hot Reload in WeChat DevTools
22
22
 
23
- <video src="https://github.com/user-attachments/assets/cb93daf5-1827-496a-bb74-8397975accf6" controls autoplay muted loop playsinline width="100%"></video>
23
+ <video src="https://github.com/user-attachments/assets/c8289e1e-d8ad-429f-a0d9-a40656b4962a" controls autoplay muted loop playsinline width="100%"></video>
24
24
 
25
25
  - **Vite 8 + React 19** Build one codebase for WeChat Mini Program and Web with the Vite ecosystem.
26
26
  - **Hot reload** Preserve App data, the active page, React component state, and input state while editing.
@@ -31,12 +31,17 @@ Continue with the [Quick Start guide](https://vpt.js.org/guides/quick-start/).
31
31
 
32
32
  ## Documentation
33
33
 
34
+ - [Quick Start](https://vpt.js.org/guides/quick-start/)
35
+ - [Styles](https://vpt.js.org/guides/styles/)
34
36
  - [Automatic subpackages](https://vpt.js.org/guides/automatic-subpackages/)
35
37
  - [Native WeChat components](https://vpt.js.org/guides/native-components/)
36
38
  - [Hot module replacement](https://vpt.js.org/guides/hot-module-replacement/)
39
+ - [Configuration options](https://vpt.js.org/guides/configuration/)
40
+ - [Conditional compilation](https://vpt.js.org/guides/conditional-directives/)
37
41
  - [Skyline mode](https://vpt.js.org/guides/skyline-mode/)
38
42
  - [Migrate from Taro CLI](https://vpt.js.org/guides/migrate-from-taro/)
39
- - [Configuration reference](https://vpt.js.org/guides/configuration/)
43
+ - [Module system](https://vpt.js.org/references/module-system/)
44
+ - [Hot module replacement internals](https://vpt.js.org/references/hmr-implementation/)
40
45
  - [Repository management](https://vpt.js.org/references/repository-management/)
41
46
 
42
47
  ## License
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # vite-plugin-taro
1
+ # VPT
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/vite-plugin-taro.svg)](https://www.npmjs.com/package/vite-plugin-taro)
4
4
  ![Vite](https://img.shields.io/npm/dependency-version/vite-plugin-taro/peer/vite?label=Vite)
@@ -21,7 +21,7 @@ npm create vite-taro@latest my-app
21
21
 
22
22
  ### 已支持微信开发者工具的 React 热更新
23
23
 
24
- <video src="https://github.com/user-attachments/assets/cb93daf5-1827-496a-bb74-8397975accf6" controls autoplay muted loop playsinline width="100%"></video>
24
+ <video src="https://github.com/user-attachments/assets/c8289e1e-d8ad-429f-a0d9-a40656b4962a" controls autoplay muted loop playsinline width="100%"></video>
25
25
 
26
26
  - **Vite 8 + React 19** 基于 Vite 生态,一份代码覆盖微信小程序与 Web。
27
27
  - **热更新** 编辑代码时保留 App 数据、当前页面、React 组件状态与输入状态。
@@ -32,12 +32,17 @@ npm create vite-taro@latest my-app
32
32
 
33
33
  ## 文档
34
34
 
35
+ - [快速开始](https://vpt.js.org/guides/quick-start/)
36
+ - [样式](https://vpt.js.org/guides/styles/)
35
37
  - [全自动分包](https://vpt.js.org/guides/automatic-subpackages/)
36
38
  - [微信原生组件](https://vpt.js.org/guides/native-components/)
37
39
  - [开发者工具热更新](https://vpt.js.org/guides/hot-module-replacement/)
40
+ - [配置选项](https://vpt.js.org/guides/configuration/)
41
+ - [条件编译](https://vpt.js.org/guides/conditional-directives/)
38
42
  - [Skyline 模式](https://vpt.js.org/guides/skyline-mode/)
39
43
  - [从 Taro CLI 迁移](https://vpt.js.org/guides/migrate-from-taro/)
40
- - [配置参考](https://vpt.js.org/guides/configuration/)
44
+ - [模块系统](https://vpt.js.org/references/module-system/)
45
+ - [热更新原理](https://vpt.js.org/references/hmr-implementation/)
41
46
  - [仓库维护](https://vpt.js.org/references/repository-management/)
42
47
 
43
48
  ## 许可证
@@ -1,5 +1,6 @@
1
1
  import type { ViteDevServer } from 'vite';
2
2
  import type { VptOptions } from '../../../../options.ts';
3
+ import type { WxStylePlugin } from '../styles/plugins.ts';
3
4
  export type WxDevHost = Readonly<{
4
5
  close: () => Promise<void>;
5
6
  }>;
@@ -9,11 +10,11 @@ export type WxDevHost = Readonly<{
9
10
  * engine writes directly to the Mini Program output directory instead of serving browser
10
11
  * HMR over HTTP.
11
12
  *
12
- * `applicationEntryIds` is the resolver's immutable cascade policy, not a second graph: it selects the App capsule followed
13
- * by configured Page capsules from Rolldown's larger entry set. Rolldown remains the authority for every live import edge.
13
+ * The shared style plugin carries the resolver's immutable App/Page cascade policy while Rolldown remains authoritative for
14
+ * every live import edge.
14
15
  */
15
- export declare function createWxDevHost({ server, options, applicationEntryIds }: {
16
+ export declare function createWxDevHost({ server, options, styles }: {
16
17
  server: ViteDevServer;
17
18
  options: VptOptions;
18
- applicationEntryIds: readonly string[];
19
+ styles: WxStylePlugin;
19
20
  }): Promise<WxDevHost>;
@@ -1,11 +1,9 @@
1
- import { readFile } from 'node:fs/promises';
2
1
  import path from 'node:path';
3
2
  import colors from 'picocolors';
4
3
  import { dev } from 'rolldown/experimental';
5
4
  import { asyncScheduler } from 'rxjs';
6
5
  import { createHmrResultsStream } from './create-hmr-results-stream.js';
7
- import { createStyleCapture } from './create-style-capture.js';
8
- import { developmentAppWxssFileName, hmrControlPath, hmrInfoFileName, hmrPatchesFileName, renderDevelopmentAppWxss, renderHmrInfo, renderInitialHmrPatches, writeHmrFile } from './hmr-files.js';
6
+ import { developmentAppWxssFileName, globalWxssFileName, hmrControlPath, hmrInfoFileName, hmrPatchesFileName, renderDevelopmentAppWxss, renderHmrInfo, renderInitialHmrPatches, writeHmrFile } from './hmr-files.js';
9
7
  import { createHostActions } from './host-actions.js';
10
8
  import { PatchPublisher } from './patch-publisher.js';
11
9
  import { createRuntimeReportsStream } from './runtime-reports.js';
@@ -18,27 +16,16 @@ const hmrSettleMilliseconds = 16;
18
16
  * engine writes directly to the Mini Program output directory instead of serving browser
19
17
  * HMR over HTTP.
20
18
  *
21
- * `applicationEntryIds` is the resolver's immutable cascade policy, not a second graph: it selects the App capsule followed
22
- * by configured Page capsules from Rolldown's larger entry set. Rolldown remains the authority for every live import edge.
19
+ * The shared style plugin carries the resolver's immutable App/Page cascade policy while Rolldown remains authoritative for
20
+ * every live import edge.
23
21
  */
24
- export async function createWxDevHost({ server, options, applicationEntryIds }) {
22
+ export async function createWxDevHost({ server, options, styles }) {
25
23
  const bundledDev = getBundledDev(server);
26
24
  // All callbacks admit typed actions through this edge; concatMap is the sole owner of effect ordering and mutable host state.
27
25
  const hostActions = createHostActions(applyHostAction, (action, error) => logWxError(server.config.logger, `wx HMR ${action.kind} failed`, error));
28
- const styleCapture = createStyleCapture({
29
- applicationEntryIds: applicationEntryIds,
30
- outDir: server.config.build.outDir,
31
- // Capture hooks run only after engine.run, when the host action subscription and all reducer dependencies are ready.
32
- emit: (action) => hostActions.next(action),
33
- transformTailwindRoot: async (rootId, requestId) => {
34
- // The capture contains generated CSS, while the sidecar must re-run from raw Tailwind directives. Vite exposes no
35
- // raw source in its live module graph, so this read is the authoritative source generation rather than a read-back.
36
- return server.environments.client.pluginContainer.transform(await readFile(rootId, 'utf8'), requestId);
37
- }
38
- });
39
26
  const publisher = new PatchPublisher((content) => writeHmrFile(server.config.build.outDir, hmrPatchesFileName, content));
40
27
  // Option installation now configures only Rolldown. Build lifecycle results enter through the engine's output action below.
41
- installWxDevOptions({ bundledDev, server, options, hostPlugins: [styleCapture.plugin] });
28
+ installWxDevOptions({ bundledDev, server, options });
42
29
  const engine = await createEngine();
43
30
  const hmrResults = createHmrResultsStream(hmrSettleMilliseconds, asyncScheduler, (result) => {
44
31
  // One reduced window becomes one existing host transaction: style preparation, one cumulative patch write, then
@@ -98,11 +85,15 @@ export async function createWxDevHost({ server, options, applicationEntryIds })
98
85
  hmrResults.complete();
99
86
  runtimeReports.complete();
100
87
  await hostActions.waitForIdle();
101
- // Keep the action edge open until the final generation has admitted all capture and output callbacks.
88
+ // Keep the action edge open until the final generation has admitted every output callback.
102
89
  await engine.ensureCurrentBuildFinish();
103
90
  await hostActions.complete();
104
91
  }
105
92
  };
93
+ /** Atomically materializes the style plugin's prepared global artifact. */
94
+ async function writeGlobalStyle(wxss) {
95
+ await writeHmrFile(server.config.build.outDir, globalWxssFileName, wxss);
96
+ }
106
97
  /** Rotates the build identity and materializes the App metadata for it. */
107
98
  async function rotateBuildSession() {
108
99
  const port = boundPort(server);
@@ -142,14 +133,6 @@ export async function createWxDevHost({ server, options, applicationEntryIds })
142
133
  /** Reduces one merged source action through the existing authoritative host state. */
143
134
  function applyHostAction(action) {
144
135
  switch (action.kind) {
145
- case 'capture-graph':
146
- // The buildStart reader is a live capability; replacing it only here makes the reducer own build rebinding.
147
- styleCapture.captureGraph(action.getModuleInfo);
148
- return;
149
- case 'capture-style':
150
- // A failed upstream transform emits no action, intentionally retaining the last valid processed CSS generation.
151
- styleCapture.captureStyle(action.id, action.style);
152
- return;
153
136
  case 'publish':
154
137
  return publishUpdates(action.result);
155
138
  case 'error':
@@ -163,20 +146,14 @@ export async function createWxDevHost({ server, options, applicationEntryIds })
163
146
  logWxError(server.config.logger, 'wx dev build failed', action.result);
164
147
  return;
165
148
  }
166
- return reconcileCompleteOutput(action.result.output);
149
+ return publishCompleteStyles();
167
150
  case 'listening':
168
151
  return rotateBuildSession();
169
152
  }
170
153
  }
171
- /**
172
- * Reconciles graph-complete styles before rotating the App-visible build identity.
173
- *
174
- * DevEngine has already written its compiler asset when onOutput fires, but bundled development may have omitted CSS Modules
175
- * from that asset. Awaiting reconciliation inside the serialized reducer guarantees the subsequent app.wxss rotation—the
176
- * event that refreshes DevTools—can only expose a generation whose global WXSS already represents the complete App/Page graph.
177
- */
178
- async function reconcileCompleteOutput(output) {
179
- await styleCapture.reconcileComplete(output);
154
+ /** Publishes graph-complete styles before rotating the App-visible build identity. */
155
+ async function publishCompleteStyles() {
156
+ await styles.finalizeUpdate([], writeGlobalStyle);
180
157
  await rotateBuildSession();
181
158
  }
182
159
  /** Applies one runtime receipt to the active physical patch history. */
@@ -252,16 +229,15 @@ export async function createWxDevHost({ server, options, applicationEntryIds })
252
229
  if (patches.length === 0) {
253
230
  return;
254
231
  }
255
- // `onHmrUpdates` is the transaction boundary after every affected transform has updated graph and candidate state.
256
- // Every non-CSS edit may alter imports or Tailwind classes; rendering broadly and comparing finalized bytes avoids
257
- // source scanning while preventing unrelated JavaScript edits from notifying DevTools through an identical rename.
258
- await styleCapture.publishChanged(patches.flatMap((patch) => patch.changedIds));
232
+ // `onHmrUpdates` runs after every affected transform has updated captured CSS and the live import graph. The style
233
+ // boundary finalizes every factory before atomically publishing their matching WXSS.
234
+ const finalizedPatches = await styles.finalizeUpdate(patches, writeGlobalStyle);
259
235
  // Publish global.wxss before the matching JavaScript patch so DevTools observes a coherent HMR transaction.
260
236
  // The physical file must exist before Rolldown advances: once committed, later patches may be generated relative to
261
237
  // this batch even if DevTools has not observed its file event yet. PatchPublisher keeps the unapplied range cumulative,
262
238
  // so any later file generation still carries every factory needed to bridge the runtime's older application frontier.
263
- await publisher.produce(patches);
264
- await commitPublishedBatch(patches);
239
+ await publisher.produce(finalizedPatches);
240
+ await commitPublishedBatch(finalizedPatches);
265
241
  }
266
242
  /**
267
243
  * Advances Rolldown's published frontier in the same sequence order materialized in the cumulative physical file.
@@ -1,5 +1,6 @@
1
1
  import { type PluginOption } from 'vite';
2
2
  import type { VptOptions } from '../../../../options.ts';
3
+ import type { WxStylePlugin } from '../styles/plugins.ts';
3
4
  /** Selects the sole Vite environment that owns the physical Mini Program development project. */
4
5
  export declare function isWxClientEnvironment(environment: Readonly<{
5
6
  name: string;
@@ -8,10 +9,10 @@ export declare function isWxClientEnvironment(environment: Readonly<{
8
9
  * Adds the serve-only bundled-development plugin set for the wx target: the dev adapter,
9
10
  * Page HMR activation, and React Refresh adaptation transforms.
10
11
  *
11
- * The ordered application entries cross this configuration boundary unchanged so the host can compose global CSS without
12
- * reconstructing the resolver's App/Page ownership policy from unrelated Rolldown shell and transport entries.
12
+ * The shared style pipeline already owns the resolver's ordered App/Page cascade policy, so the host does not reconstruct it
13
+ * from unrelated Rolldown shell and transport entries.
13
14
  */
14
- export declare function createWxDevelopmentPlugin(options: VptOptions, applicationEntryIds: readonly string[]): PluginOption[];
15
+ export declare function createWxDevelopmentPlugin(options: VptOptions, styles: WxStylePlugin): PluginOption[];
15
16
  /**
16
17
  * Transfers development ownership of `app.wxss` from complete output to the dev host.
17
18
  *
@@ -28,18 +29,8 @@ export declare function createWxDevelopmentPlugin(options: VptOptions, applicati
28
29
  * only `assets/global.wxss`, which preserves the App heap. The serve-only plugin leaves production output unchanged.
29
30
  */
30
31
  export declare function removeDevelopmentAppWxss(bundle: Record<string, unknown>): void;
31
- /** Ensures the Refresh hook exists before React's renderer evaluates and injects itself. */
32
- export declare function injectReactRefreshBootstrap(code: string): {
33
- code: string;
34
- map: null;
35
- };
36
- /** Connects the shared WX dev runtime to the application graph's Taro runtime instance. */
37
- export declare function injectTaroConnection(code: string): {
38
- code: string;
39
- map: null;
40
- };
41
- /** Activates development-only lifecycle handling for one plugin-owned Page capsule. */
42
- export declare function injectPageHmr(code: string, route: string): {
32
+ /** Injects the retained snapshot at the exact native Page registration edge. */
33
+ export declare function injectPageShellHmr(code: string): {
43
34
  code: string;
44
35
  map: null;
45
36
  };
@@ -2,11 +2,10 @@ import { normalizePath, transformWithOxc } from 'vite';
2
2
  import { esTarget } from '../../../utils/constant.js';
3
3
  import { memoize } from '../../../utils/memoize.js';
4
4
  import { normalizeModuleId } from '../../../utils/modules.js';
5
- import { appCapsulePath, pageCapsulePath, rolldownRuntimeId, taroRuntimePath } from '../module/module.js';
5
+ import { pageShellPath, rolldownRuntimeId } from '../module/module.js';
6
6
  import { createWxDevHost } from './dev-host.js';
7
7
  import { developmentAppWxssFileName } from './hmr-files.js';
8
8
  import { createWxReactRefreshTransforms } from './react-refresh.js';
9
- const taroRuntimeId = '@tarojs/runtime';
10
9
  /** Selects the sole Vite environment that owns the physical Mini Program development project. */
11
10
  export function isWxClientEnvironment(environment) {
12
11
  return environment.name === 'client';
@@ -15,10 +14,10 @@ export function isWxClientEnvironment(environment) {
15
14
  * Adds the serve-only bundled-development plugin set for the wx target: the dev adapter,
16
15
  * Page HMR activation, and React Refresh adaptation transforms.
17
16
  *
18
- * The ordered application entries cross this configuration boundary unchanged so the host can compose global CSS without
19
- * reconstructing the resolver's App/Page ownership policy from unrelated Rolldown shell and transport entries.
17
+ * The shared style pipeline already owns the resolver's ordered App/Page cascade policy, so the host does not reconstruct it
18
+ * from unrelated Rolldown shell and transport entries.
20
19
  */
21
- export function createWxDevelopmentPlugin(options, applicationEntryIds) {
20
+ export function createWxDevelopmentPlugin(options, styles) {
22
21
  /*
23
22
  * Vite creates this plugin descriptor before a server or DevEngine exists, then invokes configureServer and closeBundle on
24
23
  * different lifecycle stacks. This mutable handle transfers the one client-owned host between those hooks: configureServer
@@ -28,9 +27,7 @@ export function createWxDevelopmentPlugin(options, applicationEntryIds) {
28
27
  */
29
28
  let host = null;
30
29
  // Portable hook filters stay broad; these exact identities exclude similarly named user modules.
31
- const normalizedAppCapsulePath = normalizePath(appCapsulePath);
32
- const normalizedPageCapsulePath = normalizePath(pageCapsulePath);
33
- const normalizedTaroRuntimePath = normalizePath(taroRuntimePath);
30
+ const normalizedPageShellPath = normalizePath(pageShellPath);
34
31
  return [
35
32
  {
36
33
  name: 'vpt:wx-dev',
@@ -68,7 +65,7 @@ export function createWxDevelopmentPlugin(options, applicationEntryIds) {
68
65
  host = await createWxDevHost({
69
66
  server: server,
70
67
  options: options,
71
- applicationEntryIds: applicationEntryIds
68
+ styles: styles
72
69
  });
73
70
  }
74
71
  },
@@ -107,41 +104,15 @@ export function createWxDevelopmentPlugin(options, applicationEntryIds) {
107
104
  }
108
105
  },
109
106
  {
110
- name: 'vpt:wx-react-refresh-bootstrap',
107
+ name: 'vpt:wx-page-shell-hmr',
111
108
  apply: 'serve',
112
109
  transform: {
113
110
  order: 'post',
114
- filter: { id: /\/runtime\/wx\/capsule\/app\.js(?:\?|$)/ },
111
+ filter: { id: /\/runtime\/wx\/native\/page\.js(?:\?|$)/ },
115
112
  handler(code, id) {
116
- if (normalizeModuleId(id) !== normalizedAppCapsulePath)
113
+ if (normalizeModuleId(id) !== normalizedPageShellPath)
117
114
  return;
118
- return injectReactRefreshBootstrap(code);
119
- }
120
- }
121
- },
122
- {
123
- name: 'vpt:wx-page-hmr',
124
- apply: 'serve',
125
- transform: {
126
- order: 'post',
127
- filter: { id: /\/runtime\/wx\/capsule\/page\.js(?:\?|$)/ },
128
- handler(code, id) {
129
- if (normalizeModuleId(id) !== normalizedPageCapsulePath)
130
- return;
131
- return injectPageHmr(code, getPageRoute(id));
132
- }
133
- }
134
- },
135
- {
136
- name: 'vpt:wx-taro-hmr',
137
- apply: 'serve',
138
- transform: {
139
- order: 'post',
140
- filter: { id: /\/runtime\/wx\/capsule\/taro-runtime\.js(?:\?|$)/ },
141
- handler(code, id) {
142
- if (normalizeModuleId(id) !== normalizedTaroRuntimePath)
143
- return;
144
- return injectTaroConnection(code);
115
+ return injectPageShellHmr(code);
145
116
  }
146
117
  }
147
118
  },
@@ -166,42 +137,17 @@ export function createWxDevelopmentPlugin(options, applicationEntryIds) {
166
137
  export function removeDevelopmentAppWxss(bundle) {
167
138
  delete bundle[developmentAppWxssFileName];
168
139
  }
169
- /** Ensures the Refresh hook exists before React's renderer evaluates and injects itself. */
170
- export function injectReactRefreshBootstrap(code) {
171
- return {
172
- code: `import ${JSON.stringify('/@react-refresh')};\n${code}`,
173
- map: null
174
- };
175
- }
176
- /** Connects the shared WX dev runtime to the application graph's Taro runtime instance. */
177
- export function injectTaroConnection(code) {
178
- if (!/\bCurrent\b/.test(code) || !/\bdocument\b/.test(code) || !/\binjectPageInstance\b/.test(code)) {
179
- throw new Error('WX Taro runtime must expose Current, document, and injectPageInstance for HMR');
140
+ /** Injects the retained snapshot at the exact native Page registration edge. */
141
+ export function injectPageShellHmr(code) {
142
+ const registration = 'Page(pageConfig)';
143
+ if (!code.includes(registration)) {
144
+ throw new Error('WX native Page shell must register pageConfig');
180
145
  }
181
- const taroImport = `import { Current as __vptCurrent, document as __vptDocument, injectPageInstance as __vptInjectPageInstance } from ${JSON.stringify(taroRuntimeId)};`;
182
146
  return {
183
- code: `${code}\n${taroImport}\n__rolldown_runtime__.connectTaro(__vptCurrent, __vptDocument, __vptInjectPageInstance);`,
147
+ code: code.replace(registration, 'Page(__rolldown_runtime__.injectPageHmr(pageConfig))'),
184
148
  map: null
185
149
  };
186
150
  }
187
- /** Activates development-only lifecycle handling for one plugin-owned Page capsule. */
188
- export function injectPageHmr(code, route) {
189
- if (!/\bconst\s+config\s*=/.test(code) || !/\bexport\s+default\s+config\b/.test(code)) {
190
- throw new Error('WX Page capsule must declare and default-export config before HMR injection');
191
- }
192
- return {
193
- code: `${code}\n__rolldown_runtime__.injectPageHmr(config, ${JSON.stringify(route)});`,
194
- map: null
195
- };
196
- }
197
- /** Reads the stable route carried by every specialized Page capsule ID. */
198
- function getPageRoute(id) {
199
- const queryIndex = id.indexOf('?');
200
- const route = queryIndex < 0 ? null : new URLSearchParams(id.slice(queryIndex + 1)).get('route');
201
- if (!route)
202
- throw new Error(`WX Page capsule is missing its route: ${id}`);
203
- return route;
204
- }
205
151
  /*
206
152
  * memoize owns a mutable one-entry-by-input cache. The assembled runtime source is byte-identical across complete generations
207
153
  * because both the Rolldown base and injected implementation are immutable for the server lifetime. Reusing its lowered result
@@ -4,8 +4,9 @@ import type { Plugin } from 'vite';
4
4
  *
5
5
  * @vitejs/plugin-react's generated refresh code assumes the web HTML preamble and a browser
6
6
  * global scope; wx has neither. Each transform adapts one piece of that contract:
7
- * - the refresh runtime module (id-filtered): the vendored runtime reads and assigns
7
+ * - the refresh runtime module (id-filtered): the runtime reads and assigns
8
8
  * `window` protocol globals (rewritten to `global`) and must inject itself at evaluation;
9
+ * - React Reconciler: its renderer injection statically depends on the refresh runtime, fixing cold-start order;
9
10
  * - react-family modules (filtered on free references): the DevTools hook is read as a free
10
11
  * variable, which the WeChat runtime scope never resolves against `global` — every free
11
12
  * reference becomes an explicit member access;
@@ -13,10 +14,15 @@ import type { Plugin } from 'vite';
13
14
  * needed because the transform generates local wrappers over the imported refresh
14
15
  * runtime, so the guard that checks for the global is removed.
15
16
  *
16
- * Each transform's filter is its routing: the three domains are disjoint, so a module is
17
- * transformed by at most one of them, and modules outside all three never reach a handler.
17
+ * Filters route every adaptation directly. The Reconciler intentionally receives the renderer dependency first and the
18
+ * global-hook rewrite second; modules outside these explicit domains never reach a handler.
18
19
  */
19
20
  export declare function createWxReactRefreshTransforms(): Plugin[];
21
+ /** Makes renderer hook injection statically depend on the refresh runtime. */
22
+ export declare function injectReactRefreshRendererDependency(code: string): {
23
+ code: string;
24
+ map: null;
25
+ };
20
26
  export declare function transformRefreshRuntime({ code, id }: {
21
27
  code: string;
22
28
  id: string;
@@ -1,6 +1,8 @@
1
1
  import { isReferenceIdentifier } from 'oxc-walker';
2
2
  import { memoize } from '../../../utils/memoize.js';
3
+ import { normalizeModuleId } from '../../../utils/modules.js';
3
4
  import { transformWithOxcWalker } from '../../../utils/oxc-transform.js';
5
+ import { reactReconcilerRoot } from '../module/module.js';
4
6
  /** The React DevTools hook protocol name; free references must target `global` in wx. */
5
7
  const reactDevtoolsHookProtocol = '__REACT_DEVTOOLS_GLOBAL_HOOK__';
6
8
  /**
@@ -10,7 +12,7 @@ const reactDevtoolsHookProtocol = '__REACT_DEVTOOLS_GLOBAL_HOOK__';
10
12
  * - `__getReactRefreshIgnoredExports` is an optional extension point read while validating a refresh boundary.
11
13
  * Leaving that read on the nonexistent `window` crashes every update validation pass.
12
14
  *
13
- * Keeping this list explicit prevents the adapter from rewriting unrelated browser accesses if the vendored runtime
15
+ * Keeping this list explicit prevents the adapter from rewriting unrelated browser accesses if the refresh runtime
14
16
  * gains new code. Any future React Refresh protocol addition therefore requires a deliberate compatibility decision.
15
17
  */
16
18
  const refreshRuntimeWindowGlobals = ['__registerBeforePerformReactRefresh', '__getReactRefreshIgnoredExports'];
@@ -19,8 +21,9 @@ const refreshRuntimeWindowGlobals = ['__registerBeforePerformReactRefresh', '__g
19
21
  *
20
22
  * @vitejs/plugin-react's generated refresh code assumes the web HTML preamble and a browser
21
23
  * global scope; wx has neither. Each transform adapts one piece of that contract:
22
- * - the refresh runtime module (id-filtered): the vendored runtime reads and assigns
24
+ * - the refresh runtime module (id-filtered): the runtime reads and assigns
23
25
  * `window` protocol globals (rewritten to `global`) and must inject itself at evaluation;
26
+ * - React Reconciler: its renderer injection statically depends on the refresh runtime, fixing cold-start order;
24
27
  * - react-family modules (filtered on free references): the DevTools hook is read as a free
25
28
  * variable, which the WeChat runtime scope never resolves against `global` — every free
26
29
  * reference becomes an explicit member access;
@@ -28,8 +31,8 @@ const refreshRuntimeWindowGlobals = ['__registerBeforePerformReactRefresh', '__g
28
31
  * needed because the transform generates local wrappers over the imported refresh
29
32
  * runtime, so the guard that checks for the global is removed.
30
33
  *
31
- * Each transform's filter is its routing: the three domains are disjoint, so a module is
32
- * transformed by at most one of them, and modules outside all three never reach a handler.
34
+ * Filters route every adaptation directly. The Reconciler intentionally receives the renderer dependency first and the
35
+ * global-hook rewrite second; modules outside these explicit domains never reach a handler.
33
36
  */
34
37
  export function createWxReactRefreshTransforms() {
35
38
  return [
@@ -38,13 +41,27 @@ export function createWxReactRefreshTransforms() {
38
41
  apply: 'serve',
39
42
  transform: {
40
43
  order: 'post',
41
- // The vendored refresh runtime module; id-filtered, so no code scan.
44
+ // The refresh runtime module is id-filtered, so no code scan is needed.
42
45
  filter: { id: /^\/@react-refresh(?:\?|$)/ },
43
46
  handler(code, id) {
44
47
  return fixRefreshRuntime({ code, id });
45
48
  }
46
49
  }
47
50
  },
51
+ {
52
+ name: 'vpt:wx-react-refresh-renderer-dependency',
53
+ apply: 'serve',
54
+ transform: {
55
+ order: 'post',
56
+ filter: { id: /\/react-reconciler\/cjs\/react-reconciler\.development\.js(?:\?|$)/ },
57
+ handler(code, id) {
58
+ const rendererId = `${reactReconcilerRoot}/cjs/react-reconciler.development.js`;
59
+ if (normalizeModuleId(id) !== rendererId)
60
+ return;
61
+ return injectReactRefreshRendererDependency(code);
62
+ }
63
+ }
64
+ },
48
65
  {
49
66
  name: 'vpt:wx-react-devtools-hook',
50
67
  apply: 'serve',
@@ -86,7 +103,8 @@ export function createWxReactRefreshTransforms() {
86
103
  * The protocol name is unique, but only reference identifiers are rewritten. Declaration
87
104
  * keys and explicit members such as `global.__REACT_DEVTOOLS_GLOBAL_HOOK__` must remain
88
105
  * untouched; rewriting those would either produce invalid syntax or double-prefix the hook.
89
- * The eagerly evaluated refresh runtime creates the hook on `global` before the renderer loads.
106
+ * The Reconciler's injected static dependency orders hook injection before renderer initialization independently of Rolldown's
107
+ * final chunking decision.
90
108
  */
91
109
  function createReactDevtoolsHookVisitor(editor) {
92
110
  return function enter(node, parent) {
@@ -133,7 +151,7 @@ function createRefreshRuntimeVisitor(editor) {
133
151
  node.property.type === 'Identifier' &&
134
152
  refreshRuntimeWindowGlobals.some((globalName) => globalName === node.property.name)) {
135
153
  // `global` is the shared wx App heap used by the dev runtime and hook injection. Only
136
- // replacing the object range preserves the vendored runtime byte-for-byte otherwise
154
+ // replacing the object range preserves the upstream runtime byte-for-byte otherwise
137
155
  // and prevents unrelated `window` expressions from being silently adapted.
138
156
  editor.overwrite(node.object.start, node.object.end, 'global');
139
157
  }
@@ -169,6 +187,16 @@ function createRefreshPreambleGuardVisitor(editor) {
169
187
  this.skip();
170
188
  };
171
189
  }
190
+ /** Makes renderer hook injection statically depend on the refresh runtime. */
191
+ export function injectReactRefreshRendererDependency(code) {
192
+ if (!/\bhook\.inject\(internals\)/.test(code)) {
193
+ throw new Error('React Reconciler must inject its renderer into the DevTools hook');
194
+ }
195
+ return {
196
+ code: `import '/@react-refresh'\n${code}`,
197
+ map: null
198
+ };
199
+ }
172
200
  export function transformRefreshRuntime({ code, id }) {
173
201
  return transformWithOxcWalker({
174
202
  code,
@@ -1,4 +1,4 @@
1
- import type { InputOptions, OutputOptions, Plugin } from 'rolldown';
1
+ 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';
@@ -19,11 +19,10 @@ export type BundledDev = {
19
19
  * Installs physical WX output and runtime conventions over Vite's browser-oriented bundled-development options.
20
20
  * Build completion deliberately remains outside this options adapter: DevEngine onOutput is the single lifecycle authority.
21
21
  */
22
- export declare function installWxDevOptions({ bundledDev, server, options, hostPlugins }: {
22
+ export declare function installWxDevOptions({ bundledDev, server, options }: {
23
23
  bundledDev: BundledDev;
24
24
  server: ViteDevServer;
25
25
  options: VptOptions;
26
- hostPlugins: Plugin[];
27
26
  }): void;
28
27
  /** Returns the configured output after rejecting states unsupported by the physical WX engine. */
29
28
  export declare function requireSingleOutput(rolldownOptions: BundledDevRolldownOptions): OutputOptions;
@@ -8,7 +8,7 @@ import { appShellFileName } from '../module/module.js';
8
8
  * Installs physical WX output and runtime conventions over Vite's browser-oriented bundled-development options.
9
9
  * Build completion deliberately remains outside this options adapter: DevEngine onOutput is the single lifecycle authority.
10
10
  */
11
- export function installWxDevOptions({ bundledDev, server, options, hostPlugins }) {
11
+ export function installWxDevOptions({ bundledDev, server, options }) {
12
12
  /*
13
13
  * Vite owns this mutable adapter method and calls it later when constructing DevEngine. Replacing that one seam preserves
14
14
  * Vite's resolved graph while applying WX physical-output conventions to every options generation. Capturing and mutating one
@@ -78,10 +78,10 @@ export function installWxDevOptions({ bundledDev, server, options, hostPlugins }
78
78
  };
79
79
  /*
80
80
  * The plugin list is mutable configuration consumed once by this engine generation. Replace the top-level reference with
81
- * an ordered composite rather than pushing into Vite's potentially shared nested array: existing transforms run first,
82
- * host capture observes their final values, and the reporter observes final output without mutating either input list.
81
+ * an ordered composite rather than pushing into Vite's potentially shared nested array; the reporter observes final output
82
+ * without mutating Vite's retained input list.
83
83
  */
84
- rolldownOptions.plugins = [rolldownOptions.plugins, hostPlugins, createViteReporter(server)];
84
+ rolldownOptions.plugins = [rolldownOptions.plugins, createViteReporter(server)];
85
85
  disableViteOxcSourcemap(rolldownOptions.plugins);
86
86
  return rolldownOptions;
87
87
  };
@@ -105,7 +105,7 @@ function ensureSingleOutput(rolldownOptions) {
105
105
  /**
106
106
  * Prepends entry banners after Rolldown's analysis so their native requires remain physical dependencies rather than chunk
107
107
  * graph edges. The App initializes the runtime identity, while every Page explicitly applies the watched patch data before its
108
- * capsule import continues.
108
+ * native Page registration.
109
109
  */
110
110
  function createEntryBanner(pageFiles) {
111
111
  return (chunk) => {
@@ -114,8 +114,7 @@ function createEntryBanner(pageFiles) {
114
114
  }
115
115
  if (pageFiles.has(chunk.name)) {
116
116
  const patchesPath = path.posix.relative(path.posix.dirname(chunk.fileName), 'hmr/patches.js');
117
- const route = chunk.name.slice(0, -'.js'.length);
118
- return `__rolldown_runtime__.applyPatches(require('${patchesPath}'), ${JSON.stringify(route)});\n`;
117
+ return `__rolldown_runtime__.applyPatches(require('${patchesPath}'));\n`;
119
118
  }
120
119
  return '';
121
120
  };
@@ -10,19 +10,15 @@ import { renderCapsule } from './render/capsule.js';
10
10
  import { renderNative } from './render/native.js';
11
11
  import { materializeTransport } from './render/transport.js';
12
12
  import { createResolver } from './resolve/resolver.js';
13
- import { createWxStylePlugins } from './styles/plugins.js';
13
+ import { createWxStylePlugin } from './styles/plugins.js';
14
14
  /** Creates the complete plugin set for the wx target. */
15
15
  export function createWxTargetPlugins(options) {
16
16
  const resolver = createResolver(options);
17
17
  // Reuse the resolver instance's ordered application subset. Rolldown's complete input also contains bootstrap, transport,
18
18
  // shell, and component entries; entry membership alone cannot recover which roots define the App/Page CSS cascade.
19
19
  const placement = createWxPlacementPlugin();
20
- return [
21
- placement,
22
- createWxStylePlugins(),
23
- createWxPlugin(options, resolver, placement),
24
- createWxDevelopmentPlugin(options, resolver.applicationEntryIds)
25
- ];
20
+ const styles = createWxStylePlugin(resolver.applicationEntryIds);
21
+ return [placement, styles, createWxPlugin(options, resolver, placement), createWxDevelopmentPlugin(options, styles)];
26
22
  }
27
23
  /** Configures the complete wx target build pipeline. */
28
24
  function createWxPlugin(options, resolver, placement) {
@@ -106,10 +102,8 @@ function createWxPlugin(options, resolver, placement) {
106
102
  },
107
103
  generateBundle: {
108
104
  /*
109
- * This hook is registered after createWxStylePlugins() and shares hook-level `order: 'post'` with the adapted
110
- * upstream hooks and VPT style finalizer. Registration order therefore guarantees that the imported global
111
- * stylesheet is complete before native Page/component companions are emitted. Without this order, the finalizer
112
- * could consume incomplete Tailwind output or mistake native WXSS companions for additional compiler styles.
105
+ * Registration after the style pipeline makes the compiler stylesheet final before native Page and component
106
+ * companions are emitted. The style finalizer therefore cannot mistake native WXSS for application CSS.
113
107
  */
114
108
  order: 'post',
115
109
  async handler(_, bundle) {