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
@@ -1,4 +1,4 @@
1
- import { type PluginOption, transformWithOxc } from 'vite'
1
+ import { normalizePath, type PluginOption, transformWithOxc } from 'vite'
2
2
  import type { VitePluginTaroOptions } from '../../../../options.ts'
3
3
  import { esTarget } from '../../../utils/constant.ts'
4
4
  import { memoize } from '../../../utils/memoize.ts'
@@ -10,6 +10,11 @@ import { createWxReactRefreshTransforms } from './react-refresh.ts'
10
10
 
11
11
  const taroRuntimeId = '@tarojs/runtime'
12
12
 
13
+ /** Selects the sole Vite environment that owns the physical Mini Program development project. */
14
+ export function isWxClientEnvironment(environment: Readonly<{ name: string }>): boolean {
15
+ return environment.name === 'client'
16
+ }
17
+
13
18
  /**
14
19
  * Adds the serve-only bundled-development plugin set for the wx target: the dev adapter,
15
20
  * Page HMR activation, and React Refresh adaptation transforms.
@@ -21,17 +26,26 @@ export function createWxDevelopmentPlugin(
21
26
  options: VitePluginTaroOptions,
22
27
  applicationEntryIds: readonly string[]
23
28
  ): PluginOption[] {
24
- // This mutable handle transfers ownership from configureServer to closeBundle; at most one host exists per plugin instance.
29
+ /*
30
+ * Vite creates this plugin descriptor before a server or DevEngine exists, then invokes configureServer and closeBundle on
31
+ * different lifecycle stacks. This mutable handle transfers the one client-owned host between those hooks: configureServer
32
+ * assigns it after asynchronous construction, and closeBundle reads it to drain the same engine. Environment scoping ensures
33
+ * only the client hook pair participates. Capturing a construction Promise would start too early, while recreating the host
34
+ * in closeBundle would lose every live action, patch, style, and client frontier owned by the running instance.
35
+ */
25
36
  let host: WxDevHost | null = null
26
37
  // Portable hook filters stay broad; these exact identities exclude similarly named user modules.
27
- const normalizedAppCapsulePath = normalizeModuleId(appCapsulePath)
28
- const normalizedPageCapsulePath = normalizeModuleId(pageCapsulePath)
29
- const normalizedTaroRuntimePath = normalizeModuleId(taroRuntimePath)
38
+ const normalizedAppCapsulePath = normalizePath(appCapsulePath)
39
+ const normalizedPageCapsulePath = normalizePath(pageCapsulePath)
40
+ const normalizedTaroRuntimePath = normalizePath(taroRuntimePath)
30
41
 
31
42
  return [
32
43
  {
33
44
  name: 'vpt:wx-dev',
34
45
  apply: 'serve',
46
+ // The physical WX DevEngine is a client build. Native environment scoping gives its shared host exactly one
47
+ // generate/close lifecycle instead of admitting hooks from Vite's unrelated SSR environment.
48
+ applyToEnvironment: isWxClientEnvironment,
35
49
 
36
50
  config() {
37
51
  return {
@@ -205,9 +219,12 @@ function getPageRoute(id: string): string {
205
219
  return route
206
220
  }
207
221
 
208
- // The assembled runtime chunk is byte-identical on every build (the base runtime and the
209
- // bundled implement are immutable for the server's lifetime), so the lowering runs once
210
- // and every build reuses it.
222
+ /*
223
+ * memoize owns a mutable one-entry-by-input cache. The assembled runtime source is byte-identical across complete generations
224
+ * because both the Rolldown base and injected implementation are immutable for the server lifetime. Reusing its lowered result
225
+ * avoids repeating the only large Oxc transform on every rebuild; keying by source still invalidates correctly if a future Vite
226
+ * generation changes the runtime, unlike a module-global cached string detached from its actual input.
227
+ */
211
228
  const fixRolldownRuntime = memoize((code: string) => {
212
229
  return transformWithOxc(code, rolldownRuntimeId, {
213
230
  lang: 'js',
@@ -214,7 +214,11 @@ export function removeRefreshPreambleGuard({ code, id }: { code: string; id: str
214
214
  })
215
215
  }
216
216
 
217
- // The refresh runtime module and the react-family modules are immutable for the server's
218
- // lifetime, so their Oxc parses run once per module and every build reuses the output.
217
+ /*
218
+ * Each memoized transform owns a mutable cache keyed only by source bytes. The matching runtime/react-family modules are
219
+ * immutable between complete generations, so reparsing them would repeat O(source bytes) Oxc work without observing new state.
220
+ * Source keys still create a fresh result after a dependency upgrade or real module edit, unlike a once-only cache keyed by ID;
221
+ * keeping separate caches prevents identical text in different transform domains from sharing the wrong adaptation.
222
+ */
219
223
  const fixRefreshRuntime = memoize(transformRefreshRuntime, { getCacheKey: ({ code }) => code })
220
224
  const fixReactDevtoolsHook = memoize(transformReactDevtoolsHook, { getCacheKey: ({ code }) => code })
@@ -0,0 +1,70 @@
1
+ import { buffer, debounceTime, filter, type SchedulerLike, Subject } from 'rxjs'
2
+
3
+ type AppliedReport = Readonly<{
4
+ kind: 'applied'
5
+ buildId: string
6
+ seq: number
7
+ }>
8
+
9
+ /** The runtime hit an unrecoverable state and needs a complete build. */
10
+ type RebuildReport = Readonly<{
11
+ kind: 'rebuild'
12
+ buildId: string
13
+ reason: string
14
+ }>
15
+
16
+ export type RuntimeReport = AppliedReport | RebuildReport
17
+
18
+ /**
19
+ * Conflates runtime receipts without weakening their build identity or recovery semantics.
20
+ *
21
+ * Reports are metadata frontiers rather than executable deltas: for one build, the highest applied sequence subsumes lower and
22
+ * duplicate acknowledgements. A rebuild does not subsume reports from a different build, because delayed Pages may still POST
23
+ * after session rotation, but it dominates every acknowledgement for its own build. The host validates each retained build ID
24
+ * against the physical publisher only when its serialized task executes, preserving rotation order.
25
+ *
26
+ * Completing the Subject flushes the active window synchronously so shutdown can include its host task before closing Rolldown.
27
+ * Reduction is O(reports) time and O(distinct builds) retained space per quiet window.
28
+ */
29
+ export function createRuntimeReportsStream(
30
+ settleMilliseconds: number,
31
+ scheduler: SchedulerLike,
32
+ publish: (reports: readonly RuntimeReport[]) => void
33
+ ): Subject<RuntimeReport> {
34
+ /*
35
+ * This Subject is the sole mutable HTTP admission edge. Requests append reports synchronously and finish their responses;
36
+ * buffer then owns the short-lived window while debounceTime supplies only its quiet-edge signal. Reports are metadata rather
37
+ * than executable deltas, so the window may be reduced before entering hostActions. Directly mutating PatchPublisher from the
38
+ * request stack would race patch writes and build rotation; retaining every raw receipt beyond the window would grow with Page
39
+ * count and duplicate acknowledgements. Completion flushes the final window during phased shutdown.
40
+ */
41
+ const reports = new Subject<RuntimeReport>()
42
+
43
+ reports
44
+ .pipe(
45
+ buffer(reports.pipe(debounceTime(settleMilliseconds, scheduler))),
46
+ filter((window) => window.length > 0)
47
+ )
48
+ .subscribe((window) => publish(reduceRuntimeReportWindow(window)))
49
+ return reports
50
+ }
51
+
52
+ export function reduceRuntimeReportWindow(window: readonly RuntimeReport[]): readonly RuntimeReport[] {
53
+ /*
54
+ * This transaction-local mutable Map is bounded by distinct build IDs in one quiet window. Insertion order preserves causal
55
+ * build order, while replacing a value conflates only that build's monotonic acknowledgement frontier; rebuild becomes its
56
+ * terminal value. A single global latest report would let delayed Pages erase another build, and retaining the raw window
57
+ * would make downstream work proportional to duplicate report volume. The Map is discarded immediately after reduction.
58
+ */
59
+ const reportsByBuild = new Map<string, RuntimeReport>()
60
+ for (const report of window) {
61
+ const previous = reportsByBuild.get(report.buildId)
62
+ if (previous?.kind === 'rebuild') {
63
+ continue
64
+ }
65
+ if (report.kind === 'rebuild' || previous === undefined || report.seq > previous.seq) {
66
+ reportsByBuild.set(report.buildId, report)
67
+ }
68
+ }
69
+ return [...reportsByBuild.values()]
70
+ }
@@ -7,9 +7,8 @@ import type { VitePluginTaroOptions } from '../../../../options.ts'
7
7
  import { once } from '../../../utils/once.ts'
8
8
  import { resolvePackageFile } from '../../../utils/packages.ts'
9
9
  import { appShellFileName } from '../module.ts'
10
- import { createWxDevMode } from './create-wx-dev-mode.ts'
11
10
 
12
- export type BundledDevRolldownOptions = InputOptions & {
11
+ type BundledDevRolldownOptions = InputOptions & {
13
12
  experimental?: {
14
13
  [key: string]: unknown
15
14
  devMode?: boolean | Record<string, unknown>
@@ -24,7 +23,10 @@ export type BundledDev = {
24
23
  triggerBundleRegenerationIfStale(): Promise<boolean>
25
24
  }
26
25
 
27
- /** Installs the physical WX output and runtime conventions over Vite's browser-oriented bundled-development options. */
26
+ /**
27
+ * Installs physical WX output and runtime conventions over Vite's browser-oriented bundled-development options.
28
+ * Build completion deliberately remains outside this options adapter: DevEngine onOutput is the single lifecycle authority.
29
+ */
28
30
  export function installWxDevOptions({
29
31
  bundledDev,
30
32
  server,
@@ -35,17 +37,13 @@ export function installWxDevOptions({
35
37
  server: ViteDevServer
36
38
  options: VitePluginTaroOptions
37
39
  hostPlugins: Plugin[]
38
- }): Promise<void> {
39
- // The buildEnd hook and its one-shot result belong to the same abstraction. Consumers can await startup without receiving
40
- // a resolver capable of settling it externally.
41
- const initialBuild = Promise.withResolvers<void>()
42
- const settleInitialBuild = once((error: Error | undefined): void => {
43
- if (error) {
44
- initialBuild.reject(error)
45
- } else {
46
- initialBuild.resolve()
47
- }
48
- })
40
+ }): void {
41
+ /*
42
+ * Vite owns this mutable adapter method and calls it later when constructing DevEngine. Replacing that one seam preserves
43
+ * Vite's resolved graph while applying WX physical-output conventions to every options generation. Capturing and mutating one
44
+ * options object here would be stale on later complete builds; creating a second engine would duplicate watchers and graphs.
45
+ * The bound original remains immutable and is invoked exactly once per delegated options request.
46
+ */
49
47
  const original = bundledDev.getRolldownOptions.bind(bundledDev)
50
48
 
51
49
  bundledDev.getRolldownOptions = async () => {
@@ -59,9 +57,16 @@ export function installWxDevOptions({
59
57
  const configured = configuredOutput ?? {}
60
58
 
61
59
  // Every page entry must depend on hmr/patches.js: DevTools classifies a changed Page dependency as Page JavaScript
62
- // hot reload and re-executes live Pages, which is the only physical patch trigger that preserves the App heap.
63
- const pageFiles = new Set(options.pages.map((page) => `${page.path}.js`))
64
-
60
+ // hot reload and re-executes live Pages, which is the only physical patch trigger that preserves the App heap. Although
61
+ // Set provides O(1) membership, exposing it as ReadonlySet prevents mutation after this options generation is resolved.
62
+ const pageFiles: ReadonlySet<string> = new Set(options.pages.map((page) => `${page.path}.js`))
63
+
64
+ /*
65
+ * Rolldown passes this mutable output object onward by identity, and nested Vite plugins may already retain it. Mutating
66
+ * that single object preserves their references while atomically replacing browser-oriented naming and format fields with
67
+ * stable physical WX conventions. Cloning only our fields would leave retained references on stale hashed filenames;
68
+ * mutating configuredOutput itself would leak development normalization back into the user's resolved Vite config.
69
+ */
65
70
  Object.assign(output, configured, {
66
71
  assetFileNames: createStableFileNames(configured.assetFileNames, 'assets/[name][extname]'),
67
72
  banner: createEntryBanner(pageFiles),
@@ -72,31 +77,29 @@ export function installWxDevOptions({
72
77
  sourcemap: false
73
78
  })
74
79
 
80
+ /*
81
+ * experimental belongs to this transaction-local Rolldown options generation. Materialize it only when absent, then
82
+ * replace devMode with a new object so user fields survive without mutating a possibly shared configured sub-object.
83
+ */
75
84
  rolldownOptions.experimental ??= {}
76
- rolldownOptions.experimental.devMode = createWxDevMode(
77
- rolldownOptions.experimental.devMode,
78
- await bundleRuntimeSource()
79
- )
80
-
81
- const reportInitialBuildPlugin: Plugin = {
82
- name: 'vpt:wx-report-initial-build',
83
- buildEnd: settleInitialBuild
85
+ const existingDevMode = rolldownOptions.experimental.devMode
86
+ rolldownOptions.experimental.devMode = {
87
+ ...(typeof existingDevMode === 'object' ? existingDevMode : {}),
88
+ implement: await bundleRuntimeSource(),
89
+ lazy: false,
90
+ skipCommonRuntimeInjection: false
84
91
  }
85
92
 
86
- // Preserve the physical paths watched by WeChat DevTools across host restarts; the complete build overwrites every
87
- // active output without disrupting the hot-reload watcher.
88
- rolldownOptions.plugins = [
89
- rolldownOptions.plugins,
90
- hostPlugins,
91
- reportInitialBuildPlugin,
92
- createViteReporter(server)
93
- ]
93
+ /*
94
+ * The plugin list is mutable configuration consumed once by this engine generation. Replace the top-level reference with
95
+ * an ordered composite rather than pushing into Vite's potentially shared nested array: existing transforms run first,
96
+ * host capture observes their final values, and the reporter observes final output without mutating either input list.
97
+ */
98
+ rolldownOptions.plugins = [rolldownOptions.plugins, hostPlugins, createViteReporter(server)]
94
99
  disableViteOxcSourcemap(rolldownOptions.plugins)
95
100
 
96
101
  return rolldownOptions
97
102
  }
98
-
99
- return initialBuild.promise
100
103
  }
101
104
 
102
105
  /** Returns the configured output after rejecting states unsupported by the physical WX engine. */
@@ -113,6 +116,7 @@ function ensureSingleOutput(rolldownOptions: BundledDevRolldownOptions): OutputO
113
116
  if (Array.isArray(rolldownOptions.output)) {
114
117
  throw new Error('wx development requires one configured Rolldown output.')
115
118
  }
119
+ // Rolldown needs the created object attached to input options by identity; returning a detached fallback would be ignored.
116
120
  rolldownOptions.output ??= {}
117
121
  return rolldownOptions.output
118
122
  }
@@ -164,6 +168,11 @@ function disableViteOxcSourcemap(pluginOption: unknown): void {
164
168
 
165
169
  const plugin = pluginOption as ViteTransformPlugin
166
170
  if (plugin.name === 'builtin:vite-transform' && plugin._options?.transformOptions) {
171
+ /*
172
+ * Vite's private builtin retains this mutable transform options object and does not expose a public replacement hook.
173
+ * Updating its one sourcemap flag prevents Oxc from allocating maps that final WX output always discards; cloning the
174
+ * descriptor would not update the builtin closure that reads the original object during transforms.
175
+ */
167
176
  plugin._options.transformOptions.sourcemap = false
168
177
  }
169
178
  }
@@ -182,7 +191,12 @@ function createViteReporter(server: ViteDevServer) {
182
191
  })
183
192
  }
184
193
 
185
- // The runtime source is immutable for the host's lifetime, so the nested bundle is shared by every complete build.
194
+ /*
195
+ * once owns a mutable cached Promise, justified because getRolldownOptions may run for multiple complete generations while the
196
+ * runtime input and build options remain immutable for the process lifetime. Sharing the in-flight/result Promise prevents
197
+ * concurrent options requests from launching duplicate nested builds; caching source instead of a Rolldown output object avoids
198
+ * leaking mutable bundle metadata between engines.
199
+ */
186
200
  const bundleRuntimeSource = once(async function bundleRuntimeSource(): Promise<string> {
187
201
  // write: false keeps this nested helper build from creating a second dist directory in the application project.
188
202
  const result = await build({
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path'
2
- import type { Rolldown } from 'vite'
2
+ import { normalizePath, type Rolldown } from 'vite'
3
3
  import { normalizeModuleId } from '../../utils/modules.ts'
4
4
  import { packageRequire, resolvePackageFile } from '../../utils/packages.ts'
5
5
 
@@ -7,9 +7,7 @@ import { packageRequire, resolvePackageFile } from '../../utils/packages.ts'
7
7
  export const rolldownRuntimeId = '\0rolldown/runtime.js'
8
8
 
9
9
  /** Resolves the direct React Reconciler dependency without assuming pnpm's layout. */
10
- export const reactReconcilerRoot = normalizeModuleId(
11
- path.dirname(packageRequire.resolve('react-reconciler/package.json'))
12
- )
10
+ export const reactReconcilerRoot = normalizePath(path.dirname(packageRequire.resolve('react-reconciler/package.json')))
13
11
 
14
12
  /** Identifies the amphibious bootstrap that initializes SystemJS and serves every native shell. */
15
13
  export const bootstrapPath = resolvePackageFile('dist/runtime/wx/amphibious/bootstrap.js')
@@ -1,4 +1,5 @@
1
1
  import path from 'node:path'
2
+ import { normalizePath } from 'vite'
2
3
  import { normalizeModuleId } from '../../../utils/modules.ts'
3
4
  import { collectNativeComponentAssets, nativeComponentMetaKey } from './native-component-assets.ts'
4
5
  import { transformNativeComponentInterfaces } from './native-component-interface.ts'
@@ -26,7 +27,7 @@ export async function compileNativeComponentInterface({
26
27
  addWatchFile(source.folder)
27
28
 
28
29
  source.assets.forEach((asset) => {
29
- addWatchFile(normalizeModuleId(path.join(source.folder, asset.relativePath)))
30
+ addWatchFile(normalizePath(path.join(source.folder, asset.relativePath)))
30
31
  })
31
32
  })
32
33
 
@@ -43,6 +43,8 @@ export async function createNativeComponentOutput({
43
43
  })
44
44
 
45
45
  for (const asset of source.assets) {
46
+ // Native folders are intentionally opaque to Rolldown, so no bundle asset contains these bytes to reuse.
47
+ // Read each planned source once here to transfer it into the compiler-owned output bundle.
46
48
  files.push({
47
49
  type: 'asset',
48
50
  fileName: path.posix.join(outputFolder, asset.relativePath),
@@ -1,7 +1,6 @@
1
1
  import { readdir, stat } from 'node:fs/promises'
2
2
  import path from 'node:path'
3
- import type { Rolldown } from 'vite'
4
- import { normalizeModuleId } from '../../../utils/modules.ts'
3
+ import { normalizePath, type Rolldown } from 'vite'
5
4
  import type { NativeComponentDefinition } from './native-component-interface.ts'
6
5
 
7
6
  type NativeComponentAsset = {
@@ -46,7 +45,7 @@ export async function collectNativeComponentAssets(
46
45
 
47
46
  // This mutable accumulator is local to one traversal and avoids repeatedly copying growing asset arrays.
48
47
  const assets: NativeComponentAsset[] = []
49
- await collectDirectory(definition.folder, [], assets, normalizeModuleId(interfacePath))
48
+ await collectDirectory(definition.folder, [], assets, normalizePath(interfacePath))
50
49
 
51
50
  return { ...definition, assets }
52
51
  }
@@ -103,7 +102,7 @@ async function collectDirectory(
103
102
  continue
104
103
  }
105
104
  const sourcePath = path.join(folder, ...entrySegments)
106
- if (normalizeModuleId(sourcePath) === interfacePath) {
105
+ if (normalizePath(sourcePath) === interfacePath) {
107
106
  continue
108
107
  }
109
108
  assets.push({
@@ -3,6 +3,7 @@ import type { ExistingRawSourceMap } from 'rolldown'
3
3
  import { RolldownMagicString } from 'rolldown'
4
4
  import { parseAst } from 'rolldown/parseAst'
5
5
  import type { ESTree } from 'rolldown/utils'
6
+ import { normalizePath } from 'vite'
6
7
  import { normalizeModuleId } from '../../../utils/modules.ts'
7
8
  import { clientTaroNativeId } from '../../client/constant.ts'
8
9
 
@@ -138,7 +139,7 @@ function parseDefinition(
138
139
  throw buildError('Native component entry must use () => import(...) with a static relative .js path')
139
140
  }
140
141
 
141
- const entryPath = normalizeModuleId(path.resolve(path.dirname(moduleId), entryReference))
142
+ const entryPath = normalizePath(path.resolve(path.dirname(moduleId), entryReference))
142
143
  return {
143
144
  folder: path.posix.dirname(entryPath),
144
145
  entry: path.posix.basename(entryPath, '.js'),
@@ -1,3 +1,4 @@
1
+ import { normalizePath } from 'vite'
1
2
  import type { VitePluginTaroOptions, VitePluginTaroPageOption } from '../../../../options.ts'
2
3
  import { normalizeModuleId, resolveAppComponentPath, resolvePageComponentPath } from '../../../utils/modules.ts'
3
4
  import { createAppConfig } from '../../../utils/project-config.ts'
@@ -25,8 +26,8 @@ type PrivateIdResolver = (importer: string | undefined, projectRoot: string) =>
25
26
 
26
27
  /** Creates the resolver and source specializer for the wx module graph. */
27
28
  export function createResolver(options: VitePluginTaroOptions) {
28
- const normalizedBootstrapPath = normalizeModuleId(bootstrapPath)
29
- const normalizedPageCapsulePath = normalizeModuleId(pageCapsulePath)
29
+ const normalizedBootstrapPath = normalizePath(bootstrapPath)
30
+ const normalizedPageCapsulePath = normalizePath(pageCapsulePath)
30
31
 
31
32
  // Construct output input and application traversal roots together once so style order cannot drift from route order.
32
33
  const entryGraph = createEntryGraph(options.pages)
@@ -48,6 +48,15 @@ export function createWxStylePlugins(): PluginOption[] {
48
48
  * remain opaque.
49
49
  */
50
50
  function createWxStyleFinalizer(transformStyle: typeof transformWxStyle): Plugin {
51
+ /*
52
+ * DevEngine can omit an unchanged stylesheet from later complete output generations. Absence therefore has two meanings:
53
+ * the first clean output genuinely has no styles, or a later output is reusing the physical stylesheet already on disk.
54
+ * The Vite server creates a fresh plugin instance on a clean start, so this one lifecycle bit distinguishes those cases and
55
+ * resets naturally on restart. Deliberately retain no CSS bytes or graph projection here: memory remains O(1), changed CSS
56
+ * still arrives as a normal asset, and the dev host remains the only owner of incremental style preparation.
57
+ */
58
+ let hasFinalizedOutput = false
59
+
51
60
  return {
52
61
  name: 'vpt:wx-style-finalizer',
53
62
  generateBundle: {
@@ -61,7 +70,17 @@ function createWxStyleFinalizer(transformStyle: typeof transformWxStyle): Plugin
61
70
  }
62
71
 
63
72
  if (styles.length === 0) {
64
- this.emitFile({ type: 'asset', fileName: 'assets/global.wxss', source: '' })
73
+ /*
74
+ * A clean first output must materialize the stable import target imported by app.wxss. On later complete
75
+ * outputs, DevEngine's physical writer preserves files omitted from the bundle; emitting the same empty
76
+ * placeholder would instead overwrite valid unchanged WXSS. Emit nothing in that later case. A source or
77
+ * configuration change that really removes all styles is already published as empty by ordinary style HMR,
78
+ * while a clean server restart reaches this first-output branch and also clears any stale prior file.
79
+ */
80
+ if (!hasFinalizedOutput) {
81
+ this.emitFile({ type: 'asset', fileName: 'assets/global.wxss', source: '' })
82
+ }
83
+ hasFinalizedOutput = true
65
84
  return
66
85
  }
67
86
 
@@ -73,6 +92,7 @@ function createWxStyleFinalizer(transformStyle: typeof transformWxStyle): Plugin
73
92
  // intact. Only its finalized contents and stable WXSS identity change.
74
93
  style.source = transformedResult.css
75
94
  style.fileName = 'assets/global.wxss'
95
+ hasFinalizedOutput = true
76
96
  }
77
97
  }
78
98
  }
@@ -1,4 +1,5 @@
1
1
  import path from 'node:path'
2
+ import { normalizePath } from 'vite'
2
3
 
3
4
  type AppComponentPathOptions = {
4
5
  appPath: string
@@ -33,12 +34,12 @@ export function createPageComponentImportPath(options: PageComponentPathOptions)
33
34
  return toViteFileImportPath(resolvePageComponentPath(options))
34
35
  }
35
36
 
36
- /** Normalizes a file-backed Vite module ID for stable comparisons. */
37
+ /** Converts a file-backed Vite request ID to its normalized physical path for stable comparisons. */
37
38
  export function normalizeModuleId(id: string): string {
38
- return id.replaceAll('\\', '/').replace(/\?.*$/, '')
39
+ return normalizePath(id.replace(/\?.*$/, ''))
39
40
  }
40
41
 
41
42
  /** Converts a local file path into Vite's portable file-system import form. */
42
43
  export function toViteFileImportPath(filePath: string): string {
43
- return `/@fs/${normalizeModuleId(path.resolve(filePath))}`
44
+ return `/@fs/${normalizePath(path.resolve(filePath))}`
44
45
  }
@@ -1,11 +1,11 @@
1
1
  import { createRequire } from 'node:module'
2
2
  import path from 'node:path'
3
- import { normalizeModuleId } from './modules.ts'
3
+ import { normalizePath } from 'vite'
4
4
 
5
5
  export const packageRequire = createRequire(import.meta.url)
6
6
  const packageRoot = path.dirname(packageRequire.resolve('vite-plugin-taro/package.json'))
7
7
 
8
8
  /** Resolves a file shipped by this package as a portable Vite module ID. */
9
9
  export function resolvePackageFile(...segments: string[]): string {
10
- return normalizeModuleId(path.join(packageRoot, ...segments))
10
+ return normalizePath(path.join(packageRoot, ...segments))
11
11
  }
@@ -1,6 +0,0 @@
1
- import type { InputOptions } from 'rolldown';
2
- type DevMode = NonNullable<NonNullable<InputOptions['experimental']>['devMode']>;
3
- type DevModeOptions = Exclude<DevMode, boolean>;
4
- /** Installs the custom WX runtime while restoring Rolldown's common runtime base. */
5
- export declare function createWxDevMode(devMode: DevMode | undefined, implement: string): DevModeOptions;
6
- export {};
@@ -1,9 +0,0 @@
1
- /** Installs the custom WX runtime while restoring Rolldown's common runtime base. */
2
- export function createWxDevMode(devMode, implement) {
3
- return {
4
- ...(typeof devMode === 'object' ? devMode : {}),
5
- implement,
6
- lazy: false,
7
- skipCommonRuntimeInjection: false
8
- };
9
- }
@@ -1,12 +0,0 @@
1
- import type { GetModuleInfo, Plugin } from 'rolldown';
2
- export type ProcessedStyle = Readonly<{
3
- css: string;
4
- isTailwindRoot: boolean;
5
- }>;
6
- type StyleCaptureOptions = Readonly<{
7
- captureGraph: (getModuleInfo: GetModuleInfo) => void;
8
- captureStyle: (id: string, style: ProcessedStyle) => void;
9
- }>;
10
- /** Captures Vite's final development CSS and Rolldown's live graph without running the style pipeline twice. */
11
- export declare function createStyleCapturePlugin({ captureGraph, captureStyle }: StyleCaptureOptions): Plugin;
12
- export {};
@@ -1,21 +0,0 @@
1
- import { normalizeModuleId } from '../../../../utils/modules.js';
2
- import { extractViteCss, isGlobalStyleRequest } from '../../styles/utils.js';
3
- /** Captures Vite's final development CSS and Rolldown's live graph without running the style pipeline twice. */
4
- export function createStyleCapturePlugin({ captureGraph, captureStyle }) {
5
- return {
6
- name: 'vpt:wx-dev-style-capture',
7
- buildStart() {
8
- captureGraph((moduleId) => this.getModuleInfo(moduleId));
9
- },
10
- transform(code, id) {
11
- if (!isGlobalStyleRequest(id)) {
12
- return;
13
- }
14
- const css = extractViteCss(code, id);
15
- captureStyle(normalizeModuleId(id), {
16
- css: css,
17
- isTailwindRoot: css.includes('weapp-tailwindcss vite-generated-css:')
18
- });
19
- }
20
- };
21
- }
@@ -1,27 +0,0 @@
1
- import type { ProcessedStyle } from './create-style-capture-plugin.ts';
2
- export declare const globalWxssFileName = "assets/global.wxss";
3
- type StyleHmrOptions = Readonly<{
4
- styleIds: readonly string[];
5
- outDir: string;
6
- processedStyles: ReadonlyMap<string, ProcessedStyle>;
7
- publishedWxss: string | undefined;
8
- }>;
9
- /** Regenerates reachable Tailwind roots concurrently from upstream's current candidate state. */
10
- export declare function refreshTailwindStyles(styleIds: readonly string[], processedStyles: Map<string, ProcessedStyle>, transformRoot: (rootId: string, requestId: string) => Promise<Readonly<{
11
- code: string;
12
- }> | null>): Promise<void>;
13
- /**
14
- * Materializes one host-owned style snapshot for a completed HMR transaction.
15
- *
16
- * Rolldown owns topology but does not retain Vite's final CSS module payload, while `processedStyles` owns those payloads but
17
- * deliberately retains no topology. The caller supplies the transaction's one ordered graph plan; both Tailwind refresh and
18
- * this renderer consume that same snapshot, so sidecar generation cannot cause a second topology traversal.
19
- *
20
- * Ordinary CSS is never transformed again here: capture already observed it after Vite/PostCSS. The composed stylesheet alone
21
- * receives the same final WX conversion as complete builds. Byte equality with the host's physical publication frontier skips
22
- * an atomic rename that would otherwise notify DevTools despite no style change. The returned bytes advance that frontier only
23
- * after any required write succeeds. The caller awaits this before exposing the corresponding JavaScript patch.
24
- * Complexity is O(styles + CSS bytes), after the caller's O(V + E) plan traversal.
25
- */
26
- export declare function publishStyleHmr({ styleIds, outDir, processedStyles, publishedWxss }: StyleHmrOptions): Promise<string>;
27
- export {};
@@ -1,48 +0,0 @@
1
- import { transformWxStyle } from '../../styles/transform-wx-style.js';
2
- import { composeGraphStyleCss, createTailwindSidecarId, extractViteCss } from '../../styles/utils.js';
3
- import { writeHmrFile } from '../hmr-files.js';
4
- export const globalWxssFileName = 'assets/global.wxss';
5
- /** Regenerates reachable Tailwind roots concurrently from upstream's current candidate state. */
6
- export async function refreshTailwindStyles(styleIds, processedStyles, transformRoot) {
7
- const roots = styleIds.filter((styleId) => requireProcessedStyle(processedStyles, styleId).isTailwindRoot);
8
- const refreshedStyles = await Promise.all(roots.map(async (rootId) => {
9
- const requestId = createTailwindSidecarId(rootId);
10
- const result = await transformRoot(rootId, requestId);
11
- if (!result) {
12
- throw new Error(`Tailwind sidecar transform produced no result: ${requestId}`);
13
- }
14
- return [rootId, { css: extractViteCss(result.code, requestId), isTailwindRoot: true }];
15
- }));
16
- // Commit the cache generation only after every independent root succeeds; a failed sibling cannot leave a mixed snapshot.
17
- refreshedStyles.forEach(([rootId, style]) => {
18
- processedStyles.set(rootId, style);
19
- });
20
- }
21
- /**
22
- * Materializes one host-owned style snapshot for a completed HMR transaction.
23
- *
24
- * Rolldown owns topology but does not retain Vite's final CSS module payload, while `processedStyles` owns those payloads but
25
- * deliberately retains no topology. The caller supplies the transaction's one ordered graph plan; both Tailwind refresh and
26
- * this renderer consume that same snapshot, so sidecar generation cannot cause a second topology traversal.
27
- *
28
- * Ordinary CSS is never transformed again here: capture already observed it after Vite/PostCSS. The composed stylesheet alone
29
- * receives the same final WX conversion as complete builds. Byte equality with the host's physical publication frontier skips
30
- * an atomic rename that would otherwise notify DevTools despite no style change. The returned bytes advance that frontier only
31
- * after any required write succeeds. The caller awaits this before exposing the corresponding JavaScript patch.
32
- * Complexity is O(styles + CSS bytes), after the caller's O(V + E) plan traversal.
33
- */
34
- export async function publishStyleHmr({ styleIds, outDir, processedStyles, publishedWxss }) {
35
- const css = composeGraphStyleCss(styleIds, (styleId) => requireProcessedStyle(processedStyles, styleId).css);
36
- const wxss = (await transformWxStyle(css)).css;
37
- if (wxss !== publishedWxss) {
38
- await writeHmrFile(outDir, globalWxssFileName, wxss);
39
- }
40
- return wxss;
41
- }
42
- function requireProcessedStyle(processedStyles, styleId) {
43
- const style = processedStyles.get(styleId);
44
- if (!style) {
45
- throw new Error(`WX style plan references uncaptured CSS: ${styleId}`);
46
- }
47
- return style;
48
- }
@@ -1,13 +0,0 @@
1
- /** Runs asynchronous tasks in insertion order without letting one failure block later work. */
2
- export declare class SerializedTaskQueue {
3
- /** Mutable promise tail confines ordering state to this queue. */
4
- private tail;
5
- private readonly reportError;
6
- constructor(reportError: (operation: string, error: unknown) => void);
7
- /** Schedules a background task and reports its failure. */
8
- enqueue(operation: string, task: () => void | PromiseLike<void>): void;
9
- /** Schedules a task whose result and failure belong to the caller. */
10
- run<Result>(task: () => Result | PromiseLike<Result>): Promise<Result>;
11
- /** Waits for every task that was queued when this method was called. */
12
- waitForIdle(): Promise<void>;
13
- }