vite-plugin-taro 0.2.2 → 0.3.0

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 (133) hide show
  1. package/README.md +83 -106
  2. package/README.zh.md +83 -104
  3. package/dist/node/build-context.d.ts +25 -0
  4. package/dist/node/build-context.js +48 -0
  5. package/dist/node/css/css-pipeline.d.ts +21 -0
  6. package/dist/node/css/css-pipeline.js +129 -0
  7. package/dist/node/plugins/conditional-directives.d.ts +3 -0
  8. package/dist/node/plugins/conditional-directives.js +62 -0
  9. package/dist/node/plugins/taro-runtime.d.ts +5 -0
  10. package/dist/node/plugins/taro-runtime.js +18 -0
  11. package/dist/node/targets/h5/plugin.d.ts +6 -0
  12. package/dist/node/targets/h5/plugin.js +117 -0
  13. package/dist/node/targets/h5/virtual-modules.d.ts +5 -0
  14. package/dist/node/targets/h5/virtual-modules.js +60 -0
  15. package/dist/node/targets/wx/companion-assets.d.ts +18 -0
  16. package/dist/node/targets/wx/companion-assets.js +121 -0
  17. package/dist/node/targets/wx/dev-server/development-session.d.ts +31 -0
  18. package/dist/node/targets/wx/dev-server/development-session.js +202 -0
  19. package/dist/node/targets/wx/dev-server/full-build-scheduler.d.ts +14 -0
  20. package/dist/node/targets/wx/dev-server/full-build-scheduler.js +60 -0
  21. package/dist/node/targets/wx/dev-server/js-utils.d.ts +5 -0
  22. package/dist/node/targets/wx/dev-server/js-utils.js +28 -0
  23. package/dist/node/targets/wx/dev-server/output.d.ts +19 -0
  24. package/dist/node/targets/wx/dev-server/output.js +44 -0
  25. package/dist/node/targets/wx/dev-server/rolldown-runtime-source.d.ts +8 -0
  26. package/dist/node/targets/wx/dev-server/rolldown-runtime-source.js +95 -0
  27. package/dist/node/targets/wx/dev-server/update-server-state.d.ts +65 -0
  28. package/dist/node/targets/wx/dev-server/update-server-state.js +116 -0
  29. package/dist/node/targets/wx/dev-server/update-transport.d.ts +28 -0
  30. package/dist/node/targets/wx/dev-server/update-transport.js +212 -0
  31. package/dist/node/targets/wx/dev-server/vite-bundled-dev-adapter.d.ts +44 -0
  32. package/dist/node/targets/wx/dev-server/vite-bundled-dev-adapter.js +145 -0
  33. package/dist/node/targets/wx/development-files.d.ts +8 -0
  34. package/dist/node/targets/wx/development-files.js +8 -0
  35. package/dist/node/targets/wx/plugin.d.ts +6 -0
  36. package/dist/node/targets/wx/plugin.js +143 -0
  37. package/dist/node/targets/wx/react-refresh.d.ts +4 -0
  38. package/dist/node/targets/wx/react-refresh.js +38 -0
  39. package/dist/node/targets/wx/virtual-modules.d.ts +16 -0
  40. package/dist/node/targets/wx/virtual-modules.js +120 -0
  41. package/dist/node/utils/async.d.ts +8 -0
  42. package/dist/node/utils/async.js +16 -0
  43. package/dist/node/utils/filesystem.d.ts +13 -0
  44. package/dist/node/utils/filesystem.js +43 -0
  45. package/dist/{vite/utils.d.ts → node/utils/modules.d.ts} +2 -2
  46. package/dist/{vite/utils.js → node/utils/modules.js} +10 -3
  47. package/dist/node/utils/packages.d.ts +2 -0
  48. package/dist/node/utils/packages.js +8 -0
  49. package/dist/node/vite-plugin.d.ts +4 -0
  50. package/dist/node/vite-plugin.js +33 -0
  51. package/dist/options.d.ts +21 -0
  52. package/dist/{shim/h5.d.ts → runtime/h5/taro-runtime.d.ts} +3 -1
  53. package/dist/runtime/h5/taro-runtime.js +9 -0
  54. package/dist/runtime/taro/api.d.ts +5 -0
  55. package/dist/{virtual → runtime/taro}/api.js +3 -0
  56. package/dist/runtime/taro/components.d.ts +2 -0
  57. package/dist/runtime/taro/components.js +2 -0
  58. package/dist/runtime/wx/page-update.d.ts +6 -0
  59. package/dist/runtime/wx/page-update.js +157 -0
  60. package/dist/{shim/wx.d.ts → runtime/wx/taro-runtime.d.ts} +1 -0
  61. package/dist/{shim/wx.js → runtime/wx/taro-runtime.js} +1 -0
  62. package/dist/runtime/wx/update-client-state.d.ts +68 -0
  63. package/dist/runtime/wx/update-client-state.js +84 -0
  64. package/dist/runtime/wx/update-client.d.ts +1 -0
  65. package/dist/runtime/wx/update-client.js +170 -0
  66. package/dist/vite.d.ts +2 -2
  67. package/dist/vite.js +1 -1
  68. package/package.json +15 -11
  69. package/src/node/build-context.ts +63 -0
  70. package/src/node/css/css-pipeline.ts +147 -0
  71. package/src/node/plugins/conditional-directives.ts +76 -0
  72. package/src/node/plugins/taro-runtime.ts +22 -0
  73. package/src/node/targets/h5/plugin.ts +135 -0
  74. package/src/node/targets/h5/virtual-modules.ts +71 -0
  75. package/src/node/targets/wx/companion-assets.ts +160 -0
  76. package/src/node/targets/wx/dev-server/development-session.ts +253 -0
  77. package/src/node/targets/wx/dev-server/full-build-scheduler.ts +62 -0
  78. package/src/node/targets/wx/dev-server/js-utils.ts +33 -0
  79. package/src/node/targets/wx/dev-server/output.ts +56 -0
  80. package/src/node/targets/wx/dev-server/rolldown-runtime-source.ts +95 -0
  81. package/src/node/targets/wx/dev-server/update-server-state.ts +183 -0
  82. package/src/node/targets/wx/dev-server/update-transport.ts +261 -0
  83. package/src/node/targets/wx/dev-server/vite-bundled-dev-adapter.ts +203 -0
  84. package/src/node/targets/wx/development-files.ts +11 -0
  85. package/src/node/targets/wx/plugin.ts +163 -0
  86. package/src/node/targets/wx/react-refresh.ts +51 -0
  87. package/src/node/targets/wx/virtual-modules.ts +152 -0
  88. package/src/node/utils/async.ts +19 -0
  89. package/src/node/utils/filesystem.ts +45 -0
  90. package/src/{vite/utils.ts → node/utils/modules.ts} +11 -3
  91. package/src/node/utils/packages.ts +10 -0
  92. package/src/node/vite-plugin.ts +41 -0
  93. package/src/options.ts +25 -0
  94. package/src/runtime/h5/taro-css.d.ts +3 -0
  95. package/src/runtime/h5/taro-runtime.ts +10 -0
  96. package/src/{virtual → runtime/taro}/api.ts +3 -0
  97. package/src/runtime/taro/components.ts +2 -0
  98. package/src/runtime/wx/page-update.ts +230 -0
  99. package/src/runtime/wx/react-refresh.d.ts +20 -0
  100. package/src/{shim/wx.ts → runtime/wx/taro-runtime.ts} +1 -0
  101. package/src/runtime/wx/update-client-state.ts +138 -0
  102. package/src/runtime/wx/update-client.ts +222 -0
  103. package/src/vite.ts +2 -2
  104. package/dist/shim/h5.js +0 -5
  105. package/dist/virtual/api.d.ts +0 -3
  106. package/dist/virtual/components.d.ts +0 -1
  107. package/dist/virtual/components.js +0 -1
  108. package/dist/vite/constants.d.ts +0 -4
  109. package/dist/vite/constants.js +0 -8
  110. package/dist/vite/plugins.d.ts +0 -8
  111. package/dist/vite/plugins.js +0 -186
  112. package/dist/vite/tailwindcss.d.ts +0 -3
  113. package/dist/vite/tailwindcss.js +0 -35
  114. package/dist/vite/targets/h5.d.ts +0 -31
  115. package/dist/vite/targets/h5.js +0 -201
  116. package/dist/vite/targets/wx.d.ts +0 -74
  117. package/dist/vite/targets/wx.js +0 -362
  118. package/dist/vite/types.d.ts +0 -38
  119. package/dist/vite/virtual-modules.d.ts +0 -3
  120. package/dist/vite/virtual-modules.js +0 -13
  121. package/dist/vite/vite-plugin-taro.d.ts +0 -7
  122. package/dist/vite/vite-plugin-taro.js +0 -80
  123. package/src/shim/h5.ts +0 -6
  124. package/src/virtual/components.ts +0 -1
  125. package/src/vite/constants.ts +0 -12
  126. package/src/vite/plugins.ts +0 -218
  127. package/src/vite/tailwindcss.ts +0 -41
  128. package/src/vite/targets/h5.ts +0 -229
  129. package/src/vite/targets/wx.ts +0 -436
  130. package/src/vite/types.ts +0 -48
  131. package/src/vite/virtual-modules.ts +0 -14
  132. package/src/vite/vite-plugin-taro.ts +0 -106
  133. /package/dist/{vite/types.js → options.js} +0 -0
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Coordinates page-side update execution after the Rolldown and Taro runtimes exist.
3
+ *
4
+ * This module is intentionally page-only: it suppresses DevTools' synthetic lifecycles, preserves the Taro root,
5
+ * commits React Refresh, and relaunches stale component families. HTTP transport remains in update-client.ts.
6
+ */
7
+ import { document } from '@tarojs/runtime'
8
+ import { validateRefreshBoundaryAndEnqueueUpdate } from '/@react-refresh'
9
+
10
+ type WxPage = {
11
+ $taroPath?: string
12
+ $taroParams?: Record<string, unknown>
13
+ }
14
+
15
+ type WxPageConfig = Record<string, unknown>
16
+ type PageLifecycle = (this: WxPage, ...args: unknown[]) => unknown
17
+
18
+ type TaroRoot = {
19
+ ctx: WxPage | null
20
+ updateChildNodes(): void
21
+ performUpdate(initRender?: boolean): void
22
+ }
23
+
24
+ type ReactRefreshResult = {
25
+ staleFamilies?: Set<unknown>
26
+ }
27
+
28
+ type WxRolldownRuntime = {
29
+ beginPatch?(): void
30
+ endPatch?(): void
31
+ }
32
+
33
+ type WxPageUpdateCoordinator = {
34
+ ready: boolean
35
+ pendingUpdate?: () => void
36
+ blockRefreshRegistration?: boolean
37
+ enqueueRefresh?: () => void
38
+ beginUpdate?: () => void
39
+ endUpdate?: () => void
40
+ afterRefresh?: (update?: ReactRefreshResult) => void
41
+ }
42
+
43
+ type WxUpdateClient = {
44
+ refreshCompleted(stale: boolean): void
45
+ routeReady(): void
46
+ }
47
+
48
+ type WxRuntimeGlobal = typeof globalThis & {
49
+ __rolldown_runtime__?: WxRolldownRuntime
50
+ __VITE_PLUGIN_TARO_WX_PAGE_UPDATE__?: WxPageUpdateCoordinator
51
+ __VITE_PLUGIN_TARO_WX_UPDATE_CLIENT__?: WxUpdateClient
52
+ getCurrentPages(): Array<{ route?: string }>
53
+ wx: {
54
+ reLaunch(options: { url: string }): void
55
+ }
56
+ }
57
+
58
+ const wxRuntimeGlobal = globalThis as WxRuntimeGlobal
59
+ wxRuntimeGlobal.__VITE_PLUGIN_TARO_WX_PAGE_UPDATE__ ??= { ready: false }
60
+ const bridge = wxRuntimeGlobal.__VITE_PLUGIN_TARO_WX_PAGE_UPDATE__
61
+ const refreshBoundary = { default: function WxRefreshBoundary() {} }
62
+ const registeredRoutes = new Set<string>()
63
+ const ignoredPages = new WeakSet<WxPage>()
64
+
65
+ type PageRuntimeState = Readonly<{
66
+ activePage?: WxPage
67
+ pendingUpdate?: { page?: WxPage; root?: TaroRoot }
68
+ suppressLifecycles: boolean
69
+ }>
70
+
71
+ type PageRuntimeEvent =
72
+ | { type: 'page-activated'; page: WxPage }
73
+ | { type: 'page-unloaded'; page: WxPage }
74
+ | { type: 'update-started'; root?: TaroRoot }
75
+ | { type: 'suppression-ended' }
76
+ | { type: 'refresh-finished' }
77
+
78
+ let pageRuntimeState: PageRuntimeState = { suppressLifecycles: false }
79
+
80
+ /** Registers a native route once per App Service runtime generation. */
81
+ export function registerWxPage(route: string, register: () => void): void {
82
+ if (registeredRoutes.has(route)) return
83
+ register()
84
+ registeredRoutes.add(route)
85
+ }
86
+
87
+ /** Retains Taro's live root while filtering DevTools' synthetic update lifecycles. */
88
+ export function decorateWxPageConfig(config: WxPageConfig): WxPageConfig {
89
+ const onLoad = getLifecycle(config, 'onLoad')
90
+ config.onLoad = function (this: WxPage, ...args: unknown[]) {
91
+ if (pageRuntimeState.suppressLifecycles) {
92
+ ignoredPages.add(this)
93
+ this.$taroPath = pageRuntimeState.activePage?.$taroPath
94
+ this.$taroParams = pageRuntimeState.activePage?.$taroParams
95
+ dispatchPageRuntime({ type: 'page-activated', page: this })
96
+ return
97
+ }
98
+ const result = onLoad?.apply(this, args)
99
+ dispatchPageRuntime({ type: 'page-activated', page: this })
100
+ return result
101
+ }
102
+ wrapLifecycle(config, 'onReady', applyPendingPageUpdate)
103
+ wrapLifecycle(config, 'onShow', function () {
104
+ dispatchPageRuntime({ type: 'page-activated', page: this })
105
+ })
106
+ wrapLifecycle(config, 'onHide')
107
+ wrapLifecycle(config, 'onUnload', function () {
108
+ dispatchPageRuntime({ type: 'page-unloaded', page: this })
109
+ })
110
+ return config
111
+ }
112
+
113
+ function wrapLifecycle(config: WxPageConfig, name: string, after?: PageLifecycle): void {
114
+ const original = getLifecycle(config, name)
115
+ config[name] = function (this: WxPage, ...args: unknown[]) {
116
+ if (pageRuntimeState.suppressLifecycles) return
117
+ if (ignoredPages.has(this)) {
118
+ if (name === 'onUnload') ignoredPages.delete(this)
119
+ return
120
+ }
121
+ const result = original?.apply(this, args)
122
+ after?.apply(this, args)
123
+ return result
124
+ }
125
+ }
126
+
127
+ function getLifecycle(config: WxPageConfig, name: string): PageLifecycle | undefined {
128
+ return typeof config[name] === 'function' ? (config[name] as PageLifecycle) : undefined
129
+ }
130
+
131
+ function applyPendingPageUpdate(): void {
132
+ bridge.ready = true
133
+ const pendingUpdate = bridge.pendingUpdate
134
+ if (pendingUpdate) {
135
+ delete bridge.pendingUpdate
136
+ pendingUpdate()
137
+ }
138
+ wxRuntimeGlobal.__VITE_PLUGIN_TARO_WX_UPDATE_CLIENT__?.routeReady()
139
+ }
140
+
141
+ function getActiveTaroRoot(): TaroRoot | undefined {
142
+ const page = pageRuntimeState.activePage
143
+ if (!page?.$taroPath) return
144
+ return (document.getElementById(page.$taroPath) as TaroRoot | null) ?? undefined
145
+ }
146
+
147
+ bridge.enqueueRefresh = () => {
148
+ validateRefreshBoundaryAndEnqueueUpdate('vite-plugin-taro-wx', refreshBoundary, refreshBoundary)
149
+ }
150
+
151
+ bridge.beginUpdate = () => {
152
+ bridge.blockRefreshRegistration = false
153
+ wxRuntimeGlobal.__rolldown_runtime__?.beginPatch?.()
154
+ dispatchPageRuntime({ type: 'update-started', root: getActiveTaroRoot() })
155
+ refreshTaroRoot(pageRuntimeState.pendingUpdate?.root, pageRuntimeState.pendingUpdate?.page)
156
+ runInNextNativeTask(() => dispatchPageRuntime({ type: 'suppression-ended' }))
157
+ }
158
+
159
+ bridge.endUpdate = () => {
160
+ wxRuntimeGlobal.__rolldown_runtime__?.endPatch?.()
161
+ bridge.blockRefreshRegistration = true
162
+ bridge.enqueueRefresh?.()
163
+ }
164
+
165
+ bridge.afterRefresh = (update) => {
166
+ bridge.blockRefreshRegistration = false
167
+ const pendingUpdate = pageRuntimeState.pendingUpdate
168
+ dispatchPageRuntime({ type: 'refresh-finished' })
169
+ const page = pendingUpdate?.page
170
+ const root = pendingUpdate?.root
171
+
172
+ const stale = Boolean(update?.staleFamilies?.size)
173
+ if (stale) {
174
+ wxRuntimeGlobal.__VITE_PLUGIN_TARO_WX_UPDATE_CLIENT__?.refreshCompleted(true)
175
+ relaunchActiveRoute(page)
176
+ return
177
+ }
178
+ runInNextNativeTask(() => {
179
+ refreshTaroRoot(root, pageRuntimeState.activePage ?? page)
180
+ wxRuntimeGlobal.__VITE_PLUGIN_TARO_WX_UPDATE_CLIENT__?.refreshCompleted(false)
181
+ })
182
+ }
183
+
184
+ function dispatchPageRuntime(event: PageRuntimeEvent): void {
185
+ pageRuntimeState = transitionPageRuntime(pageRuntimeState, event)
186
+ }
187
+
188
+ function transitionPageRuntime(state: PageRuntimeState, event: PageRuntimeEvent): PageRuntimeState {
189
+ switch (event.type) {
190
+ case 'page-activated':
191
+ return { ...state, activePage: event.page }
192
+ case 'page-unloaded':
193
+ return state.activePage === event.page ? { ...state, activePage: undefined } : state
194
+ case 'update-started':
195
+ return {
196
+ ...state,
197
+ pendingUpdate: { page: state.activePage, root: event.root },
198
+ suppressLifecycles: true
199
+ }
200
+ case 'suppression-ended':
201
+ return { ...state, suppressLifecycles: false }
202
+ case 'refresh-finished':
203
+ return { ...state, pendingUpdate: undefined }
204
+ }
205
+ }
206
+
207
+ function refreshTaroRoot(root: TaroRoot | undefined, page: WxPage | undefined): void {
208
+ if (!root) return
209
+ root.ctx = page ?? null
210
+ root.updateChildNodes()
211
+ root.performUpdate(true)
212
+ }
213
+
214
+ function relaunchActiveRoute(page: WxPage | undefined): void {
215
+ const route = wxRuntimeGlobal.getCurrentPages().at(-1)?.route
216
+ if (!route) return
217
+ const query = Object.entries(page?.$taroParams ?? {})
218
+ .filter(([key, value]) => key !== '$taroTimestamp' && value !== undefined)
219
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
220
+ .join('&')
221
+ runInNextNativeTask(() => wxRuntimeGlobal.wx.reLaunch({ url: `/${route}${query ? `?${query}` : ''}` }))
222
+ }
223
+
224
+ /**
225
+ * DevTools dispatches synthetic page lifecycles after the changed module returns. A macrotask—not a microtask—runs
226
+ * after that native turn and also avoids navigation or Taro root mutation from inside React Refresh's callback.
227
+ */
228
+ function runInNextNativeTask(task: () => void): void {
229
+ setTimeout(task)
230
+ }
@@ -0,0 +1,20 @@
1
+ /** Vite's development-only React Refresh virtual module used by the WX runtime. */
2
+ declare module '*/@react-refresh' {
3
+ export type RefreshResult = {
4
+ updatedFamilies: Set<unknown>
5
+ staleFamilies: Set<unknown>
6
+ }
7
+
8
+ export function register(type: unknown, id: string): void
9
+ export function registerExportsForReactRefresh(filename: string, moduleExports: Record<string, unknown>): void
10
+ export function validateRefreshBoundaryAndEnqueueUpdate(
11
+ id: string,
12
+ previousExports: Record<string, unknown>,
13
+ nextExports: Record<string, unknown>
14
+ ): string | undefined
15
+
16
+ const runtime: {
17
+ injectIntoGlobalHook(globalObject: typeof globalThis): void
18
+ }
19
+ export default runtime
20
+ }
@@ -1,3 +1,4 @@
1
+ /** Runtime exports consumed by generated WX App, page, and recursive-component entries. */
1
2
  import '@tarojs/plugin-platform-weapp/dist/runtime.js'
2
3
 
3
4
  // @ts-expect-error Taro exposes createReactApp from this runtime-only deep entry without types.
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Pure App-side stop-and-wait protocol state machine.
3
+ *
4
+ * It knows build versions and sequencing only; wx.request, literal code execution, and page lifecycle effects live in
5
+ * update-client.ts and page-update.ts.
6
+ */
7
+ type WxUpdateClientPhase = 'stopped' | 'registering' | 'polling' | 'applying' | 'refreshing' | 'relaunching'
8
+
9
+ type WxUpdateClientState = {
10
+ buildId: string
11
+ version: number
12
+ phase: WxUpdateClientPhase
13
+ targetVersion?: number
14
+ pendingBatch?: { fromVersion: number; targetVersion: number }
15
+ }
16
+
17
+ export type WxUpdateClientEvent =
18
+ | { type: 'started' }
19
+ | { type: 'registration-completed' }
20
+ | { type: 'poll-completed' }
21
+ | { type: 'batch-observed'; buildId: string; fromVersion: number; targetVersion: number }
22
+ | { type: 'batch-executed'; targetVersion: number }
23
+ | { type: 'batch-failed' }
24
+ | { type: 'refresh-completed'; stale: boolean }
25
+ | { type: 'route-ready' }
26
+ | { type: 'transport-failed' }
27
+
28
+ export type WxUpdateClientCommand =
29
+ | { type: 'register'; version: number }
30
+ | { type: 'poll'; version: number }
31
+ | { type: 'apply-batch'; fromVersion: number; targetVersion: number }
32
+ | { type: 'report-version'; version: number; reason: 'applied' | 'batch-mismatch' }
33
+ | { type: 'request-full-build'; reason: 'batch-execution-failed' }
34
+ | { type: 'retry-transport' }
35
+
36
+ type WxUpdateClientTransition = {
37
+ state: WxUpdateClientState
38
+ commands: WxUpdateClientCommand[]
39
+ }
40
+
41
+ export function createWxUpdateClientState(buildId: string): WxUpdateClientState {
42
+ return { buildId, version: 0, phase: 'stopped' }
43
+ }
44
+
45
+ export function transitionWxUpdateClient(
46
+ state: WxUpdateClientState,
47
+ event: WxUpdateClientEvent
48
+ ): WxUpdateClientTransition {
49
+ switch (event.type) {
50
+ case 'started':
51
+ if (state.phase !== 'stopped') return transition(state)
52
+ return command({ ...state, phase: 'registering' }, { type: 'register', version: state.version })
53
+ case 'registration-completed': {
54
+ if (state.phase !== 'registering') return transition(state)
55
+ const pending = state.pendingBatch
56
+ if (!pending) return command({ ...state, phase: 'polling' }, { type: 'poll', version: state.version })
57
+ return command(
58
+ { ...state, phase: 'applying', targetVersion: pending.targetVersion, pendingBatch: undefined },
59
+ { type: 'apply-batch', ...pending }
60
+ )
61
+ }
62
+ case 'poll-completed':
63
+ if (state.phase !== 'polling') return transition(state)
64
+ return command(state, { type: 'poll', version: state.version })
65
+ case 'batch-observed':
66
+ return observeBatch(state, event)
67
+ case 'batch-executed':
68
+ if (state.phase !== 'applying' || state.targetVersion !== event.targetVersion) {
69
+ return command(state, { type: 'report-version', version: state.version, reason: 'batch-mismatch' })
70
+ }
71
+ return transition({ ...state, phase: 'refreshing', version: event.targetVersion })
72
+ case 'batch-failed':
73
+ return command(
74
+ { ...state, phase: 'polling', targetVersion: undefined },
75
+ { type: 'request-full-build', reason: 'batch-execution-failed' }
76
+ )
77
+ case 'refresh-completed':
78
+ if (state.phase !== 'refreshing') return transition(state)
79
+ if (event.stale) return transition({ ...state, phase: 'relaunching' })
80
+ return applied(state)
81
+ case 'route-ready':
82
+ if (state.phase !== 'relaunching') return transition(state)
83
+ return applied(state)
84
+ case 'transport-failed':
85
+ return command(state, { type: 'retry-transport' })
86
+ }
87
+ }
88
+
89
+ function observeBatch(
90
+ state: WxUpdateClientState,
91
+ batch: { buildId: string; fromVersion: number; targetVersion: number }
92
+ ): WxUpdateClientTransition {
93
+ const valid =
94
+ batch.buildId === state.buildId &&
95
+ Number.isInteger(batch.fromVersion) &&
96
+ Number.isInteger(batch.targetVersion) &&
97
+ batch.fromVersion >= 0 &&
98
+ batch.targetVersion > batch.fromVersion
99
+ if (state.phase === 'registering') {
100
+ if (!valid || batch.fromVersion !== state.version || batch.targetVersion <= state.version) {
101
+ return transition(state)
102
+ }
103
+ return transition({
104
+ ...state,
105
+ pendingBatch: { fromVersion: batch.fromVersion, targetVersion: batch.targetVersion }
106
+ })
107
+ }
108
+ if (!valid) {
109
+ return command(state, { type: 'report-version', version: state.version, reason: 'batch-mismatch' })
110
+ }
111
+ if (batch.targetVersion <= state.version) {
112
+ return command(state, { type: 'report-version', version: state.version, reason: 'applied' })
113
+ }
114
+ if (state.phase !== 'polling' || batch.fromVersion !== state.version) {
115
+ return command(state, { type: 'report-version', version: state.version, reason: 'batch-mismatch' })
116
+ }
117
+ return command(
118
+ { ...state, phase: 'applying', targetVersion: batch.targetVersion },
119
+ {
120
+ type: 'apply-batch',
121
+ fromVersion: batch.fromVersion,
122
+ targetVersion: batch.targetVersion
123
+ }
124
+ )
125
+ }
126
+
127
+ function applied(state: WxUpdateClientState): WxUpdateClientTransition {
128
+ const appliedState = { ...state, phase: 'polling' as const, targetVersion: undefined }
129
+ return command(appliedState, { type: 'report-version', version: state.version, reason: 'applied' })
130
+ }
131
+
132
+ function transition(state: WxUpdateClientState): WxUpdateClientTransition {
133
+ return { state, commands: [] }
134
+ }
135
+
136
+ function command(state: WxUpdateClientState, value: WxUpdateClientCommand): WxUpdateClientTransition {
137
+ return { state, commands: [value] }
138
+ }
@@ -0,0 +1,222 @@
1
+ /**
2
+ * App-owned adapter for the WX update protocol.
3
+ *
4
+ * It transports metadata with wx.request and executes literal batches delivered through update.js. React/Taro page
5
+ * preservation is delegated to page-update.ts so the App never depends on the mutable update file.
6
+ */
7
+ import {
8
+ createWxUpdateClientState,
9
+ transitionWxUpdateClient,
10
+ type WxUpdateClientCommand,
11
+ type WxUpdateClientEvent
12
+ } from './update-client-state.ts'
13
+
14
+ type WxUpdateControl = {
15
+ url: string
16
+ token: string
17
+ buildId: string
18
+ }
19
+
20
+ type WxUpdateBatchMetadata = {
21
+ buildId: string
22
+ fromVersion: number
23
+ targetVersion: number
24
+ }
25
+
26
+ type WxRequestTask = {
27
+ abort(): void
28
+ }
29
+
30
+ type WxRequestResult = {
31
+ data?: unknown
32
+ }
33
+
34
+ type WxPageUpdateCoordinator = {
35
+ ready: boolean
36
+ pendingUpdate?: () => void
37
+ beginUpdate?: () => void
38
+ endUpdate?: () => void
39
+ }
40
+
41
+ type WxUpdateClient = {
42
+ receiveBatch(metadata: WxUpdateBatchMetadata, apply: () => void): void
43
+ refreshCompleted(stale: boolean): void
44
+ routeReady(): void
45
+ }
46
+
47
+ type WxClientGlobal = typeof globalThis & {
48
+ __VITE_PLUGIN_TARO_WX_CONTROL__?: WxUpdateControl
49
+ __VITE_PLUGIN_TARO_WX_UPDATE_CLIENT__?: WxUpdateClient
50
+ __VITE_PLUGIN_TARO_WX_PAGE_UPDATE__?: WxPageUpdateCoordinator
51
+ wx: {
52
+ request(options: {
53
+ url: string
54
+ method: 'POST'
55
+ data: unknown
56
+ timeout: number
57
+ success(result: WxRequestResult): void
58
+ fail(error: { errMsg?: string }): void
59
+ }): WxRequestTask
60
+ }
61
+ }
62
+
63
+ const wxClientGlobal = globalThis as WxClientGlobal
64
+ const transportRetryDelay = 500
65
+ const rebuildPollDelay = 1_000
66
+ const updateFileWatchdogDelay = 2_000
67
+
68
+ export function startWxUpdateClient(): void {
69
+ if (wxClientGlobal.__VITE_PLUGIN_TARO_WX_UPDATE_CLIENT__) return
70
+ const control = wxClientGlobal.__VITE_PLUGIN_TARO_WX_CONTROL__
71
+ if (!control) throw new Error('vite-plugin-taro could not find its WX update control configuration.')
72
+
73
+ let state = createWxUpdateClientState(control.buildId)
74
+ const sessionId = createSessionId()
75
+ let requestGeneration = 0
76
+ let activeRequest: WxRequestTask | undefined
77
+ let scheduledRequest: ReturnType<typeof setTimeout> | undefined
78
+ let pendingBatchApply: (() => void) | undefined
79
+
80
+ const dispatch = (event: WxUpdateClientEvent, apply?: () => void): void => {
81
+ const transition = transitionWxUpdateClient(state, event)
82
+ state = transition.state
83
+ for (const command of transition.commands) execute(command, apply)
84
+ }
85
+
86
+ const send = (action: 'register' | 'poll' | 'rebuild', version: number): void => {
87
+ if (scheduledRequest) clearTimeout(scheduledRequest)
88
+ scheduledRequest = undefined
89
+ activeRequest?.abort()
90
+ const generation = ++requestGeneration
91
+ activeRequest = wxClientGlobal.wx.request({
92
+ url: control.url,
93
+ method: 'POST',
94
+ data: {
95
+ token: control.token,
96
+ action,
97
+ buildId: state.buildId,
98
+ sessionId,
99
+ version
100
+ },
101
+ timeout: 30_000,
102
+ success(result) {
103
+ if (generation !== requestGeneration) return
104
+ activeRequest = undefined
105
+ const response = parseResponse(result.data)
106
+ if (action === 'register' && response.type === 'registered') {
107
+ dispatch({ type: 'registration-completed' })
108
+ return
109
+ }
110
+ if (action === 'poll' && (response.type === 'idle' || response.type === 'changed')) {
111
+ dispatch({ type: 'poll-completed' })
112
+ return
113
+ }
114
+ if (response.type === 'batch-published') {
115
+ // If DevTools misses the update.js event, report the unchanged version and republish with a new nonce.
116
+ scheduleRequest(updateFileWatchdogDelay)
117
+ return
118
+ }
119
+ if (response.type === 'rebuilding') {
120
+ scheduleRequest(rebuildPollDelay)
121
+ return
122
+ }
123
+ dispatch({ type: 'transport-failed' })
124
+ },
125
+ fail() {
126
+ if (generation !== requestGeneration) return
127
+ activeRequest = undefined
128
+ dispatch({ type: 'transport-failed' })
129
+ }
130
+ })
131
+ }
132
+
133
+ /** Keeps exactly one delayed metadata request and discards it when protocol state advances. */
134
+ const scheduleRequest = (delay: number): void => {
135
+ if (scheduledRequest) clearTimeout(scheduledRequest)
136
+ const expectedPhase = state.phase
137
+ const expectedVersion = state.version
138
+ scheduledRequest = setTimeout(() => {
139
+ scheduledRequest = undefined
140
+ if (state.phase !== expectedPhase || state.version !== expectedVersion) return
141
+ send(state.phase === 'registering' ? 'register' : 'poll', state.version)
142
+ }, delay)
143
+ }
144
+
145
+ const execute = (command: WxUpdateClientCommand, apply?: () => void): void => {
146
+ switch (command.type) {
147
+ case 'register':
148
+ send('register', command.version)
149
+ return
150
+ case 'poll':
151
+ case 'report-version':
152
+ send('poll', command.version)
153
+ return
154
+ case 'apply-batch': {
155
+ const batchApply = apply ?? pendingBatchApply
156
+ pendingBatchApply = undefined
157
+ if (!batchApply) {
158
+ dispatch({ type: 'batch-failed' })
159
+ return
160
+ }
161
+ const applyUpdate = () => {
162
+ const bridge = wxClientGlobal.__VITE_PLUGIN_TARO_WX_PAGE_UPDATE__
163
+ if (!bridge) {
164
+ dispatch({ type: 'batch-failed' })
165
+ return
166
+ }
167
+ bridge.beginUpdate?.()
168
+ try {
169
+ batchApply()
170
+ dispatch({ type: 'batch-executed', targetVersion: command.targetVersion })
171
+ } catch {
172
+ dispatch({ type: 'batch-failed' })
173
+ } finally {
174
+ bridge.endUpdate?.()
175
+ }
176
+ }
177
+ const bridge = wxClientGlobal.__VITE_PLUGIN_TARO_WX_PAGE_UPDATE__
178
+ if (bridge?.ready) applyUpdate()
179
+ else if (bridge) bridge.pendingUpdate = applyUpdate
180
+ else dispatch({ type: 'batch-failed' })
181
+ return
182
+ }
183
+ case 'request-full-build':
184
+ send('rebuild', state.version)
185
+ return
186
+ case 'retry-transport':
187
+ scheduleRequest(transportRetryDelay)
188
+ }
189
+ }
190
+
191
+ wxClientGlobal.__VITE_PLUGIN_TARO_WX_UPDATE_CLIENT__ = {
192
+ receiveBatch(metadata, apply) {
193
+ dispatch({ type: 'batch-observed', ...metadata }, apply)
194
+ if (
195
+ state.pendingBatch?.fromVersion === metadata.fromVersion &&
196
+ state.pendingBatch.targetVersion === metadata.targetVersion
197
+ ) {
198
+ pendingBatchApply = apply
199
+ } else if (!state.pendingBatch && state.phase !== 'applying') {
200
+ pendingBatchApply = undefined
201
+ }
202
+ },
203
+ refreshCompleted(stale) {
204
+ dispatch({ type: 'refresh-completed', stale })
205
+ },
206
+ routeReady() {
207
+ dispatch({ type: 'route-ready' })
208
+ }
209
+ }
210
+ dispatch({ type: 'started' })
211
+ }
212
+
213
+ function createSessionId(): string {
214
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`
215
+ }
216
+
217
+ function parseResponse(value: unknown): { type: string } {
218
+ if (typeof value !== 'object' || value === null || !('type' in value) || typeof value.type !== 'string') {
219
+ return { type: 'invalid' }
220
+ }
221
+ return { type: value.type }
222
+ }
package/src/vite.ts CHANGED
@@ -1,2 +1,2 @@
1
- export type { VitePluginTaroOptions, VitePluginTaroPageOption, VitePluginTaroTarget } from './vite/types.ts'
2
- export { default } from './vite/vite-plugin-taro.ts'
1
+ export { default } from './node/vite-plugin.ts'
2
+ export type { VitePluginTaroOptions, VitePluginTaroPageOption, VitePluginTaroTarget } from './options.ts'
package/dist/shim/h5.js DELETED
@@ -1,5 +0,0 @@
1
- import '@tarojs/plugin-platform-h5/dist/runtime';
2
- // @ts-expect-error Taro exposes createReactApp from this runtime-only deep entry without types.
3
- export { createReactApp } from '@tarojs/plugin-framework-react/dist/runtime';
4
- export { createBrowserHistory, createHashHistory, createRouter, handleAppMount } from '@tarojs/router';
5
- export { window } from '@tarojs/runtime';
@@ -1,3 +0,0 @@
1
- import Taro from '@tarojs/taro';
2
- export * from '@tarojs/taro';
3
- export default Taro;
@@ -1 +0,0 @@
1
- export * from '@tarojs/components';
@@ -1 +0,0 @@
1
- export * from '@tarojs/components';
@@ -1,4 +0,0 @@
1
- export declare const isProd: boolean;
2
- export declare const nodeRequire: NodeJS.Require;
3
- export declare const h5ShimImportPath: string;
4
- export declare const wxShimImportPath: string;
@@ -1,8 +0,0 @@
1
- import { createRequire } from 'node:module';
2
- import path from 'node:path';
3
- import { toImportPath } from './utils.js';
4
- export const isProd = process.env.NODE_ENV === 'production';
5
- export const nodeRequire = createRequire(import.meta.url);
6
- const packageRoot = path.dirname(nodeRequire.resolve('vite-plugin-taro/package.json'));
7
- export const h5ShimImportPath = toImportPath(path.join(packageRoot, 'dist/shim/h5.js'));
8
- export const wxShimImportPath = toImportPath(path.join(packageRoot, 'dist/shim/wx.js'));
@@ -1,8 +0,0 @@
1
- import type { Plugin } from 'vite';
2
- import type { VitePluginTaroBuildContext } from './types.ts';
3
- /**
4
- * Applies Taro-style conditional compilation comments before Vite parses source files.
5
- *
6
- * Mirrors Taro's CSS #ifdef/#ifndef handling, generalized before Vite parses code.
7
- */
8
- export declare function createVitePluginTaroConditionalDirectivePlugin(context: VitePluginTaroBuildContext): Plugin;