vite-plugin-taro 0.5.13 → 0.5.16

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 (71) hide show
  1. package/dist/node/plugins/client/client-taro.d.ts +1 -1
  2. package/dist/node/plugins/client/client-taro.js +2 -1
  3. package/dist/node/plugins/h5/create-stencil-client-adapter.d.ts +1 -1
  4. package/dist/node/plugins/h5/create-stencil-client-adapter.js +2 -1
  5. package/dist/node/plugins/h5/resolver/module-resolver.js +2 -1
  6. package/dist/node/plugins/wx/dev/create-hmr-results-stream.d.ts +23 -0
  7. package/dist/node/plugins/wx/dev/create-hmr-results-stream.js +57 -0
  8. package/dist/node/plugins/wx/dev/create-style-capture.d.ts +54 -0
  9. package/dist/node/plugins/wx/dev/create-style-capture.js +173 -0
  10. package/dist/node/plugins/wx/dev/dev-host.d.ts +1 -1
  11. package/dist/node/plugins/wx/dev/dev-host.js +161 -93
  12. package/dist/node/plugins/wx/dev/hmr-files.d.ts +1 -0
  13. package/dist/node/plugins/wx/dev/hmr-files.js +1 -0
  14. package/dist/node/plugins/wx/dev/host-actions.d.ts +16 -0
  15. package/dist/node/plugins/wx/dev/host-actions.js +58 -0
  16. package/dist/node/plugins/wx/dev/patch-publisher.d.ts +2 -2
  17. package/dist/node/plugins/wx/dev/patch-publisher.js +17 -1
  18. package/dist/node/plugins/wx/dev/plugins.d.ts +4 -0
  19. package/dist/node/plugins/wx/dev/plugins.js +24 -8
  20. package/dist/node/plugins/wx/dev/react-refresh.js +6 -2
  21. package/dist/node/plugins/wx/dev/runtime-reports.d.ts +27 -0
  22. package/dist/node/plugins/wx/dev/runtime-reports.js +45 -0
  23. package/dist/node/plugins/wx/dev/wx-dev-options.d.ts +7 -3
  24. package/dist/node/plugins/wx/dev/wx-dev-options.js +46 -28
  25. package/dist/node/plugins/wx/module.d.ts +1 -1
  26. package/dist/node/plugins/wx/module.js +2 -1
  27. package/dist/node/plugins/wx/native/compile-native-component-interface.js +2 -1
  28. package/dist/node/plugins/wx/native/create-native-component-output.js +2 -0
  29. package/dist/node/plugins/wx/native/native-component-assets.d.ts +1 -1
  30. package/dist/node/plugins/wx/native/native-component-assets.js +3 -3
  31. package/dist/node/plugins/wx/native/native-component-interface.js +2 -1
  32. package/dist/node/plugins/wx/resolve/resolver.js +3 -2
  33. package/dist/node/plugins/wx/styles/plugins.js +20 -1
  34. package/dist/node/utils/modules.d.ts +1 -1
  35. package/dist/node/utils/modules.js +4 -3
  36. package/dist/node/utils/packages.js +2 -2
  37. package/package.json +4 -3
  38. package/src/node/plugins/client/client-taro.ts +2 -2
  39. package/src/node/plugins/h5/create-stencil-client-adapter.ts +2 -2
  40. package/src/node/plugins/h5/resolver/module-resolver.ts +2 -1
  41. package/src/node/plugins/wx/dev/create-hmr-results-stream.ts +71 -0
  42. package/src/node/plugins/wx/dev/create-style-capture.ts +248 -0
  43. package/src/node/plugins/wx/dev/dev-host.ts +188 -124
  44. package/src/node/plugins/wx/dev/hmr-files.ts +1 -0
  45. package/src/node/plugins/wx/dev/host-actions.ts +88 -0
  46. package/src/node/plugins/wx/dev/patch-publisher.ts +19 -2
  47. package/src/node/plugins/wx/dev/plugins.ts +25 -8
  48. package/src/node/plugins/wx/dev/react-refresh.ts +6 -2
  49. package/src/node/plugins/wx/dev/runtime-reports.ts +70 -0
  50. package/src/node/plugins/wx/dev/wx-dev-options.ts +50 -36
  51. package/src/node/plugins/wx/module.ts +2 -4
  52. package/src/node/plugins/wx/native/compile-native-component-interface.ts +2 -1
  53. package/src/node/plugins/wx/native/create-native-component-output.ts +2 -0
  54. package/src/node/plugins/wx/native/native-component-assets.ts +3 -4
  55. package/src/node/plugins/wx/native/native-component-interface.ts +2 -1
  56. package/src/node/plugins/wx/resolve/resolver.ts +3 -2
  57. package/src/node/plugins/wx/styles/plugins.ts +21 -1
  58. package/src/node/utils/modules.ts +4 -3
  59. package/src/node/utils/packages.ts +2 -2
  60. package/dist/node/plugins/wx/dev/create-wx-dev-mode.d.ts +0 -6
  61. package/dist/node/plugins/wx/dev/create-wx-dev-mode.js +0 -9
  62. package/dist/node/plugins/wx/dev/styles/create-style-capture-plugin.d.ts +0 -12
  63. package/dist/node/plugins/wx/dev/styles/create-style-capture-plugin.js +0 -21
  64. package/dist/node/plugins/wx/dev/styles/publish-style-hmr.d.ts +0 -27
  65. package/dist/node/plugins/wx/dev/styles/publish-style-hmr.js +0 -48
  66. package/dist/node/utils/serialized-task-queue.d.ts +0 -13
  67. package/dist/node/utils/serialized-task-queue.js +0 -25
  68. package/src/node/plugins/wx/dev/create-wx-dev-mode.ts +0 -14
  69. package/src/node/plugins/wx/dev/styles/create-style-capture-plugin.ts +0 -34
  70. package/src/node/plugins/wx/dev/styles/publish-style-hmr.ts +0 -71
  71. package/src/node/utils/serialized-task-queue.ts +0 -32
@@ -0,0 +1,45 @@
1
+ import { buffer, debounceTime, filter, Subject } from 'rxjs';
2
+ /**
3
+ * Conflates runtime receipts without weakening their build identity or recovery semantics.
4
+ *
5
+ * Reports are metadata frontiers rather than executable deltas: for one build, the highest applied sequence subsumes lower and
6
+ * duplicate acknowledgements. A rebuild does not subsume reports from a different build, because delayed Pages may still POST
7
+ * after session rotation, but it dominates every acknowledgement for its own build. The host validates each retained build ID
8
+ * against the physical publisher only when its serialized task executes, preserving rotation order.
9
+ *
10
+ * Completing the Subject flushes the active window synchronously so shutdown can include its host task before closing Rolldown.
11
+ * Reduction is O(reports) time and O(distinct builds) retained space per quiet window.
12
+ */
13
+ export function createRuntimeReportsStream(settleMilliseconds, scheduler, publish) {
14
+ /*
15
+ * This Subject is the sole mutable HTTP admission edge. Requests append reports synchronously and finish their responses;
16
+ * buffer then owns the short-lived window while debounceTime supplies only its quiet-edge signal. Reports are metadata rather
17
+ * than executable deltas, so the window may be reduced before entering hostActions. Directly mutating PatchPublisher from the
18
+ * request stack would race patch writes and build rotation; retaining every raw receipt beyond the window would grow with Page
19
+ * count and duplicate acknowledgements. Completion flushes the final window during phased shutdown.
20
+ */
21
+ const reports = new Subject();
22
+ reports
23
+ .pipe(buffer(reports.pipe(debounceTime(settleMilliseconds, scheduler))), filter((window) => window.length > 0))
24
+ .subscribe((window) => publish(reduceRuntimeReportWindow(window)));
25
+ return reports;
26
+ }
27
+ export function reduceRuntimeReportWindow(window) {
28
+ /*
29
+ * This transaction-local mutable Map is bounded by distinct build IDs in one quiet window. Insertion order preserves causal
30
+ * build order, while replacing a value conflates only that build's monotonic acknowledgement frontier; rebuild becomes its
31
+ * terminal value. A single global latest report would let delayed Pages erase another build, and retaining the raw window
32
+ * would make downstream work proportional to duplicate report volume. The Map is discarded immediately after reduction.
33
+ */
34
+ const reportsByBuild = new Map();
35
+ for (const report of window) {
36
+ const previous = reportsByBuild.get(report.buildId);
37
+ if (previous?.kind === 'rebuild') {
38
+ continue;
39
+ }
40
+ if (report.kind === 'rebuild' || previous === undefined || report.seq > previous.seq) {
41
+ reportsByBuild.set(report.buildId, report);
42
+ }
43
+ }
44
+ return [...reportsByBuild.values()];
45
+ }
@@ -2,7 +2,7 @@ import type { InputOptions, OutputOptions, Plugin } from 'rolldown';
2
2
  import { type DevEngine } from 'rolldown/experimental';
3
3
  import type { ViteDevServer } from 'vite';
4
4
  import type { VitePluginTaroOptions } from '../../../../options.ts';
5
- export type BundledDevRolldownOptions = InputOptions & {
5
+ type BundledDevRolldownOptions = InputOptions & {
6
6
  experimental?: {
7
7
  [key: string]: unknown;
8
8
  devMode?: boolean | Record<string, unknown>;
@@ -15,12 +15,16 @@ export type BundledDev = {
15
15
  listen(): Promise<void>;
16
16
  triggerBundleRegenerationIfStale(): Promise<boolean>;
17
17
  };
18
- /** Installs the physical WX output and runtime conventions over Vite's browser-oriented bundled-development options. */
18
+ /**
19
+ * Installs physical WX output and runtime conventions over Vite's browser-oriented bundled-development options.
20
+ * Build completion deliberately remains outside this options adapter: DevEngine onOutput is the single lifecycle authority.
21
+ */
19
22
  export declare function installWxDevOptions({ bundledDev, server, options, hostPlugins }: {
20
23
  bundledDev: BundledDev;
21
24
  server: ViteDevServer;
22
25
  options: VitePluginTaroOptions;
23
26
  hostPlugins: Plugin[];
24
- }): Promise<void>;
27
+ }): void;
25
28
  /** Returns the configured output after rejecting states unsupported by the physical WX engine. */
26
29
  export declare function requireSingleOutput(rolldownOptions: BundledDevRolldownOptions): OutputOptions;
30
+ export {};
@@ -4,20 +4,17 @@ import { viteReporterPlugin } from 'rolldown/experimental';
4
4
  import { once } from '../../../utils/once.js';
5
5
  import { resolvePackageFile } from '../../../utils/packages.js';
6
6
  import { appShellFileName } from '../module.js';
7
- import { createWxDevMode } from './create-wx-dev-mode.js';
8
- /** Installs the physical WX output and runtime conventions over Vite's browser-oriented bundled-development options. */
7
+ /**
8
+ * Installs physical WX output and runtime conventions over Vite's browser-oriented bundled-development options.
9
+ * Build completion deliberately remains outside this options adapter: DevEngine onOutput is the single lifecycle authority.
10
+ */
9
11
  export function installWxDevOptions({ bundledDev, server, options, hostPlugins }) {
10
- // The buildEnd hook and its one-shot result belong to the same abstraction. Consumers can await startup without receiving
11
- // a resolver capable of settling it externally.
12
- const initialBuild = Promise.withResolvers();
13
- const settleInitialBuild = once((error) => {
14
- if (error) {
15
- initialBuild.reject(error);
16
- }
17
- else {
18
- initialBuild.resolve();
19
- }
20
- });
12
+ /*
13
+ * Vite owns this mutable adapter method and calls it later when constructing DevEngine. Replacing that one seam preserves
14
+ * Vite's resolved graph while applying WX physical-output conventions to every options generation. Capturing and mutating one
15
+ * options object here would be stale on later complete builds; creating a second engine would duplicate watchers and graphs.
16
+ * The bound original remains immutable and is invoked exactly once per delegated options request.
17
+ */
21
18
  const original = bundledDev.getRolldownOptions.bind(bundledDev);
22
19
  bundledDev.getRolldownOptions = async () => {
23
20
  const rolldownOptions = await original();
@@ -28,8 +25,15 @@ export function installWxDevOptions({ bundledDev, server, options, hostPlugins }
28
25
  }
29
26
  const configured = configuredOutput ?? {};
30
27
  // Every page entry must depend on hmr/patches.js: DevTools classifies a changed Page dependency as Page JavaScript
31
- // hot reload and re-executes live Pages, which is the only physical patch trigger that preserves the App heap.
28
+ // hot reload and re-executes live Pages, which is the only physical patch trigger that preserves the App heap. Although
29
+ // Set provides O(1) membership, exposing it as ReadonlySet prevents mutation after this options generation is resolved.
32
30
  const pageFiles = new Set(options.pages.map((page) => `${page.path}.js`));
31
+ /*
32
+ * Rolldown passes this mutable output object onward by identity, and nested Vite plugins may already retain it. Mutating
33
+ * that single object preserves their references while atomically replacing browser-oriented naming and format fields with
34
+ * stable physical WX conventions. Cloning only our fields would leave retained references on stale hashed filenames;
35
+ * mutating configuredOutput itself would leak development normalization back into the user's resolved Vite config.
36
+ */
33
37
  Object.assign(output, configured, {
34
38
  assetFileNames: createStableFileNames(configured.assetFileNames, 'assets/[name][extname]'),
35
39
  banner: createEntryBanner(pageFiles),
@@ -39,24 +43,27 @@ export function installWxDevOptions({ bundledDev, server, options, hostPlugins }
39
43
  minify: true,
40
44
  sourcemap: false
41
45
  });
46
+ /*
47
+ * experimental belongs to this transaction-local Rolldown options generation. Materialize it only when absent, then
48
+ * replace devMode with a new object so user fields survive without mutating a possibly shared configured sub-object.
49
+ */
42
50
  rolldownOptions.experimental ??= {};
43
- rolldownOptions.experimental.devMode = createWxDevMode(rolldownOptions.experimental.devMode, await bundleRuntimeSource());
44
- const reportInitialBuildPlugin = {
45
- name: 'vpt:wx-report-initial-build',
46
- buildEnd: settleInitialBuild
51
+ const existingDevMode = rolldownOptions.experimental.devMode;
52
+ rolldownOptions.experimental.devMode = {
53
+ ...(typeof existingDevMode === 'object' ? existingDevMode : {}),
54
+ implement: await bundleRuntimeSource(),
55
+ lazy: false,
56
+ skipCommonRuntimeInjection: false
47
57
  };
48
- // Preserve the physical paths watched by WeChat DevTools across host restarts; the complete build overwrites every
49
- // active output without disrupting the hot-reload watcher.
50
- rolldownOptions.plugins = [
51
- rolldownOptions.plugins,
52
- hostPlugins,
53
- reportInitialBuildPlugin,
54
- createViteReporter(server)
55
- ];
58
+ /*
59
+ * The plugin list is mutable configuration consumed once by this engine generation. Replace the top-level reference with
60
+ * an ordered composite rather than pushing into Vite's potentially shared nested array: existing transforms run first,
61
+ * host capture observes their final values, and the reporter observes final output without mutating either input list.
62
+ */
63
+ rolldownOptions.plugins = [rolldownOptions.plugins, hostPlugins, createViteReporter(server)];
56
64
  disableViteOxcSourcemap(rolldownOptions.plugins);
57
65
  return rolldownOptions;
58
66
  };
59
- return initialBuild.promise;
60
67
  }
61
68
  /** Returns the configured output after rejecting states unsupported by the physical WX engine. */
62
69
  export function requireSingleOutput(rolldownOptions) {
@@ -70,6 +77,7 @@ function ensureSingleOutput(rolldownOptions) {
70
77
  if (Array.isArray(rolldownOptions.output)) {
71
78
  throw new Error('wx development requires one configured Rolldown output.');
72
79
  }
80
+ // Rolldown needs the created object attached to input options by identity; returning a detached fallback would be ignored.
73
81
  rolldownOptions.output ??= {};
74
82
  return rolldownOptions.output;
75
83
  }
@@ -112,6 +120,11 @@ function disableViteOxcSourcemap(pluginOption) {
112
120
  return;
113
121
  const plugin = pluginOption;
114
122
  if (plugin.name === 'builtin:vite-transform' && plugin._options?.transformOptions) {
123
+ /*
124
+ * Vite's private builtin retains this mutable transform options object and does not expose a public replacement hook.
125
+ * Updating its one sourcemap flag prevents Oxc from allocating maps that final WX output always discards; cloning the
126
+ * descriptor would not update the builtin closure that reads the original object during transforms.
127
+ */
115
128
  plugin._options.transformOptions.sourcemap = false;
116
129
  }
117
130
  }
@@ -128,7 +141,12 @@ function createViteReporter(server) {
128
141
  warnLargeChunks: false
129
142
  });
130
143
  }
131
- // The runtime source is immutable for the host's lifetime, so the nested bundle is shared by every complete build.
144
+ /*
145
+ * once owns a mutable cached Promise, justified because getRolldownOptions may run for multiple complete generations while the
146
+ * runtime input and build options remain immutable for the process lifetime. Sharing the in-flight/result Promise prevents
147
+ * concurrent options requests from launching duplicate nested builds; caching source instead of a Rolldown output object avoids
148
+ * leaking mutable bundle metadata between engines.
149
+ */
132
150
  const bundleRuntimeSource = once(async function bundleRuntimeSource() {
133
151
  // write: false keeps this nested helper build from creating a second dist directory in the application project.
134
152
  const result = await build({
@@ -1,4 +1,4 @@
1
- import type { Rolldown } from 'vite';
1
+ import { type Rolldown } from 'vite';
2
2
  /** Identifies Rolldown's generated helper module independently of its unstable output filename. */
3
3
  export declare const rolldownRuntimeId = "\0rolldown/runtime.js";
4
4
  /** Resolves the direct React Reconciler dependency without assuming pnpm's layout. */
@@ -1,10 +1,11 @@
1
1
  import path from 'node:path';
2
+ import { normalizePath } from 'vite';
2
3
  import { normalizeModuleId } from '../../utils/modules.js';
3
4
  import { packageRequire, resolvePackageFile } from '../../utils/packages.js';
4
5
  /** Identifies Rolldown's generated helper module independently of its unstable output filename. */
5
6
  export const rolldownRuntimeId = '\0rolldown/runtime.js';
6
7
  /** Resolves the direct React Reconciler dependency without assuming pnpm's layout. */
7
- export const reactReconcilerRoot = normalizeModuleId(path.dirname(packageRequire.resolve('react-reconciler/package.json')));
8
+ export const reactReconcilerRoot = normalizePath(path.dirname(packageRequire.resolve('react-reconciler/package.json')));
8
9
  /** Identifies the amphibious bootstrap that initializes SystemJS and serves every native shell. */
9
10
  export const bootstrapPath = resolvePackageFile('dist/runtime/wx/amphibious/bootstrap.js');
10
11
  /** Identifies the native transport source materialized before Rolldown finalizes content hashes. */
@@ -1,4 +1,5 @@
1
1
  import path from 'node:path';
2
+ import { normalizePath } from 'vite';
2
3
  import { normalizeModuleId } from '../../../utils/modules.js';
3
4
  import { collectNativeComponentAssets, nativeComponentMetaKey } from './native-component-assets.js';
4
5
  import { transformNativeComponentInterfaces } from './native-component-interface.js';
@@ -10,7 +11,7 @@ export async function compileNativeComponentInterface({ code, id, sourcemap, add
10
11
  nativeComponents.forEach((source) => {
11
12
  addWatchFile(source.folder);
12
13
  source.assets.forEach((asset) => {
13
- addWatchFile(normalizeModuleId(path.join(source.folder, asset.relativePath)));
14
+ addWatchFile(normalizePath(path.join(source.folder, asset.relativePath)));
14
15
  });
15
16
  });
16
17
  const assetBytes = nativeComponents.reduce((sourceTotal, source) => {
@@ -26,6 +26,8 @@ export async function createNativeComponentOutput({ bundle, getModuleInfo }) {
26
26
  fields: source.fields
27
27
  });
28
28
  for (const asset of source.assets) {
29
+ // Native folders are intentionally opaque to Rolldown, so no bundle asset contains these bytes to reuse.
30
+ // Read each planned source once here to transfer it into the compiler-owned output bundle.
29
31
  files.push({
30
32
  type: 'asset',
31
33
  fileName: path.posix.join(outputFolder, asset.relativePath),
@@ -1,4 +1,4 @@
1
- import type { Rolldown } from 'vite';
1
+ import { type Rolldown } from 'vite';
2
2
  import type { NativeComponentDefinition } from './native-component-interface.ts';
3
3
  type NativeComponentAsset = {
4
4
  relativePath: string;
@@ -1,6 +1,6 @@
1
1
  import { readdir, stat } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { normalizeModuleId } from '../../../utils/modules.js';
3
+ import { normalizePath } from 'vite';
4
4
  export const nativeComponentMetaKey = 'vpt:native-components';
5
5
  /** Reads the native contribution to a transformed module's package weight. */
6
6
  export function getNativeComponentAssetBytes(meta) {
@@ -16,7 +16,7 @@ export async function collectNativeComponentAssets(definition, interfacePath) {
16
16
  await requireNativeComponentEntry(definition);
17
17
  // This mutable accumulator is local to one traversal and avoids repeatedly copying growing asset arrays.
18
18
  const assets = [];
19
- await collectDirectory(definition.folder, [], assets, normalizeModuleId(interfacePath));
19
+ await collectDirectory(definition.folder, [], assets, normalizePath(interfacePath));
20
20
  return { ...definition, assets };
21
21
  }
22
22
  /** Rejects a missing or non-directory source before Rolldown starts its recursive traversal. */
@@ -65,7 +65,7 @@ async function collectDirectory(folder, segments, assets, interfacePath) {
65
65
  continue;
66
66
  }
67
67
  const sourcePath = path.join(folder, ...entrySegments);
68
- if (normalizeModuleId(sourcePath) === interfacePath) {
68
+ if (normalizePath(sourcePath) === interfacePath) {
69
69
  continue;
70
70
  }
71
71
  assets.push({
@@ -1,6 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import { RolldownMagicString } from 'rolldown';
3
3
  import { parseAst } from 'rolldown/parseAst';
4
+ import { normalizePath } from 'vite';
4
5
  import { normalizeModuleId } from '../../../utils/modules.js';
5
6
  import { clientTaroNativeId } from '../../client/constant.js';
6
7
  /** Replaces native component interface calls with source-folder names and returns their static metadata. */
@@ -96,7 +97,7 @@ function parseDefinition(call, moduleId, declaredInterfaces, buildError) {
96
97
  if (!entryReference) {
97
98
  throw buildError('Native component entry must use () => import(...) with a static relative .js path');
98
99
  }
99
- const entryPath = normalizeModuleId(path.resolve(path.dirname(moduleId), entryReference));
100
+ const entryPath = normalizePath(path.resolve(path.dirname(moduleId), entryReference));
100
101
  return {
101
102
  folder: path.posix.dirname(entryPath),
102
103
  entry: path.posix.basename(entryPath, '.js'),
@@ -1,3 +1,4 @@
1
+ import { normalizePath } from 'vite';
1
2
  import { normalizeModuleId, resolveAppComponentPath, resolvePageComponentPath } from '../../../utils/modules.js';
2
3
  import { createAppConfig } from '../../../utils/project-config.js';
3
4
  import { appComponentId } from '../../client/constant.js';
@@ -6,8 +7,8 @@ import { specializeBootstrap } from './specialize-bootstrap.js';
6
7
  import { specializePageCapsule } from './specialize-page-capsule.js';
7
8
  /** Creates the resolver and source specializer for the wx module graph. */
8
9
  export function createResolver(options) {
9
- const normalizedBootstrapPath = normalizeModuleId(bootstrapPath);
10
- const normalizedPageCapsulePath = normalizeModuleId(pageCapsulePath);
10
+ const normalizedBootstrapPath = normalizePath(bootstrapPath);
11
+ const normalizedPageCapsulePath = normalizePath(pageCapsulePath);
11
12
  // Construct output input and application traversal roots together once so style order cannot drift from route order.
12
13
  const entryGraph = createEntryGraph(options.pages);
13
14
  // Provide constant-time route validation and access to each configured Page JSON object.
@@ -42,6 +42,14 @@ export function createWxStylePlugins() {
42
42
  * remain opaque.
43
43
  */
44
44
  function createWxStyleFinalizer(transformStyle) {
45
+ /*
46
+ * DevEngine can omit an unchanged stylesheet from later complete output generations. Absence therefore has two meanings:
47
+ * the first clean output genuinely has no styles, or a later output is reusing the physical stylesheet already on disk.
48
+ * The Vite server creates a fresh plugin instance on a clean start, so this one lifecycle bit distinguishes those cases and
49
+ * resets naturally on restart. Deliberately retain no CSS bytes or graph projection here: memory remains O(1), changed CSS
50
+ * still arrives as a normal asset, and the dev host remains the only owner of incremental style preparation.
51
+ */
52
+ let hasFinalizedOutput = false;
45
53
  return {
46
54
  name: 'vpt:wx-style-finalizer',
47
55
  generateBundle: {
@@ -53,7 +61,17 @@ function createWxStyleFinalizer(transformStyle) {
53
61
  throw new Error('WX builds support at most one compiler-emitted stylesheet');
54
62
  }
55
63
  if (styles.length === 0) {
56
- this.emitFile({ type: 'asset', fileName: 'assets/global.wxss', source: '' });
64
+ /*
65
+ * A clean first output must materialize the stable import target imported by app.wxss. On later complete
66
+ * outputs, DevEngine's physical writer preserves files omitted from the bundle; emitting the same empty
67
+ * placeholder would instead overwrite valid unchanged WXSS. Emit nothing in that later case. A source or
68
+ * configuration change that really removes all styles is already published as empty by ordinary style HMR,
69
+ * while a clean server restart reaches this first-output branch and also clears any stale prior file.
70
+ */
71
+ if (!hasFinalizedOutput) {
72
+ this.emitFile({ type: 'asset', fileName: 'assets/global.wxss', source: '' });
73
+ }
74
+ hasFinalizedOutput = true;
57
75
  return;
58
76
  }
59
77
  const [style] = styles;
@@ -63,6 +81,7 @@ function createWxStyleFinalizer(transformStyle) {
63
81
  // intact. Only its finalized contents and stable WXSS identity change.
64
82
  style.source = transformedResult.css;
65
83
  style.fileName = 'assets/global.wxss';
84
+ hasFinalizedOutput = true;
66
85
  }
67
86
  }
68
87
  };
@@ -14,7 +14,7 @@ export declare function resolveAppComponentPath({ appPath, projectRoot }: AppCom
14
14
  export declare function resolvePageComponentPath({ pagePath, projectRoot }: PageComponentPathOptions): string;
15
15
  /** Creates a portable import for one configured Page component. */
16
16
  export declare function createPageComponentImportPath(options: PageComponentPathOptions): string;
17
- /** Normalizes a file-backed Vite module ID for stable comparisons. */
17
+ /** Converts a file-backed Vite request ID to its normalized physical path for stable comparisons. */
18
18
  export declare function normalizeModuleId(id: string): string;
19
19
  /** Converts a local file path into Vite's portable file-system import form. */
20
20
  export declare function toViteFileImportPath(filePath: string): string;
@@ -1,4 +1,5 @@
1
1
  import path from 'node:path';
2
+ import { normalizePath } from 'vite';
2
3
  /** Resolves a final relative reference to the canonical output chunk ID used by the WX module registry. */
3
4
  export function resolveChunkReference(importerChunkId, reference) {
4
5
  if (!reference.startsWith('./') && !reference.startsWith('../')) {
@@ -18,11 +19,11 @@ export function resolvePageComponentPath({ pagePath, projectRoot }) {
18
19
  export function createPageComponentImportPath(options) {
19
20
  return toViteFileImportPath(resolvePageComponentPath(options));
20
21
  }
21
- /** Normalizes a file-backed Vite module ID for stable comparisons. */
22
+ /** Converts a file-backed Vite request ID to its normalized physical path for stable comparisons. */
22
23
  export function normalizeModuleId(id) {
23
- return id.replaceAll('\\', '/').replace(/\?.*$/, '');
24
+ return normalizePath(id.replace(/\?.*$/, ''));
24
25
  }
25
26
  /** Converts a local file path into Vite's portable file-system import form. */
26
27
  export function toViteFileImportPath(filePath) {
27
- return `/@fs/${normalizeModuleId(path.resolve(filePath))}`;
28
+ return `/@fs/${normalizePath(path.resolve(filePath))}`;
28
29
  }
@@ -1,9 +1,9 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import path from 'node:path';
3
- import { normalizeModuleId } from './modules.js';
3
+ import { normalizePath } from 'vite';
4
4
  export const packageRequire = createRequire(import.meta.url);
5
5
  const packageRoot = path.dirname(packageRequire.resolve('vite-plugin-taro/package.json'));
6
6
  /** Resolves a file shipped by this package as a portable Vite module ID. */
7
7
  export function resolvePackageFile(...segments) {
8
- return normalizeModuleId(path.join(packageRoot, ...segments));
8
+ return normalizePath(path.join(packageRoot, ...segments));
9
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-taro",
3
- "version": "0.5.13",
3
+ "version": "0.5.16",
4
4
  "author": "sep2",
5
5
  "description": "Vite 8 plugin for building one React/Taro codebase for WeChat Mini Program and H5 targets.",
6
6
  "type": "module",
@@ -73,10 +73,11 @@
73
73
  "react-reconciler": "0.33.0",
74
74
  "react-refresh": "^0.18.0",
75
75
  "rolldown": "1.2.3",
76
+ "rxjs": "^7.8.2",
76
77
  "tailwindcss": "^4.3.3",
77
78
  "weapp-tailwindcss": "^5.2.11",
78
- "@tarojs/plugin-framework-react": "npm:vite-plugin-taro-plugin-framework-react@0.5.13",
79
- "@tarojs/react": "npm:vite-plugin-taro-react@0.5.13"
79
+ "@tarojs/plugin-framework-react": "npm:vite-plugin-taro-plugin-framework-react@0.5.16",
80
+ "@tarojs/react": "npm:vite-plugin-taro-react@0.5.16"
80
81
  },
81
82
  "peerDependencies": {
82
83
  "react": "^19.0.0",
@@ -1,4 +1,4 @@
1
- import type { Plugin } from 'vite'
1
+ import { normalizePath, type Plugin } from 'vite'
2
2
  import type { VitePluginTaroTarget } from '../../../options.ts'
3
3
  import { normalizeModuleId } from '../../utils/modules.ts'
4
4
  import { resolvePackageFile } from '../../utils/packages.ts'
@@ -21,7 +21,7 @@ import { injectTaroFrameworkApis } from './inject-taro-framework-apis.ts'
21
21
  export const clientTaroApiId = 'virtual:taro/api'
22
22
 
23
23
  const clientTaroApiPath = resolvePackageFile('dist/runtime/client/taro/api.js')
24
- const normalizedClientTaroApiPath = normalizeModuleId(clientTaroApiPath)
24
+ const normalizedClientTaroApiPath = normalizePath(clientTaroApiPath)
25
25
 
26
26
  const clientTaroComponentId = 'virtual:taro/components'
27
27
 
@@ -1,6 +1,6 @@
1
1
  import type { WalkerEnter } from 'oxc-walker'
2
2
  import type { RolldownMagicString } from 'rolldown'
3
- import type { Plugin } from 'vite'
3
+ import { normalizePath, type Plugin } from 'vite'
4
4
  import { normalizeModuleId } from '../../utils/modules.ts'
5
5
  import { transformWithOxcWalker } from '../../utils/oxc-transform.ts'
6
6
  import { packageRequire } from '../../utils/packages.ts'
@@ -8,7 +8,7 @@ import { packageRequire } from '../../utils/packages.ts'
8
8
  const stencilClientPath = packageRequire.resolve('@stencil/core/internal/client', {
9
9
  paths: [packageRequire.resolve('@tarojs/components/package.json')]
10
10
  })
11
- const normalizedStencilClientPath = normalizeModuleId(stencilClientPath)
11
+ const normalizedStencilClientPath = normalizePath(stencilClientPath)
12
12
 
13
13
  /**
14
14
  * Creates the compiler-owned adaptation of Stencil's client style insertion.
@@ -1,3 +1,4 @@
1
+ import { normalizePath } from 'vite'
1
2
  import type { VitePluginTaroOptions } from '../../../../options.ts'
2
3
  import { normalizeModuleId, resolveAppComponentPath } from '../../../utils/modules.ts'
3
4
  import { appComponentId } from '../../client/constant.ts'
@@ -24,7 +25,7 @@ export function createModuleResolver(options: VitePluginTaroOptions) {
24
25
  projectRoot: string
25
26
  sourcemap?: boolean
26
27
  }) {
27
- if (normalizeModuleId(id) === normalizeModuleId(h5AppPath)) {
28
+ if (normalizeModuleId(id) === normalizePath(h5AppPath)) {
28
29
  return transformH5App({ code, id, options, projectRoot, sourcemap })
29
30
  }
30
31
  }
@@ -0,0 +1,71 @@
1
+ import type { DevOptions } from 'rolldown/experimental'
2
+ import { buffer, debounceTime, filter, type SchedulerLike, Subject } from 'rxjs'
3
+
4
+ type HmrUpdatesResult = Parameters<NonNullable<DevOptions['onHmrUpdates']>>[0]
5
+ type HmrUpdates = Exclude<HmrUpdatesResult, Error>
6
+
7
+ /**
8
+ * Adapts Rolldown's non-awaited HMR callback into lossless quiet-window publications.
9
+ *
10
+ * Debouncing the result stream itself would retain only the final callback and lose incremental patch factories that later
11
+ * patches provably do not reproduce. Instead, the debounced view is only a closing notifier for `buffer`: every callback is
12
+ * retained in arrival order, and one configured quiet period emits the complete window to the host's existing serialized
13
+ * writer. This first stream migration deliberately does not own physical state; `publish` and `reportError` remain synchronous
14
+ * admission callbacks so the dev host can enqueue both through its single writer.
15
+ *
16
+ * DevEngine failures are values, not Observable errors. They represent transient editor generations and carry no executable
17
+ * patch, so the stream reports the final error but otherwise removes them. Successful callbacks on either side remain ordered
18
+ * and lossless; Rolldown generated each against its unpublished client frontier, and the host delivers every retained payload.
19
+ * Completing the returned Subject flushes its current buffer synchronously, allowing host shutdown to await the resulting
20
+ * serialized task rather than silently dropping an admitted patch.
21
+ *
22
+ * Complexity is O(updates + changed files) per emitted window, with one retained reference per callback until the quiet edge.
23
+ */
24
+ export function createHmrResultsStream(
25
+ settleMilliseconds: number,
26
+ scheduler: SchedulerLike,
27
+ publish: (result: HmrUpdates) => void,
28
+ reportError: (error: Error) => void
29
+ ): Subject<HmrUpdatesResult> {
30
+ /*
31
+ * This hot Subject is the sole mutable boundary between Rolldown's non-awaited callback and RxJS. Each next preserves one
32
+ * compiler generation by reference and in arrival order. buffer owns the temporary lossless window; debounceTime observes the
33
+ * same Subject only to decide when that window closes. Replacing this with a debounced scalar would discard patch factories,
34
+ * while letting callback Promises mutate host state directly would overlap publications. Completion flushes the final window
35
+ * before shutdown, after which the Subject is never reused.
36
+ */
37
+ const results = new Subject<HmrUpdatesResult>()
38
+
39
+ results
40
+ .pipe(
41
+ buffer(results.pipe(debounceTime(settleMilliseconds, scheduler))),
42
+ filter((window) => window.length > 0)
43
+ )
44
+ .subscribe((window) => {
45
+ /*
46
+ * An Error is one source generation that produced no executable delta; it does not invalidate successful Rolldown
47
+ * callbacks already admitted on either side of it. Dropping the whole quiet window would therefore discard real
48
+ * patch factories, while starting a complete build would compile the same known-invalid editor contents. Keep the
49
+ * old runtime running, report only the latest diagnostic from a repeated parser burst, and continue below with the
50
+ * successful callback values exactly as if the temporary invalid save had never produced an HMR payload.
51
+ */
52
+ const lastFailure = window.findLast((result): result is Error => result instanceof Error)
53
+ if (lastFailure) {
54
+ reportError(lastFailure)
55
+ }
56
+ const successfulWindow = window.filter((result): result is HmrUpdates => !(result instanceof Error))
57
+ if (successfulWindow.length > 0) {
58
+ /*
59
+ * flatMap preserves callback arrival order and each callback's internal Rolldown order. Updates must not be
60
+ * deduplicated: later undelivered patches are deltas and do not reproduce factories from earlier sequences.
61
+ * changedFiles is opaque DevEngine metadata and has no identity semantics in the host, so preserving its raw
62
+ * concatenation is both cheaper and more faithful than maintaining a second Set-based interpretation here.
63
+ */
64
+ publish({
65
+ updates: successfulWindow.flatMap((result) => result.updates),
66
+ changedFiles: successfulWindow.flatMap((result) => result.changedFiles)
67
+ })
68
+ }
69
+ })
70
+ return results
71
+ }