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.
- package/README.md +83 -106
- package/README.zh.md +83 -104
- package/dist/node/build-context.d.ts +25 -0
- package/dist/node/build-context.js +48 -0
- package/dist/node/css/css-pipeline.d.ts +21 -0
- package/dist/node/css/css-pipeline.js +129 -0
- package/dist/node/plugins/conditional-directives.d.ts +3 -0
- package/dist/node/plugins/conditional-directives.js +62 -0
- package/dist/node/plugins/taro-runtime.d.ts +5 -0
- package/dist/node/plugins/taro-runtime.js +18 -0
- package/dist/node/targets/h5/plugin.d.ts +6 -0
- package/dist/node/targets/h5/plugin.js +117 -0
- package/dist/node/targets/h5/virtual-modules.d.ts +5 -0
- package/dist/node/targets/h5/virtual-modules.js +60 -0
- package/dist/node/targets/wx/companion-assets.d.ts +18 -0
- package/dist/node/targets/wx/companion-assets.js +121 -0
- package/dist/node/targets/wx/dev-server/development-session.d.ts +31 -0
- package/dist/node/targets/wx/dev-server/development-session.js +202 -0
- package/dist/node/targets/wx/dev-server/full-build-scheduler.d.ts +14 -0
- package/dist/node/targets/wx/dev-server/full-build-scheduler.js +60 -0
- package/dist/node/targets/wx/dev-server/js-utils.d.ts +5 -0
- package/dist/node/targets/wx/dev-server/js-utils.js +28 -0
- package/dist/node/targets/wx/dev-server/output.d.ts +19 -0
- package/dist/node/targets/wx/dev-server/output.js +44 -0
- package/dist/node/targets/wx/dev-server/rolldown-runtime-source.d.ts +8 -0
- package/dist/node/targets/wx/dev-server/rolldown-runtime-source.js +95 -0
- package/dist/node/targets/wx/dev-server/update-server-state.d.ts +65 -0
- package/dist/node/targets/wx/dev-server/update-server-state.js +116 -0
- package/dist/node/targets/wx/dev-server/update-transport.d.ts +28 -0
- package/dist/node/targets/wx/dev-server/update-transport.js +212 -0
- package/dist/node/targets/wx/dev-server/vite-bundled-dev-adapter.d.ts +44 -0
- package/dist/node/targets/wx/dev-server/vite-bundled-dev-adapter.js +145 -0
- package/dist/node/targets/wx/development-files.d.ts +8 -0
- package/dist/node/targets/wx/development-files.js +8 -0
- package/dist/node/targets/wx/plugin.d.ts +6 -0
- package/dist/node/targets/wx/plugin.js +143 -0
- package/dist/node/targets/wx/react-refresh.d.ts +4 -0
- package/dist/node/targets/wx/react-refresh.js +38 -0
- package/dist/node/targets/wx/virtual-modules.d.ts +16 -0
- package/dist/node/targets/wx/virtual-modules.js +120 -0
- package/dist/node/utils/async.d.ts +8 -0
- package/dist/node/utils/async.js +16 -0
- package/dist/node/utils/filesystem.d.ts +13 -0
- package/dist/node/utils/filesystem.js +43 -0
- package/dist/{vite/utils.d.ts → node/utils/modules.d.ts} +2 -2
- package/dist/{vite/utils.js → node/utils/modules.js} +10 -3
- package/dist/node/utils/packages.d.ts +2 -0
- package/dist/node/utils/packages.js +8 -0
- package/dist/node/vite-plugin.d.ts +4 -0
- package/dist/node/vite-plugin.js +33 -0
- package/dist/options.d.ts +21 -0
- package/dist/{shim/h5.d.ts → runtime/h5/taro-runtime.d.ts} +3 -1
- package/dist/runtime/h5/taro-runtime.js +9 -0
- package/dist/runtime/taro/api.d.ts +5 -0
- package/dist/{virtual → runtime/taro}/api.js +3 -0
- package/dist/runtime/taro/components.d.ts +2 -0
- package/dist/runtime/taro/components.js +2 -0
- package/dist/runtime/wx/page-update.d.ts +6 -0
- package/dist/runtime/wx/page-update.js +157 -0
- package/dist/{shim/wx.d.ts → runtime/wx/taro-runtime.d.ts} +1 -0
- package/dist/{shim/wx.js → runtime/wx/taro-runtime.js} +1 -0
- package/dist/runtime/wx/update-client-state.d.ts +68 -0
- package/dist/runtime/wx/update-client-state.js +84 -0
- package/dist/runtime/wx/update-client.d.ts +1 -0
- package/dist/runtime/wx/update-client.js +170 -0
- package/dist/vite.d.ts +2 -2
- package/dist/vite.js +1 -1
- package/package.json +15 -11
- package/src/node/build-context.ts +63 -0
- package/src/node/css/css-pipeline.ts +147 -0
- package/src/node/plugins/conditional-directives.ts +76 -0
- package/src/node/plugins/taro-runtime.ts +22 -0
- package/src/node/targets/h5/plugin.ts +135 -0
- package/src/node/targets/h5/virtual-modules.ts +71 -0
- package/src/node/targets/wx/companion-assets.ts +160 -0
- package/src/node/targets/wx/dev-server/development-session.ts +253 -0
- package/src/node/targets/wx/dev-server/full-build-scheduler.ts +62 -0
- package/src/node/targets/wx/dev-server/js-utils.ts +33 -0
- package/src/node/targets/wx/dev-server/output.ts +56 -0
- package/src/node/targets/wx/dev-server/rolldown-runtime-source.ts +95 -0
- package/src/node/targets/wx/dev-server/update-server-state.ts +183 -0
- package/src/node/targets/wx/dev-server/update-transport.ts +261 -0
- package/src/node/targets/wx/dev-server/vite-bundled-dev-adapter.ts +203 -0
- package/src/node/targets/wx/development-files.ts +11 -0
- package/src/node/targets/wx/plugin.ts +163 -0
- package/src/node/targets/wx/react-refresh.ts +51 -0
- package/src/node/targets/wx/virtual-modules.ts +152 -0
- package/src/node/utils/async.ts +19 -0
- package/src/node/utils/filesystem.ts +45 -0
- package/src/{vite/utils.ts → node/utils/modules.ts} +11 -3
- package/src/node/utils/packages.ts +10 -0
- package/src/node/vite-plugin.ts +41 -0
- package/src/options.ts +25 -0
- package/src/runtime/h5/taro-css.d.ts +3 -0
- package/src/runtime/h5/taro-runtime.ts +10 -0
- package/src/{virtual → runtime/taro}/api.ts +3 -0
- package/src/runtime/taro/components.ts +2 -0
- package/src/runtime/wx/page-update.ts +230 -0
- package/src/runtime/wx/react-refresh.d.ts +20 -0
- package/src/{shim/wx.ts → runtime/wx/taro-runtime.ts} +1 -0
- package/src/runtime/wx/update-client-state.ts +138 -0
- package/src/runtime/wx/update-client.ts +222 -0
- package/src/vite.ts +2 -2
- package/dist/shim/h5.js +0 -5
- package/dist/virtual/api.d.ts +0 -3
- package/dist/virtual/components.d.ts +0 -1
- package/dist/virtual/components.js +0 -1
- package/dist/vite/constants.d.ts +0 -4
- package/dist/vite/constants.js +0 -8
- package/dist/vite/plugins.d.ts +0 -8
- package/dist/vite/plugins.js +0 -186
- package/dist/vite/tailwindcss.d.ts +0 -3
- package/dist/vite/tailwindcss.js +0 -35
- package/dist/vite/targets/h5.d.ts +0 -31
- package/dist/vite/targets/h5.js +0 -201
- package/dist/vite/targets/wx.d.ts +0 -74
- package/dist/vite/targets/wx.js +0 -362
- package/dist/vite/types.d.ts +0 -38
- package/dist/vite/virtual-modules.d.ts +0 -3
- package/dist/vite/virtual-modules.js +0 -13
- package/dist/vite/vite-plugin-taro.d.ts +0 -7
- package/dist/vite/vite-plugin-taro.js +0 -80
- package/src/shim/h5.ts +0 -6
- package/src/virtual/components.ts +0 -1
- package/src/vite/constants.ts +0 -12
- package/src/vite/plugins.ts +0 -218
- package/src/vite/tailwindcss.ts +0 -41
- package/src/vite/targets/h5.ts +0 -229
- package/src/vite/targets/wx.ts +0 -436
- package/src/vite/types.ts +0 -48
- package/src/vite/virtual-modules.ts +0 -14
- package/src/vite/vite-plugin-taro.ts +0 -106
- /package/dist/{vite/types.js → options.js} +0 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure host-side stop-and-wait protocol state machine.
|
|
3
|
+
*
|
|
4
|
+
* It retains versioned Rolldown deltas and selects one missing range for the active App Service. HTTP and filesystem
|
|
5
|
+
* effects are represented as commands and handled by update-transport.ts.
|
|
6
|
+
*/
|
|
7
|
+
type WxUpdateDelta = {
|
|
8
|
+
version: number
|
|
9
|
+
code: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type WxUpdateBatch = {
|
|
13
|
+
buildId: string
|
|
14
|
+
sessionId: string
|
|
15
|
+
fromVersion: number
|
|
16
|
+
targetVersion: number
|
|
17
|
+
deltas: WxUpdateDelta[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type WxUpdateServerState = {
|
|
21
|
+
buildId: string
|
|
22
|
+
hostVersion: number
|
|
23
|
+
retainedDeltaBytes: number
|
|
24
|
+
deltas: WxUpdateDelta[]
|
|
25
|
+
activeSessionId?: string
|
|
26
|
+
retiredSessionIds: string[]
|
|
27
|
+
inFlight?: Pick<WxUpdateBatch, 'sessionId' | 'fromVersion' | 'targetVersion'>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type WxUpdateServerEvent =
|
|
31
|
+
| { type: 'delta-added'; code: string; bytes?: number }
|
|
32
|
+
| { type: 'client-registered'; buildId: string; sessionId: string; version: number }
|
|
33
|
+
| { type: 'client-reported'; buildId: string; sessionId: string; version: number }
|
|
34
|
+
| { type: 'batch-publish-failed'; sessionId: string; targetVersion: number }
|
|
35
|
+
| { type: 'full-build-committed'; buildId: string }
|
|
36
|
+
|
|
37
|
+
export type WxUpdateServerCommand =
|
|
38
|
+
| { type: 'publish-batch'; batch: WxUpdateBatch }
|
|
39
|
+
| { type: 'request-full-build'; reason: 'client-version-ahead' | 'invalid-client-version' }
|
|
40
|
+
| { type: 'ignore-client'; reason: 'stale-build' | 'retired-session' | 'unknown-session' }
|
|
41
|
+
|
|
42
|
+
type WxUpdateServerTransition = {
|
|
43
|
+
state: WxUpdateServerState
|
|
44
|
+
commands: WxUpdateServerCommand[]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createWxUpdateServerState(buildId: string): WxUpdateServerState {
|
|
48
|
+
return {
|
|
49
|
+
buildId,
|
|
50
|
+
hostVersion: 0,
|
|
51
|
+
retainedDeltaBytes: 0,
|
|
52
|
+
deltas: [],
|
|
53
|
+
retiredSessionIds: []
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function transitionWxUpdateServer(
|
|
58
|
+
state: WxUpdateServerState,
|
|
59
|
+
event: WxUpdateServerEvent
|
|
60
|
+
): WxUpdateServerTransition {
|
|
61
|
+
switch (event.type) {
|
|
62
|
+
case 'delta-added': {
|
|
63
|
+
const version = state.hostVersion + 1
|
|
64
|
+
return transition({
|
|
65
|
+
...state,
|
|
66
|
+
hostVersion: version,
|
|
67
|
+
retainedDeltaBytes: state.retainedDeltaBytes + (event.bytes ?? event.code.length),
|
|
68
|
+
deltas: [...state.deltas, { version, code: event.code }]
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
case 'client-registered':
|
|
72
|
+
return registerClient(state, event)
|
|
73
|
+
case 'client-reported':
|
|
74
|
+
return reportClient(state, event)
|
|
75
|
+
case 'batch-publish-failed':
|
|
76
|
+
if (state.inFlight?.sessionId !== event.sessionId || state.inFlight.targetVersion !== event.targetVersion) {
|
|
77
|
+
return transition(state)
|
|
78
|
+
}
|
|
79
|
+
return transition({ ...state, inFlight: undefined })
|
|
80
|
+
case 'full-build-committed':
|
|
81
|
+
return transition(createWxUpdateServerState(event.buildId))
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function registerClient(
|
|
86
|
+
state: WxUpdateServerState,
|
|
87
|
+
client: { buildId: string; sessionId: string; version: number }
|
|
88
|
+
): WxUpdateServerTransition {
|
|
89
|
+
const rejected = rejectClient(state, client)
|
|
90
|
+
if (rejected) return rejected
|
|
91
|
+
if (state.retiredSessionIds.includes(client.sessionId)) {
|
|
92
|
+
return command(state, { type: 'ignore-client', reason: 'retired-session' })
|
|
93
|
+
}
|
|
94
|
+
if (state.activeSessionId === client.sessionId) return transition(state)
|
|
95
|
+
|
|
96
|
+
const retiredSessionIds = state.activeSessionId
|
|
97
|
+
? [...new Set([...state.retiredSessionIds, state.activeSessionId])]
|
|
98
|
+
: state.retiredSessionIds
|
|
99
|
+
const registered = {
|
|
100
|
+
...state,
|
|
101
|
+
activeSessionId: client.sessionId,
|
|
102
|
+
retiredSessionIds,
|
|
103
|
+
inFlight: undefined
|
|
104
|
+
}
|
|
105
|
+
return transition(registered)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function reportClient(
|
|
109
|
+
state: WxUpdateServerState,
|
|
110
|
+
client: { buildId: string; sessionId: string; version: number }
|
|
111
|
+
): WxUpdateServerTransition {
|
|
112
|
+
const rejected = rejectClient(state, client)
|
|
113
|
+
if (rejected) return rejected
|
|
114
|
+
if (state.retiredSessionIds.includes(client.sessionId)) {
|
|
115
|
+
return command(state, { type: 'ignore-client', reason: 'retired-session' })
|
|
116
|
+
}
|
|
117
|
+
if (client.sessionId !== state.activeSessionId) {
|
|
118
|
+
return command(state, { type: 'ignore-client', reason: 'unknown-session' })
|
|
119
|
+
}
|
|
120
|
+
return synchronizeClient(state, client.version)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function rejectClient(
|
|
124
|
+
state: WxUpdateServerState,
|
|
125
|
+
client: { buildId: string; version: number }
|
|
126
|
+
): WxUpdateServerTransition | undefined {
|
|
127
|
+
if (client.buildId !== state.buildId) {
|
|
128
|
+
return command(state, { type: 'ignore-client', reason: 'stale-build' })
|
|
129
|
+
}
|
|
130
|
+
if (!Number.isInteger(client.version) || client.version < 0) {
|
|
131
|
+
return command(state, { type: 'request-full-build', reason: 'invalid-client-version' })
|
|
132
|
+
}
|
|
133
|
+
if (client.version > state.hostVersion) {
|
|
134
|
+
return command(state, { type: 'request-full-build', reason: 'client-version-ahead' })
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function synchronizeClient(state: WxUpdateServerState, clientVersion: number): WxUpdateServerTransition {
|
|
139
|
+
let synchronized = state
|
|
140
|
+
const inFlight = state.inFlight
|
|
141
|
+
if (inFlight) {
|
|
142
|
+
if (clientVersion === inFlight.fromVersion) {
|
|
143
|
+
const batch = createBatch(state, clientVersion, inFlight.targetVersion)
|
|
144
|
+
return command(state, { type: 'publish-batch', batch })
|
|
145
|
+
}
|
|
146
|
+
if (clientVersion !== inFlight.targetVersion) {
|
|
147
|
+
return command(state, { type: 'request-full-build', reason: 'invalid-client-version' })
|
|
148
|
+
}
|
|
149
|
+
synchronized = { ...state, inFlight: undefined }
|
|
150
|
+
}
|
|
151
|
+
if (clientVersion >= synchronized.hostVersion) return transition(synchronized)
|
|
152
|
+
|
|
153
|
+
const batch = createBatch(synchronized, clientVersion, synchronized.hostVersion)
|
|
154
|
+
return command(
|
|
155
|
+
{
|
|
156
|
+
...synchronized,
|
|
157
|
+
inFlight: {
|
|
158
|
+
sessionId: batch.sessionId,
|
|
159
|
+
fromVersion: batch.fromVersion,
|
|
160
|
+
targetVersion: batch.targetVersion
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
{ type: 'publish-batch', batch }
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function createBatch(state: WxUpdateServerState, fromVersion: number, targetVersion: number): WxUpdateBatch {
|
|
168
|
+
return {
|
|
169
|
+
buildId: state.buildId,
|
|
170
|
+
sessionId: state.activeSessionId!,
|
|
171
|
+
fromVersion,
|
|
172
|
+
targetVersion,
|
|
173
|
+
deltas: state.deltas.filter((delta) => delta.version > fromVersion && delta.version <= targetVersion)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function transition(state: WxUpdateServerState): WxUpdateServerTransition {
|
|
178
|
+
return { state, commands: [] }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function command(state: WxUpdateServerState, value: WxUpdateServerCommand): WxUpdateServerTransition {
|
|
182
|
+
return { state, commands: [value] }
|
|
183
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapts the pure server protocol to Vite HTTP middleware and literal update-file publication.
|
|
3
|
+
* HTTP messages contain metadata only; executable JavaScript reaches DevTools through the injected file writer.
|
|
4
|
+
*/
|
|
5
|
+
import { randomBytes } from 'node:crypto'
|
|
6
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
7
|
+
import type { ViteDevServer } from 'vite'
|
|
8
|
+
import {
|
|
9
|
+
createWxUpdateServerState,
|
|
10
|
+
transitionWxUpdateServer,
|
|
11
|
+
type WxUpdateBatch,
|
|
12
|
+
type WxUpdateServerCommand
|
|
13
|
+
} from './update-server-state.ts'
|
|
14
|
+
|
|
15
|
+
const endpointPath = '/__vite_plugin_taro_wx_update__'
|
|
16
|
+
// Keep the host response below update-client.ts's 30-second wx.request timeout.
|
|
17
|
+
const pollTimeout = 25_000
|
|
18
|
+
|
|
19
|
+
type PendingPoll = {
|
|
20
|
+
response: ServerResponse
|
|
21
|
+
timeout: NodeJS.Timeout
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type UpdateRequest = {
|
|
25
|
+
token?: unknown
|
|
26
|
+
action?: unknown
|
|
27
|
+
buildId?: unknown
|
|
28
|
+
sessionId?: unknown
|
|
29
|
+
version?: unknown
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class WxUpdateTransport {
|
|
33
|
+
private state = createWxUpdateServerState(createId())
|
|
34
|
+
private readonly token = createId()
|
|
35
|
+
private readonly pendingPolls = new Map<string, PendingPoll>()
|
|
36
|
+
private closed = false
|
|
37
|
+
|
|
38
|
+
constructor(
|
|
39
|
+
private readonly server: ViteDevServer,
|
|
40
|
+
private readonly requestFullBuild: () => void,
|
|
41
|
+
private readonly writeUpdateFile: (buildId: string, source: string) => Promise<void>
|
|
42
|
+
) {}
|
|
43
|
+
|
|
44
|
+
get retainedDeltaCount(): number {
|
|
45
|
+
return this.state.hostVersion
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get retainedDeltaBytes(): number {
|
|
49
|
+
return this.state.retainedDeltaBytes
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
install(): void {
|
|
53
|
+
this.server.middlewares.use(endpointPath, this.handleRequest)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
close(): void {
|
|
57
|
+
this.closed = true
|
|
58
|
+
this.respondToAll({ type: 'rebuilding' })
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
addDelta(code: string): void {
|
|
62
|
+
this.apply({ type: 'delta-added', code, bytes: Buffer.byteLength(code) })
|
|
63
|
+
this.respondToAll({ type: 'changed' })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
createBuildId(): string {
|
|
67
|
+
return createId()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
isCurrentBuild(buildId: string): boolean {
|
|
71
|
+
return this.state.buildId === buildId
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
commitFullBuild(buildId: string): void {
|
|
75
|
+
this.apply({ type: 'full-build-committed', buildId })
|
|
76
|
+
this.respondToAll({ type: 'rebuilding' })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Generates late-bound App metadata after Vite has selected its actual listening port. */
|
|
80
|
+
createControlSource(buildId = this.state.buildId): string {
|
|
81
|
+
const address = this.server.httpServer?.address()
|
|
82
|
+
const port = address && typeof address !== 'string' ? address.port : this.server.config.server.port
|
|
83
|
+
return `globalThis.__VITE_PLUGIN_TARO_WX_CONTROL__ = ${JSON.stringify({
|
|
84
|
+
url: `http://localhost:${port}${endpointPath}`,
|
|
85
|
+
token: this.token,
|
|
86
|
+
buildId
|
|
87
|
+
})};\n`
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private readonly handleRequest = async (request: IncomingMessage, response: ServerResponse): Promise<void> => {
|
|
91
|
+
if (this.closed || request.method !== 'POST') {
|
|
92
|
+
respond(response, 404, { type: 'not-found' })
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let body: UpdateRequest
|
|
97
|
+
try {
|
|
98
|
+
body = JSON.parse(await readBody(request)) as UpdateRequest
|
|
99
|
+
} catch {
|
|
100
|
+
respond(response, 400, { type: 'invalid-request' })
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
if (body.token !== this.token || !isClientReport(body)) {
|
|
104
|
+
respond(response, 403, { type: 'forbidden' })
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (body.action === 'rebuild') {
|
|
109
|
+
this.requestFullBuild()
|
|
110
|
+
respond(response, 202, { type: 'rebuilding' })
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
if (body.action === 'register') {
|
|
114
|
+
const transition = this.apply({
|
|
115
|
+
type: 'client-registered',
|
|
116
|
+
buildId: body.buildId,
|
|
117
|
+
sessionId: body.sessionId,
|
|
118
|
+
version: body.version
|
|
119
|
+
})
|
|
120
|
+
if (transition.some((command) => command.type === 'request-full-build')) this.requestFullBuild()
|
|
121
|
+
respond(response, 200, { type: 'registered' })
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
if (body.action !== 'poll') {
|
|
125
|
+
respond(response, 400, { type: 'invalid-request' })
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const commands = this.apply({
|
|
130
|
+
type: 'client-reported',
|
|
131
|
+
buildId: body.buildId,
|
|
132
|
+
sessionId: body.sessionId,
|
|
133
|
+
version: body.version
|
|
134
|
+
})
|
|
135
|
+
await this.executePollCommands(body.sessionId, commands, response)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private async executePollCommands(
|
|
139
|
+
sessionId: string,
|
|
140
|
+
commands: WxUpdateServerCommand[],
|
|
141
|
+
response: ServerResponse
|
|
142
|
+
): Promise<void> {
|
|
143
|
+
const publish = commands.find((command) => command.type === 'publish-batch')
|
|
144
|
+
if (publish?.type === 'publish-batch') {
|
|
145
|
+
try {
|
|
146
|
+
await this.writeBatch(publish.batch)
|
|
147
|
+
respond(response, 200, { type: 'batch-published', targetVersion: publish.batch.targetVersion })
|
|
148
|
+
} catch {
|
|
149
|
+
this.apply({
|
|
150
|
+
type: 'batch-publish-failed',
|
|
151
|
+
sessionId: publish.batch.sessionId,
|
|
152
|
+
targetVersion: publish.batch.targetVersion
|
|
153
|
+
})
|
|
154
|
+
respond(response, 500, { type: 'publish-failed' })
|
|
155
|
+
}
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
if (commands.some((command) => command.type === 'request-full-build')) {
|
|
159
|
+
this.requestFullBuild()
|
|
160
|
+
respond(response, 202, { type: 'rebuilding' })
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
if (commands.some((command) => command.type === 'ignore-client')) {
|
|
164
|
+
respond(response, 409, { type: 'rebuilding' })
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
this.holdPoll(sessionId, response)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private holdPoll(sessionId: string, response: ServerResponse): void {
|
|
171
|
+
const previous = this.pendingPolls.get(sessionId)
|
|
172
|
+
if (previous) this.finishPoll(sessionId, previous, { type: 'changed' })
|
|
173
|
+
|
|
174
|
+
let pending: PendingPoll
|
|
175
|
+
pending = {
|
|
176
|
+
response,
|
|
177
|
+
timeout: setTimeout(() => this.finishPoll(sessionId, pending, { type: 'idle' }), pollTimeout)
|
|
178
|
+
}
|
|
179
|
+
this.pendingPolls.set(sessionId, pending)
|
|
180
|
+
response.on('close', () => this.finishPoll(sessionId, pending))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private respondToAll(value: { type: string }): void {
|
|
184
|
+
for (const [sessionId, pending] of this.pendingPolls) this.finishPoll(sessionId, pending, value)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private finishPoll(sessionId: string, pending: PendingPoll, value?: { type: string }): void {
|
|
188
|
+
if (this.pendingPolls.get(sessionId) !== pending) return
|
|
189
|
+
clearTimeout(pending.timeout)
|
|
190
|
+
this.pendingPolls.delete(sessionId)
|
|
191
|
+
if (value) respond(pending.response, 200, value)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private apply(event: Parameters<typeof transitionWxUpdateServer>[1]): WxUpdateServerCommand[] {
|
|
195
|
+
const transition = transitionWxUpdateServer(this.state, event)
|
|
196
|
+
this.state = transition.state
|
|
197
|
+
return transition.commands
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private async writeBatch(batch: WxUpdateBatch): Promise<void> {
|
|
201
|
+
await this.writeUpdateFile(batch.buildId, renderBatch(batch, createId()))
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Renders executable code for update.js; this source is never included in an HTTP response. */
|
|
206
|
+
function renderBatch(batch: WxUpdateBatch, nonce: string): string {
|
|
207
|
+
return `// ${nonce}\nglobalThis.__VITE_PLUGIN_TARO_WX_UPDATE_CLIENT__.receiveBatch(${JSON.stringify({
|
|
208
|
+
buildId: batch.buildId,
|
|
209
|
+
fromVersion: batch.fromVersion,
|
|
210
|
+
targetVersion: batch.targetVersion
|
|
211
|
+
})}, () => {
|
|
212
|
+
${indent(batch.deltas.map((delta) => delta.code).join('\n'), 4)}
|
|
213
|
+
});\n`
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function indent(value: string, spaces: number): string {
|
|
217
|
+
const prefix = ' '.repeat(spaces)
|
|
218
|
+
return value
|
|
219
|
+
.split('\n')
|
|
220
|
+
.map((line) => `${prefix}${line}`)
|
|
221
|
+
.join('\n')
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function isClientReport(value: UpdateRequest): value is {
|
|
225
|
+
token: string
|
|
226
|
+
action: 'register' | 'poll' | 'rebuild'
|
|
227
|
+
buildId: string
|
|
228
|
+
sessionId: string
|
|
229
|
+
version: number
|
|
230
|
+
} {
|
|
231
|
+
return (
|
|
232
|
+
typeof value.token === 'string' &&
|
|
233
|
+
typeof value.action === 'string' &&
|
|
234
|
+
typeof value.buildId === 'string' &&
|
|
235
|
+
typeof value.sessionId === 'string' &&
|
|
236
|
+
typeof value.version === 'number'
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function readBody(request: IncomingMessage): Promise<string> {
|
|
241
|
+
const chunks: Buffer[] = []
|
|
242
|
+
let size = 0
|
|
243
|
+
for await (const chunk of request) {
|
|
244
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
|
|
245
|
+
size += buffer.length
|
|
246
|
+
if (size > 64 * 1024) throw new Error('WX update request body is too large.')
|
|
247
|
+
chunks.push(buffer)
|
|
248
|
+
}
|
|
249
|
+
return Buffer.concat(chunks).toString('utf8')
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function respond(response: ServerResponse, status: number, body: unknown): void {
|
|
253
|
+
if (response.writableEnded) return
|
|
254
|
+
response.statusCode = status
|
|
255
|
+
response.setHeader('content-type', 'application/json')
|
|
256
|
+
response.end(JSON.stringify(body))
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function createId(): string {
|
|
260
|
+
return randomBytes(16).toString('hex')
|
|
261
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import type { ResolvedConfig, ViteDevServer } from 'vite'
|
|
3
|
+
import { wxPagePreloadFile, wxUpdateControlFile, wxUpdateFile } from '../development-files.ts'
|
|
4
|
+
import type { WxOutputFile } from './output.ts'
|
|
5
|
+
import { wxRolldownRuntimeSource } from './rolldown-runtime-source.ts'
|
|
6
|
+
|
|
7
|
+
const clientId = 'vite-plugin-taro-wx'
|
|
8
|
+
|
|
9
|
+
/** HMR boundary shape returned by Vite's private WX DevEngine integration. */
|
|
10
|
+
type WxDevEngineBoundary = {
|
|
11
|
+
boundary: string
|
|
12
|
+
acceptedVia: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Update result shape returned by Vite's private bundled-development engine. */
|
|
16
|
+
export type WxDevEngineUpdate =
|
|
17
|
+
| { type: 'Noop' }
|
|
18
|
+
| { type: 'FullReload'; reason?: string }
|
|
19
|
+
| {
|
|
20
|
+
type: 'Patch'
|
|
21
|
+
code: string
|
|
22
|
+
filename: string
|
|
23
|
+
hmrBoundaries: WxDevEngineBoundary[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type WxDevEngineInternal = {
|
|
27
|
+
registerModules(clientId: string, modules: string[]): void
|
|
28
|
+
ensureCurrentBuildFinish(): Promise<void>
|
|
29
|
+
ensureLatestBuildOutput(): Promise<unknown>
|
|
30
|
+
getBundleState(): Promise<{ lastBuildErrored: boolean }>
|
|
31
|
+
triggerFullBuild(): void
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type WxRolldownOptions = {
|
|
35
|
+
output?: Record<string, unknown> | Array<Record<string, unknown>>
|
|
36
|
+
experimental?: {
|
|
37
|
+
devMode?: boolean | Record<string, unknown>
|
|
38
|
+
[key: string]: unknown
|
|
39
|
+
}
|
|
40
|
+
[key: string]: unknown
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type WxDevEngineClient = {
|
|
44
|
+
send(payload: unknown): void
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type ViteBundledDevInternal = {
|
|
48
|
+
_devEngine?: WxDevEngineInternal
|
|
49
|
+
clients: {
|
|
50
|
+
setupIfNeeded(client: WxDevEngineClient, clientId: string): void
|
|
51
|
+
}
|
|
52
|
+
getRolldownOptions(): Promise<WxRolldownOptions>
|
|
53
|
+
storeOutputFiles(output: WxOutputFile[]): void
|
|
54
|
+
handleHmrOutput(client: WxDevEngineClient, files: string[], output: WxDevEngineUpdate, info?: unknown): void
|
|
55
|
+
listen(): Promise<void>
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type ViteBundledDevCallbacks = {
|
|
59
|
+
onOutput(output: WxOutputFile[]): void
|
|
60
|
+
onPatch(files: string[], output: WxDevEngineUpdate): boolean
|
|
61
|
+
onError(message: string): void
|
|
62
|
+
waitForInitialBundle(): Promise<void>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Isolates the private Vite bundled-development API used by the WX session. */
|
|
66
|
+
export class ViteBundledDevAdapter {
|
|
67
|
+
private bundledDev: ViteBundledDevInternal | undefined
|
|
68
|
+
|
|
69
|
+
constructor(
|
|
70
|
+
private readonly config: ResolvedConfig,
|
|
71
|
+
private readonly server: ViteDevServer,
|
|
72
|
+
private readonly callbacks: ViteBundledDevCallbacks
|
|
73
|
+
) {}
|
|
74
|
+
|
|
75
|
+
install(): void {
|
|
76
|
+
const bundledDev = this.server.environments.client.bundledDev as unknown as ViteBundledDevInternal | undefined
|
|
77
|
+
if (!bundledDev) throw new Error('vite-plugin-taro requires Vite bundled development for WX development.')
|
|
78
|
+
if (
|
|
79
|
+
typeof bundledDev.getRolldownOptions !== 'function' ||
|
|
80
|
+
typeof bundledDev.storeOutputFiles !== 'function' ||
|
|
81
|
+
typeof bundledDev.handleHmrOutput !== 'function'
|
|
82
|
+
) {
|
|
83
|
+
throw new Error('vite-plugin-taro does not support this Vite bundled-development API shape.')
|
|
84
|
+
}
|
|
85
|
+
this.bundledDev = bundledDev
|
|
86
|
+
this.installOptions(bundledDev)
|
|
87
|
+
this.installOutput(bundledDev)
|
|
88
|
+
this.installPatches(bundledDev)
|
|
89
|
+
this.installListener(bundledDev)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
registerBundleModules(output: WxOutputFile[]): number {
|
|
93
|
+
const moduleIds = new Set<string>()
|
|
94
|
+
for (const item of output) {
|
|
95
|
+
if (item.type !== 'chunk') continue
|
|
96
|
+
for (const id of Object.keys(item.modules ?? {})) moduleIds.add(toStableModuleId(id, this.config.root))
|
|
97
|
+
}
|
|
98
|
+
this.registerModules([...moduleIds])
|
|
99
|
+
return moduleIds.size
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
registerPatchModules(code: string): void {
|
|
103
|
+
const moduleIds = new Set<string>()
|
|
104
|
+
for (const match of code.matchAll(/create(?:Esm|Cjs)Initializer\("([^"]+)"/g)) moduleIds.add(match[1])
|
|
105
|
+
this.registerModules([...moduleIds])
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private registerModules(moduleIds: string[]): void {
|
|
109
|
+
if (moduleIds.length) this.bundledDev?._devEngine?.registerModules(clientId, moduleIds)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async rebuild(): Promise<void> {
|
|
113
|
+
const engine = this.bundledDev?._devEngine
|
|
114
|
+
if (!engine) return
|
|
115
|
+
engine.triggerFullBuild()
|
|
116
|
+
await engine.ensureLatestBuildOutput()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private installOptions(bundledDev: ViteBundledDevInternal): void {
|
|
120
|
+
const getRolldownOptions = bundledDev.getRolldownOptions.bind(bundledDev)
|
|
121
|
+
bundledDev.getRolldownOptions = async () => {
|
|
122
|
+
const options = await getRolldownOptions()
|
|
123
|
+
if (!options.output) options.output = {}
|
|
124
|
+
const output = getFirstOutput(options)
|
|
125
|
+
const configuredOutput = this.config.build.rolldownOptions.output
|
|
126
|
+
const desiredOutput = Array.isArray(configuredOutput) ? configuredOutput[0] : configuredOutput
|
|
127
|
+
Object.assign(output, desiredOutput, {
|
|
128
|
+
format: 'cjs',
|
|
129
|
+
sourcemap: false,
|
|
130
|
+
minify: false,
|
|
131
|
+
banner: createWxDevelopmentBanner
|
|
132
|
+
})
|
|
133
|
+
options.experimental ??= {}
|
|
134
|
+
options.experimental.devMode = {
|
|
135
|
+
...(typeof options.experimental.devMode === 'object' ? options.experimental.devMode : {}),
|
|
136
|
+
lazy: false,
|
|
137
|
+
// Rolldown compiles this source into runtime.js before any Taro/React module executes.
|
|
138
|
+
implement: wxRolldownRuntimeSource
|
|
139
|
+
}
|
|
140
|
+
return options
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private installOutput(bundledDev: ViteBundledDevInternal): void {
|
|
145
|
+
const storeOutputFiles = bundledDev.storeOutputFiles.bind(bundledDev)
|
|
146
|
+
bundledDev.storeOutputFiles = (output) => {
|
|
147
|
+
storeOutputFiles(output)
|
|
148
|
+
this.callbacks.onOutput(output)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private installPatches(bundledDev: ViteBundledDevInternal): void {
|
|
153
|
+
const handleHmrOutput = bundledDev.handleHmrOutput.bind(bundledDev)
|
|
154
|
+
bundledDev.handleHmrOutput = (client, files, output, info) => {
|
|
155
|
+
if (output.type === 'Noop') return
|
|
156
|
+
if (this.callbacks.onPatch(files, output)) handleHmrOutput(client, files, output, info)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private installListener(bundledDev: ViteBundledDevInternal): void {
|
|
161
|
+
const listen = bundledDev.listen.bind(bundledDev)
|
|
162
|
+
bundledDev.listen = async () => {
|
|
163
|
+
await listen()
|
|
164
|
+
bundledDev.clients.setupIfNeeded({ send: (payload) => this.handlePayload(payload) }, clientId)
|
|
165
|
+
const engine = bundledDev._devEngine
|
|
166
|
+
if (!engine) throw new Error('vite-plugin-taro expected Vite to initialize the WX DevEngine.')
|
|
167
|
+
await engine.ensureCurrentBuildFinish()
|
|
168
|
+
if ((await engine.getBundleState()).lastBuildErrored) {
|
|
169
|
+
throw new Error('The initial WX bundled-development build failed.')
|
|
170
|
+
}
|
|
171
|
+
await this.callbacks.waitForInitialBundle()
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private handlePayload(payload: unknown): void {
|
|
176
|
+
if (typeof payload !== 'object' || payload === null) return
|
|
177
|
+
const candidate = payload as { type?: unknown; err?: { message?: unknown } }
|
|
178
|
+
if (candidate.type === 'error' && typeof candidate.err?.message === 'string') {
|
|
179
|
+
this.callbacks.onError(candidate.err.message)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function toStableModuleId(id: string, root: string): string {
|
|
185
|
+
const normalizedId = id.replace(/\\/g, '/')
|
|
186
|
+
if (normalizedId.startsWith('\0') || !path.posix.isAbsolute(normalizedId)) return normalizedId
|
|
187
|
+
return path.posix.relative(root.replace(/\\/g, '/'), normalizedId)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function getFirstOutput(options: WxRolldownOptions): Record<string, unknown> {
|
|
191
|
+
if (!Array.isArray(options.output)) return options.output as Record<string, unknown>
|
|
192
|
+
const output = options.output[0] ?? {}
|
|
193
|
+
options.output[0] = output
|
|
194
|
+
return output
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Adds native direct dependencies whose execution order cannot be represented through the Rolldown graph. */
|
|
198
|
+
function createWxDevelopmentBanner(chunk: { fileName: string }): string {
|
|
199
|
+
if (chunk.fileName === 'app.js') return `require(${JSON.stringify(`./${wxUpdateControlFile}`)});`
|
|
200
|
+
if (!chunk.fileName.startsWith('pages/') || !chunk.fileName.endsWith('.js')) return ''
|
|
201
|
+
const prefix = '../'.repeat(chunk.fileName.split('/').length - 1)
|
|
202
|
+
return `require(${JSON.stringify(`${prefix}runtime.js`)});require(${JSON.stringify(`${prefix}${wxPagePreloadFile}`)});require(${JSON.stringify(`${prefix}${wxUpdateFile}`)});`
|
|
203
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Plugin-owned files added only to a WX development output. */
|
|
2
|
+
export const wxDevelopmentDirectory = 'vpt-hmr'
|
|
3
|
+
|
|
4
|
+
/** App-only metadata containing the local transport URL, token, and build ID. */
|
|
5
|
+
export const wxUpdateControlFile = `${wxDevelopmentDirectory}/control.js`
|
|
6
|
+
|
|
7
|
+
/** Stable page-component initialization required before replaying retained updates. */
|
|
8
|
+
export const wxPagePreloadFile = `${wxDevelopmentDirectory}/preload.js`
|
|
9
|
+
|
|
10
|
+
/** The one mutable, literal JavaScript update dependency required by every page. */
|
|
11
|
+
export const wxUpdateFile = `${wxDevelopmentDirectory}/update.js`
|