vite-plugin-taro 0.5.6 → 0.5.7
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/dist/node/plugins/wx/dev/plugins.d.ts +22 -0
- package/dist/node/plugins/wx/dev/plugins.js +158 -0
- package/dist/node/plugins/wx/dev/react-refresh.d.ts +1 -2
- package/dist/node/plugins/wx/dev/react-refresh.js +12 -14
- package/dist/node/plugins/wx/dev/wx-dev-options.js +2 -1
- package/dist/node/plugins/wx/module.d.ts +2 -0
- package/dist/node/plugins/wx/module.js +2 -0
- package/dist/node/plugins/wx/plugins.js +1 -1
- package/dist/runtime/wx/capsule/page.js +1 -71
- package/dist/runtime/wx/dev/dev-runtime.js +161 -84
- package/package.json +3 -3
- package/src/node/plugins/wx/dev/plugins.ts +170 -0
- package/src/node/plugins/wx/dev/react-refresh.ts +12 -15
- package/src/node/plugins/wx/dev/wx-dev-options.ts +2 -1
- package/src/node/plugins/wx/module.ts +3 -0
- package/src/node/plugins/wx/plugins.ts +1 -1
- package/src/runtime/wx/capsule/page.ts +1 -93
- package/src/runtime/wx/dev/dev-runtime.ts +233 -89
- package/src/runtime/wx/wechat.d.ts +3 -6
- package/dist/node/plugins/wx/dev/plugin.d.ts +0 -7
- package/dist/node/plugins/wx/dev/plugin.js +0 -73
- package/src/node/plugins/wx/dev/plugin.ts +0 -82
|
@@ -7,34 +7,59 @@
|
|
|
7
7
|
//
|
|
8
8
|
// Every Page explicitly passes the inert hmr/patches.js export here before importing its capsule. The runtime applies that
|
|
9
9
|
// cumulative suffix synchronously, reports its successful application frontier, and ignores sequences replayed by other Pages.
|
|
10
|
+
/** Forwards a native lifecycle with its original Page receiver. */
|
|
11
|
+
function forward(handler, receiver, args) {
|
|
12
|
+
if (typeof handler === 'function')
|
|
13
|
+
handler.apply(receiver, args);
|
|
14
|
+
}
|
|
15
|
+
/** Narrows the untyped element returned across the Taro connection boundary. */
|
|
16
|
+
function isTaroRoot(value) {
|
|
17
|
+
return typeof value === 'object' && value !== null && 'ctx' in value;
|
|
18
|
+
}
|
|
19
|
+
/** Shared no-op CSS contract because physical rebuilds replace styles wholesale. */
|
|
20
|
+
const hotContextInternals = Object.freeze({
|
|
21
|
+
updateStyle() { },
|
|
22
|
+
removeStyle() { }
|
|
23
|
+
});
|
|
10
24
|
/**
|
|
11
25
|
* Per-module hot state, mirroring Rolldown's web runtime: accept is a passive registration;
|
|
12
26
|
* the propagation invokes the previous execution's callbacks with the fresh exports.
|
|
13
27
|
*/
|
|
14
28
|
class WxHotContext {
|
|
15
|
-
_internal =
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
_internal = hotContextInternals;
|
|
30
|
+
/**
|
|
31
|
+
* Callbacks registered by this module execution. The array is allocated on first accept,
|
|
32
|
+
* remains attached to the old execution across cache eviction, and is invoked by the next
|
|
33
|
+
* boundary execution. Undefined means this context stays passive.
|
|
34
|
+
*/
|
|
35
|
+
acceptCallbacks;
|
|
36
|
+
/** Stable module identity and shared sparse index used only when this context accepts. */
|
|
37
|
+
moduleId;
|
|
38
|
+
acceptingContexts;
|
|
39
|
+
constructor(moduleId, acceptingContexts) {
|
|
40
|
+
this.moduleId = moduleId;
|
|
41
|
+
this.acceptingContexts = acceptingContexts;
|
|
25
42
|
}
|
|
26
|
-
|
|
27
|
-
|
|
43
|
+
accept(callback) {
|
|
44
|
+
if (!this.acceptCallbacks) {
|
|
45
|
+
this.acceptCallbacks = [callback];
|
|
46
|
+
this.acceptingContexts.set(this.moduleId, this);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.acceptCallbacks.push(callback);
|
|
28
50
|
}
|
|
29
|
-
/** Invokes this old context's callbacks
|
|
51
|
+
/** Invokes this old context's callbacks; invalidation aborts the update synchronously. */
|
|
30
52
|
runAccept(moduleExports) {
|
|
31
|
-
|
|
32
|
-
|
|
53
|
+
const callbacks = this.acceptCallbacks;
|
|
54
|
+
if (!callbacks) {
|
|
55
|
+
throw new Error(`passive hot context reached as boundary: ${this.moduleId}`);
|
|
56
|
+
}
|
|
57
|
+
for (const callback of callbacks) {
|
|
58
|
+
callback?.(moduleExports);
|
|
33
59
|
}
|
|
34
|
-
return this.invalidationReason;
|
|
35
60
|
}
|
|
36
61
|
invalidate(reason) {
|
|
37
|
-
|
|
62
|
+
throw new Error(reason ?? 'the accepting module invalidated the update');
|
|
38
63
|
}
|
|
39
64
|
// Vite's generated CSS module calls hot.prune with its style teardown; the physical
|
|
40
65
|
// rebuild replaces styles wholesale, so it is a no-op.
|
|
@@ -42,10 +67,17 @@ class WxHotContext {
|
|
|
42
67
|
}
|
|
43
68
|
/** The WX host: extends the Rolldown contract instead of reimplementing it. */
|
|
44
69
|
class WxDevRuntime extends DevRuntime {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
70
|
+
/**
|
|
71
|
+
* One session for this App heap. Undefined only before app.js consumes hmr/info.js; its
|
|
72
|
+
* identity and endpoint then stay fixed while appliedSeq records the committed frontier.
|
|
73
|
+
*/
|
|
74
|
+
session;
|
|
75
|
+
/**
|
|
76
|
+
* Sparse current-generation accepting boundaries keyed by module id. Entries alone must
|
|
77
|
+
* outlive Rolldown module-cache eviction so old callbacks can receive fresh exports.
|
|
78
|
+
* createModuleHotContext removes the prior generation immediately; first accept inserts
|
|
79
|
+
* the new one. Passive contexts are never retained, so space is O(number of boundaries).
|
|
80
|
+
*/
|
|
49
81
|
moduleHotContexts = new Map();
|
|
50
82
|
constructor() {
|
|
51
83
|
// The base has no messenger: the engine tracks per-client shipped payloads instead
|
|
@@ -54,13 +86,13 @@ class WxDevRuntime extends DevRuntime {
|
|
|
54
86
|
}
|
|
55
87
|
/**
|
|
56
88
|
* Generated code always calls this before registerModule and reads `_internal` from the
|
|
57
|
-
* return value.
|
|
58
|
-
*
|
|
89
|
+
* return value. Each execution immediately retires its previous accepting boundary;
|
|
90
|
+
* calling accept registers the new context after the apply plan captured old callbacks.
|
|
59
91
|
*/
|
|
60
92
|
createModuleHotContext(moduleId) {
|
|
61
|
-
|
|
62
|
-
this.moduleHotContexts.
|
|
63
|
-
return
|
|
93
|
+
// A new execution supersedes the old boundary before it decides whether to accept.
|
|
94
|
+
this.moduleHotContexts.delete(moduleId);
|
|
95
|
+
return new WxHotContext(moduleId, this.moduleHotContexts);
|
|
64
96
|
}
|
|
65
97
|
/** Computes accepting boundaries and every executed module that must be re-armed. */
|
|
66
98
|
computeHmrUpdate(changedIds) {
|
|
@@ -84,7 +116,7 @@ class WxDevRuntime extends DevRuntime {
|
|
|
84
116
|
if (updateSet.has(moduleId))
|
|
85
117
|
return undefined;
|
|
86
118
|
updateSet.add(moduleId);
|
|
87
|
-
if (this.moduleHotContexts.
|
|
119
|
+
if (this.moduleHotContexts.has(moduleId)) {
|
|
88
120
|
boundaries.push(moduleId);
|
|
89
121
|
return undefined;
|
|
90
122
|
}
|
|
@@ -127,49 +159,120 @@ class WxDevRuntime extends DevRuntime {
|
|
|
127
159
|
this.removeModuleCache(moduleId);
|
|
128
160
|
}
|
|
129
161
|
for (const { moduleId, hotContext } of applies) {
|
|
130
|
-
|
|
131
|
-
if (invalidationReason) {
|
|
132
|
-
throw new Error(`${moduleId}: ${invalidationReason}`);
|
|
133
|
-
}
|
|
162
|
+
hotContext?.runAccept(this.initModule(moduleId));
|
|
134
163
|
}
|
|
135
164
|
}
|
|
136
165
|
/** Consumed once per App heap from hmr/info.js; the host buildId identifies its cumulative patch history. */
|
|
137
166
|
initialize(info) {
|
|
138
|
-
if (this.
|
|
167
|
+
if (this.session)
|
|
168
|
+
return;
|
|
169
|
+
this.session = { ...info, appliedSeq: 0 };
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* One-shot bridge to the application's Taro singleton. It cannot be imported into this
|
|
173
|
+
* separately bundled global runtime without creating a second Taro identity. Undefined
|
|
174
|
+
* only before the serve-only facade connection; route transactions share its lifetime.
|
|
175
|
+
*/
|
|
176
|
+
taro;
|
|
177
|
+
/** Connects HMR to the same Taro singleton used by the application module graph. */
|
|
178
|
+
connectTaro(current, document, injectPageInstance) {
|
|
179
|
+
if (this.taro) {
|
|
139
180
|
return;
|
|
140
181
|
}
|
|
141
|
-
this.
|
|
182
|
+
this.taro = {
|
|
183
|
+
current,
|
|
184
|
+
document,
|
|
185
|
+
injectPageInstance,
|
|
186
|
+
pageReplacements: new Map()
|
|
187
|
+
};
|
|
142
188
|
}
|
|
143
|
-
/**
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
189
|
+
/** Injects snapshot-preserving behavior into one route-specific Taro Page configuration. */
|
|
190
|
+
injectPageHmr(config, route) {
|
|
191
|
+
const originalOnUnload = config.onUnload;
|
|
192
|
+
const originalOnLoad = config.onLoad;
|
|
193
|
+
const originalOnShow = config.onShow;
|
|
194
|
+
const runtime = this;
|
|
195
|
+
config.onUnload = function (...args) {
|
|
196
|
+
const taro = runtime.requireTaro();
|
|
197
|
+
if (taro.pageReplacements.has(route)) {
|
|
198
|
+
taro.pageReplacements.set(route, {
|
|
199
|
+
$taroPath: this.$taroPath,
|
|
200
|
+
$taroParams: this.$taroParams,
|
|
201
|
+
// WeChat owns this serializable view-model. Keeping its reference is O(1),
|
|
202
|
+
// unlike cloning the complete recursive projection before every edit.
|
|
203
|
+
data: this.data
|
|
204
|
+
});
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
forward(originalOnUnload, this, args);
|
|
208
|
+
};
|
|
209
|
+
config.onLoad = function (...args) {
|
|
210
|
+
const taro = runtime.requireTaro();
|
|
211
|
+
const snapshot = taro.pageReplacements.get(route);
|
|
212
|
+
if (snapshot) {
|
|
213
|
+
// Replace the transaction before native work so exceptions cannot retain the
|
|
214
|
+
// large data snapshot while the route waits for its synthetic onShow.
|
|
215
|
+
taro.pageReplacements.set(route, null);
|
|
216
|
+
// Snapshot paint is the first bridge operation and removes the empty-page gap.
|
|
217
|
+
this.setData(snapshot.data);
|
|
218
|
+
this.$taroPath = snapshot.$taroPath;
|
|
219
|
+
this.$taroParams = snapshot.$taroParams;
|
|
220
|
+
runtime.bindPage(this, snapshot.$taroPath);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
forward(originalOnLoad, this, args);
|
|
224
|
+
};
|
|
225
|
+
config.onShow = function (...args) {
|
|
226
|
+
if (runtime.requireTaro().pageReplacements.delete(route)) {
|
|
227
|
+
// Synthetic shows must not repeat requests or reset application state.
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
forward(originalOnShow, this, args);
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/** Returns the Taro connection or fails at the first incorrectly ordered use. */
|
|
234
|
+
requireTaro() {
|
|
235
|
+
if (!this.taro)
|
|
236
|
+
throw new Error('[vpt] WX HMR used before the Taro runtime was connected');
|
|
237
|
+
return this.taro;
|
|
238
|
+
}
|
|
239
|
+
/** Returns a retained Taro root after normal Page mount has created it. */
|
|
240
|
+
findRoot(path) {
|
|
241
|
+
const root = this.requireTaro().document.getElementById(path);
|
|
242
|
+
return isTaroRoot(root) ? root : undefined;
|
|
148
243
|
}
|
|
149
|
-
/**
|
|
150
|
-
|
|
151
|
-
|
|
244
|
+
/** Rebinds a retained Taro tree to one replacement native Page without repainting. */
|
|
245
|
+
bindPage(instance, path) {
|
|
246
|
+
const taro = this.requireTaro();
|
|
247
|
+
taro.injectPageInstance(instance, path);
|
|
248
|
+
taro.current.page = instance;
|
|
249
|
+
const pageElement = this.findRoot(path);
|
|
250
|
+
if (!pageElement) {
|
|
251
|
+
throw new Error(`[vpt] retained Taro page not found: ${path}`);
|
|
252
|
+
}
|
|
253
|
+
pageElement.ctx = instance;
|
|
152
254
|
}
|
|
153
|
-
/** Applies one Page-delivered payload
|
|
154
|
-
applyPatches(payload) {
|
|
255
|
+
/** Applies one Page-delivered payload and arms its route for native replacement. */
|
|
256
|
+
applyPatches(payload, route) {
|
|
155
257
|
// The initial physical dependency exports undefined until the host has a patch range.
|
|
156
258
|
if (!payload)
|
|
157
259
|
return;
|
|
158
|
-
const
|
|
159
|
-
if (!
|
|
260
|
+
const session = this.session;
|
|
261
|
+
if (!session || payload.buildId !== session.buildId) {
|
|
160
262
|
console.warn('[vpt] patches for a stale build');
|
|
161
263
|
return;
|
|
162
264
|
}
|
|
163
|
-
// A
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
|
|
265
|
+
// A replayed payload still causes DevTools to replace this physical Page. Arm the
|
|
266
|
+
// route independently of whether this App heap already applied its patch sequence.
|
|
267
|
+
if (route) {
|
|
268
|
+
this.requireTaro().pageReplacements.set(route, null);
|
|
269
|
+
}
|
|
167
270
|
// Apply synchronously: the page's imports below the require resolve against the
|
|
168
271
|
// freshly registered modules, so the re-executed Page evaluates with the new code.
|
|
169
|
-
if (this.applyPatchBatch(payload.patches)) {
|
|
272
|
+
if (this.applyPatchBatch(session, payload.patches)) {
|
|
170
273
|
// The host may publish later generations while this synchronous apply runs. Reporting only afterward makes this
|
|
171
274
|
// the application frontier: publisher history is never pruned merely because its JavaScript file was observed.
|
|
172
|
-
void this.sendReport({ kind: 'applied', seq:
|
|
275
|
+
void this.sendReport({ kind: 'applied', seq: session.appliedSeq });
|
|
173
276
|
}
|
|
174
277
|
}
|
|
175
278
|
/**
|
|
@@ -185,10 +288,10 @@ class WxDevRuntime extends DevRuntime {
|
|
|
185
288
|
* - a second Page evaluating [5, 6, 7] skips the complete replay and leaves the latest factories untouched;
|
|
186
289
|
* - [5, 7] fails at the expected sequence 6, keeps appliedSeq 4, and requests a full rebuild because factory 6 is unrecoverable.
|
|
187
290
|
*/
|
|
188
|
-
applyPatchBatch(patches) {
|
|
291
|
+
applyPatchBatch(session, patches) {
|
|
189
292
|
// Keep the initial watermark immutable throughout the fold. Comparing replays against a moving watermark would allow
|
|
190
293
|
// a duplicate new sequence in the same payload to masquerade as an already-applied patch.
|
|
191
|
-
const previousSeq =
|
|
294
|
+
const previousSeq = session.appliedSeq;
|
|
192
295
|
// Mutable only during this synchronous pass. `nextSeq` validates continuity while `changedIds` unions every incremental
|
|
193
296
|
// patch's roots for the one final graph traversal; neither value escapes into persistent runtime state.
|
|
194
297
|
let nextSeq = previousSeq + 1;
|
|
@@ -223,7 +326,7 @@ class WxDevRuntime extends DevRuntime {
|
|
|
223
326
|
this.applyHmrUpdate(changedIds);
|
|
224
327
|
// Commit application only after graph propagation and boundary callbacks succeed. A failure leaves the old watermark
|
|
225
328
|
// intact and requests a full rebuild below, so a partially applied batch is never acknowledged as healthy.
|
|
226
|
-
|
|
329
|
+
session.appliedSeq = nextSeq - 1;
|
|
227
330
|
return true;
|
|
228
331
|
}
|
|
229
332
|
catch (error) {
|
|
@@ -237,17 +340,17 @@ class WxDevRuntime extends DevRuntime {
|
|
|
237
340
|
}
|
|
238
341
|
/** Sends one metadata-only report to the host; executable code never travels over HTTP. */
|
|
239
342
|
sendReport(data) {
|
|
240
|
-
const
|
|
241
|
-
if (!
|
|
343
|
+
const session = this.session;
|
|
344
|
+
if (!session) {
|
|
242
345
|
// A report without initialize is a programming error; fail loudly instead of
|
|
243
346
|
// silently dropping the sync traffic.
|
|
244
347
|
throw new Error('WX dev runtime is not initialized');
|
|
245
348
|
}
|
|
246
349
|
return new Promise((resolve, reject) => {
|
|
247
350
|
wx.request({
|
|
248
|
-
url:
|
|
351
|
+
url: session.endpoint,
|
|
249
352
|
method: 'POST',
|
|
250
|
-
data: { buildId:
|
|
353
|
+
data: { buildId: session.buildId, ...data },
|
|
251
354
|
header: { 'content-type': 'application/json' },
|
|
252
355
|
success() {
|
|
253
356
|
resolve();
|
|
@@ -261,30 +364,4 @@ class WxDevRuntime extends DevRuntime {
|
|
|
261
364
|
}
|
|
262
365
|
const runtime = new WxDevRuntime();
|
|
263
366
|
globalThis.__rolldown_runtime__ = runtime;
|
|
264
|
-
// Install the React DevTools hook before the Taro renderer injects itself: the runtime chunk
|
|
265
|
-
// is the first module of the App heap, and the renderer checks the hook when it evaluates at
|
|
266
|
-
// App mount. The refresh runtime's own injection (see react-refresh.ts) replays this hook's
|
|
267
|
-
// renderers, so the hook must store what inject receives — a real DevTools hook keeps the
|
|
268
|
-
// renderer in the renderers Map; without the stored renderer the replay captures nothing and
|
|
269
|
-
// Refresh has no renderer helpers to schedule re-renders on.
|
|
270
|
-
//
|
|
271
|
-
// The hook lives on `global`, and every free `__REACT_DEVTOOLS_GLOBAL_HOOK__` reference in
|
|
272
|
-
// react-family modules is rewritten to `global.__REACT_DEVTOOLS_GLOBAL_HOOK__`: the
|
|
273
|
-
// The WeChat runtime scope does not resolve free variables against `global` (verified: the free
|
|
274
|
-
// lookup is undefined while the member access exists), so the renderer would never inject
|
|
275
|
-
// otherwise. `??=` keeps a pre-installed hook (e.g. a real DevTools integration) intact; the
|
|
276
|
-
// runtime chunk's own lowering keeps the operator es2018-compatible.
|
|
277
|
-
const reactDevtoolsHook = {
|
|
278
|
-
renderers: new Map(),
|
|
279
|
-
supportsFiber: true,
|
|
280
|
-
inject: (injected) => {
|
|
281
|
-
const id = reactDevtoolsHook.renderers.size;
|
|
282
|
-
reactDevtoolsHook.renderers.set(id, injected);
|
|
283
|
-
return id;
|
|
284
|
-
},
|
|
285
|
-
onScheduleFiberRoot: () => { },
|
|
286
|
-
onCommitFiberRoot: () => { },
|
|
287
|
-
onCommitFiberUnmount: () => { }
|
|
288
|
-
};
|
|
289
|
-
global.__REACT_DEVTOOLS_GLOBAL_HOOK__ ??= reactDevtoolsHook;
|
|
290
367
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-taro",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"author": "sep2",
|
|
5
5
|
"description": "Vite 8 plugin for building one React/Taro codebase for WeChat Mini Program and H5 targets.",
|
|
6
6
|
"type": "module",
|
|
@@ -75,8 +75,8 @@
|
|
|
75
75
|
"rolldown": "1.2.3",
|
|
76
76
|
"tailwindcss": "^4.3.3",
|
|
77
77
|
"weapp-tailwindcss": "^5.2.11",
|
|
78
|
-
"@tarojs/react": "npm:vite-plugin-taro-react@0.5.
|
|
79
|
-
"@tarojs/
|
|
78
|
+
"@tarojs/plugin-framework-react": "npm:vite-plugin-taro-plugin-framework-react@0.5.7",
|
|
79
|
+
"@tarojs/react": "npm:vite-plugin-taro-react@0.5.7"
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
82
|
"react": "^19.0.0",
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type { PluginOption } from 'vite'
|
|
2
|
+
import { transformWithOxc } from 'vite'
|
|
3
|
+
import type { VitePluginTaroOptions } from '../../../../options.ts'
|
|
4
|
+
import { esTarget } from '../../../utils/constant.ts'
|
|
5
|
+
import { memoize } from '../../../utils/memoize.ts'
|
|
6
|
+
import { normalizeModuleId } from '../../../utils/modules.ts'
|
|
7
|
+
import { appCapsulePath, pageCapsulePath, rolldownRuntimeId, taroRuntimePath } from '../module.ts'
|
|
8
|
+
import { createWxDevHost, type WxDevHost } from './dev-host.ts'
|
|
9
|
+
import { createWxReactRefreshTransforms } from './react-refresh.ts'
|
|
10
|
+
|
|
11
|
+
const taroRuntimeId = '@tarojs/runtime'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Adds the serve-only bundled-development plugin set for the wx target: the dev adapter,
|
|
15
|
+
* Page HMR activation, and React Refresh adaptation transforms.
|
|
16
|
+
*/
|
|
17
|
+
export function createWxDevelopmentPlugin(options: VitePluginTaroOptions): PluginOption[] {
|
|
18
|
+
let host: WxDevHost | null = null
|
|
19
|
+
// Portable hook filters stay broad; these exact identities exclude similarly named user modules.
|
|
20
|
+
const normalizedAppCapsulePath = normalizeModuleId(appCapsulePath)
|
|
21
|
+
const normalizedPageCapsulePath = normalizeModuleId(pageCapsulePath)
|
|
22
|
+
const normalizedTaroRuntimePath = normalizeModuleId(taroRuntimePath)
|
|
23
|
+
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
name: 'vpt:wx-dev',
|
|
27
|
+
apply: 'serve',
|
|
28
|
+
|
|
29
|
+
config() {
|
|
30
|
+
return {
|
|
31
|
+
build: {
|
|
32
|
+
// Disable maps in resolved environment config as well as final output so Oxc and Babel skip producing
|
|
33
|
+
// intermediate maps that Rolldown would discard.
|
|
34
|
+
sourcemap: false
|
|
35
|
+
},
|
|
36
|
+
experimental: {
|
|
37
|
+
// Ask Vite to resolve its bundled-development graph and expose the private adapter instance. The wx
|
|
38
|
+
// configureServer hook replaces only its startup method with the directly writing DevEngine.
|
|
39
|
+
bundledDev: true
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
configureServer: {
|
|
45
|
+
// Install after Vite and user plugins have finished configuring the environment, but before server.listen()
|
|
46
|
+
// asks bundledDev to create its hard-coded skip-write DevEngine.
|
|
47
|
+
order: 'post',
|
|
48
|
+
async handler(server) {
|
|
49
|
+
host = await createWxDevHost({ server, options })
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
closeBundle() {
|
|
54
|
+
return host?.close()
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'vpt:wx-runtime-lowering',
|
|
59
|
+
apply: 'serve',
|
|
60
|
+
transform: {
|
|
61
|
+
order: 'post',
|
|
62
|
+
// The dev-mode transform assembles the runtime chunk (Rolldown's base runtime
|
|
63
|
+
// plus our injected implement) as this module's transform output, which
|
|
64
|
+
// bypasses the build's es2018 lowering. Real-device engines and WeChat's
|
|
65
|
+
// upload parser predate class fields and nullish operators, so the assembled
|
|
66
|
+
// runtime is lowered here — the only module that needs it. The exact id
|
|
67
|
+
// filter needs no code scan; the id must stay in sync with rolldownRuntimeId
|
|
68
|
+
// in module.ts (kept as a regex for the Rolldown-side filter).
|
|
69
|
+
filter: { id: /^\0rolldown\/runtime\.js(?:\?|$)/ },
|
|
70
|
+
handler(code) {
|
|
71
|
+
// The `setPublicClassFields` assumption emits plain `this.x = ...`
|
|
72
|
+
// assignments instead of external helpers, whose references the later
|
|
73
|
+
// minifier would mangle.
|
|
74
|
+
return fixRolldownRuntime(code)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'vpt:wx-react-refresh-bootstrap',
|
|
80
|
+
apply: 'serve',
|
|
81
|
+
transform: {
|
|
82
|
+
order: 'post',
|
|
83
|
+
filter: { id: /\/runtime\/wx\/capsule\/app\.js(?:\?|$)/ },
|
|
84
|
+
handler(code, id) {
|
|
85
|
+
if (normalizeModuleId(id) !== normalizedAppCapsulePath) return
|
|
86
|
+
return injectReactRefreshBootstrap(code)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'vpt:wx-page-hmr',
|
|
92
|
+
apply: 'serve',
|
|
93
|
+
transform: {
|
|
94
|
+
order: 'post',
|
|
95
|
+
filter: { id: /\/runtime\/wx\/capsule\/page\.js(?:\?|$)/ },
|
|
96
|
+
handler(code, id) {
|
|
97
|
+
if (normalizeModuleId(id) !== normalizedPageCapsulePath) return
|
|
98
|
+
return injectPageHmr(code, getPageRoute(id))
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'vpt:wx-taro-hmr',
|
|
104
|
+
apply: 'serve',
|
|
105
|
+
transform: {
|
|
106
|
+
order: 'post',
|
|
107
|
+
filter: { id: /\/runtime\/wx\/capsule\/taro-runtime\.js(?:\?|$)/ },
|
|
108
|
+
handler(code, id) {
|
|
109
|
+
if (normalizeModuleId(id) !== normalizedTaroRuntimePath) return
|
|
110
|
+
return injectTaroConnection(code)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
...createWxReactRefreshTransforms()
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Ensures the Refresh hook exists before React's renderer evaluates and injects itself. */
|
|
119
|
+
export function injectReactRefreshBootstrap(code: string): { code: string; map: null } {
|
|
120
|
+
return {
|
|
121
|
+
code: `import ${JSON.stringify('/@react-refresh')};\n${code}`,
|
|
122
|
+
map: null
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Connects the shared WX dev runtime to the application graph's Taro runtime instance. */
|
|
127
|
+
export function injectTaroConnection(code: string): { code: string; map: null } {
|
|
128
|
+
if (!/\bCurrent\b/.test(code) || !/\bdocument\b/.test(code) || !/\binjectPageInstance\b/.test(code)) {
|
|
129
|
+
throw new Error('WX Taro runtime must expose Current, document, and injectPageInstance for HMR')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const taroImport = `import { Current as __vptCurrent, document as __vptDocument, injectPageInstance as __vptInjectPageInstance } from ${JSON.stringify(taroRuntimeId)};`
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
code: `${code}\n${taroImport}\n__rolldown_runtime__.connectTaro(__vptCurrent, __vptDocument, __vptInjectPageInstance);`,
|
|
136
|
+
map: null
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Activates development-only lifecycle handling for one plugin-owned Page capsule. */
|
|
141
|
+
export function injectPageHmr(code: string, route: string): { code: string; map: null } {
|
|
142
|
+
if (!/\bconst\s+config\s*=/.test(code) || !/\bexport\s+default\s+config\b/.test(code)) {
|
|
143
|
+
throw new Error('WX Page capsule must declare and default-export config before HMR injection')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
code: `${code}\n__rolldown_runtime__.injectPageHmr(config, ${JSON.stringify(route)});`,
|
|
148
|
+
map: null
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Reads the stable route carried by every specialized Page capsule ID. */
|
|
153
|
+
function getPageRoute(id: string): string {
|
|
154
|
+
const queryIndex = id.indexOf('?')
|
|
155
|
+
const route = queryIndex < 0 ? null : new URLSearchParams(id.slice(queryIndex + 1)).get('route')
|
|
156
|
+
if (!route) throw new Error(`WX Page capsule is missing its route: ${id}`)
|
|
157
|
+
return route
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// The assembled runtime chunk is byte-identical on every build (the base runtime and the
|
|
161
|
+
// bundled implement are immutable for the server's lifetime), so the lowering runs once
|
|
162
|
+
// and every build reuses it.
|
|
163
|
+
const fixRolldownRuntime = memoize((code: string) => {
|
|
164
|
+
return transformWithOxc(code, rolldownRuntimeId, {
|
|
165
|
+
lang: 'js',
|
|
166
|
+
target: esTarget,
|
|
167
|
+
sourcemap: false,
|
|
168
|
+
assumptions: { setPublicClassFields: true }
|
|
169
|
+
})
|
|
170
|
+
})
|
|
@@ -25,8 +25,7 @@ const refreshRuntimeWindowGlobals = ['__registerBeforePerformReactRefresh', '__g
|
|
|
25
25
|
* @vitejs/plugin-react's generated refresh code assumes the web HTML preamble and a browser
|
|
26
26
|
* global scope; wx has neither. Each transform adapts one piece of that contract:
|
|
27
27
|
* - the refresh runtime module (id-filtered): the vendored runtime reads and assigns
|
|
28
|
-
* `window` protocol globals (rewritten to `global`) and must inject itself at evaluation
|
|
29
|
-
* — the preamble's `injectIntoGlobalHook` call has no HTML home in wx;
|
|
28
|
+
* `window` protocol globals (rewritten to `global`) and must inject itself at evaluation;
|
|
30
29
|
* - react-family modules (filtered on free references): the DevTools hook is read as a free
|
|
31
30
|
* variable, which the WeChat runtime scope never resolves against `global` — every free
|
|
32
31
|
* reference becomes an explicit member access;
|
|
@@ -93,7 +92,7 @@ export function createWxReactRefreshTransforms(): Plugin[] {
|
|
|
93
92
|
* The protocol name is unique, but only reference identifiers are rewritten. Declaration
|
|
94
93
|
* keys and explicit members such as `global.__REACT_DEVTOOLS_GLOBAL_HOOK__` must remain
|
|
95
94
|
* untouched; rewriting those would either produce invalid syntax or double-prefix the hook.
|
|
96
|
-
* The
|
|
95
|
+
* The eagerly evaluated refresh runtime creates the hook on `global` before the renderer loads.
|
|
97
96
|
*/
|
|
98
97
|
function createReactDevtoolsHookVisitor(editor: RolldownMagicString): WalkerEnter {
|
|
99
98
|
return function enter(node, parent) {
|
|
@@ -139,20 +138,18 @@ function createRefreshRuntimeVisitor(editor: RolldownMagicString): WalkerEnter {
|
|
|
139
138
|
|
|
140
139
|
return function enter(node) {
|
|
141
140
|
if (
|
|
142
|
-
node.type
|
|
143
|
-
node.computed
|
|
144
|
-
node.object.type
|
|
145
|
-
node.object.name
|
|
146
|
-
node.property.type
|
|
147
|
-
|
|
141
|
+
node.type === 'MemberExpression' &&
|
|
142
|
+
!node.computed &&
|
|
143
|
+
node.object.type === 'Identifier' &&
|
|
144
|
+
node.object.name === 'window' &&
|
|
145
|
+
node.property.type === 'Identifier' &&
|
|
146
|
+
refreshRuntimeWindowGlobals.some((globalName) => globalName === node.property.name)
|
|
148
147
|
) {
|
|
149
|
-
|
|
148
|
+
// `global` is the shared wx App heap used by the dev runtime and hook injection. Only
|
|
149
|
+
// replacing the object range preserves the vendored runtime byte-for-byte otherwise
|
|
150
|
+
// and prevents unrelated `window` expressions from being silently adapted.
|
|
151
|
+
editor.overwrite(node.object.start, node.object.end, 'global')
|
|
150
152
|
}
|
|
151
|
-
|
|
152
|
-
// `global` is the shared wx App heap used by the dev runtime and hook injection. Only
|
|
153
|
-
// replacing the object range preserves the vendored runtime byte-for-byte otherwise
|
|
154
|
-
// and prevents unrelated `window` expressions from being silently adapted.
|
|
155
|
-
editor.overwrite(node.object.start, node.object.end, 'global')
|
|
156
153
|
}
|
|
157
154
|
}
|
|
158
155
|
|
|
@@ -136,7 +136,8 @@ function createEntryBanner(pageFiles: ReadonlySet<string>): (chunk: { name: stri
|
|
|
136
136
|
}
|
|
137
137
|
if (pageFiles.has(chunk.name)) {
|
|
138
138
|
const patchesPath = path.posix.relative(path.posix.dirname(chunk.fileName), 'hmr/patches.js')
|
|
139
|
-
|
|
139
|
+
const route = chunk.name.slice(0, -'.js'.length)
|
|
140
|
+
return `__rolldown_runtime__.applyPatches(require('${patchesPath}'), ${JSON.stringify(route)});\n`
|
|
140
141
|
}
|
|
141
142
|
return ''
|
|
142
143
|
}
|
|
@@ -47,6 +47,9 @@ export const pageCapsuleId = '\0vpt:page-capsule'
|
|
|
47
47
|
/** Provides the Page capsule source specialized through a stable route query. */
|
|
48
48
|
export const pageCapsulePath = resolvePackageFile('dist/runtime/wx/capsule/page.js')
|
|
49
49
|
|
|
50
|
+
/** Identifies the Taro facade shared by the App, Page, and recursive Component capsules. */
|
|
51
|
+
export const taroRuntimePath = resolvePackageFile('dist/runtime/wx/capsule/taro-runtime.js')
|
|
52
|
+
|
|
50
53
|
/** Identifies the reusable synchronous native Page shell source. */
|
|
51
54
|
export const pageShellPath = resolvePackageFile('dist/runtime/wx/native/page.js')
|
|
52
55
|
|
|
@@ -3,7 +3,7 @@ import type { VitePluginTaroOptions } from '../../../options.ts'
|
|
|
3
3
|
import { esTarget } from '../../utils/constant.ts'
|
|
4
4
|
import { packageRequire } from '../../utils/packages.ts'
|
|
5
5
|
import { clientTaroNativeId } from '../client/constant.ts'
|
|
6
|
-
import { createWxDevelopmentPlugin } from './dev/
|
|
6
|
+
import { createWxDevelopmentPlugin } from './dev/plugins.ts'
|
|
7
7
|
import { getWxExecutionKind, isTransportModule } from './module.ts'
|
|
8
8
|
import { compileNativeComponentInterface } from './native/compile-native-component-interface.ts'
|
|
9
9
|
import { getNativeComponentAssetBytes } from './native/native-component-assets.ts'
|