vite-plugin-taro 0.6.0 → 0.6.2
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.en.md +1 -1
- package/README.md +1 -1
- package/dist/node/plugins/h5/transform-app.js +1 -1
- package/dist/node/plugins/wx/dev/plugins.js +1 -1
- package/dist/node/plugins/wx/dev/wx-dev-options.js +22 -1
- package/dist/node/plugins/wx/module/chunk-path.d.ts +8 -0
- package/dist/node/plugins/wx/module/chunk-path.js +18 -0
- package/dist/node/plugins/wx/{module.js → module/module.js} +2 -2
- package/dist/node/plugins/wx/native/create-native-component-output.d.ts +3 -1
- package/dist/node/plugins/wx/native/create-native-component-output.js +3 -5
- package/dist/node/plugins/wx/output/files.d.ts +3 -2
- package/dist/node/plugins/wx/output/files.js +2 -2
- package/dist/node/plugins/wx/output/json.d.ts +1 -1
- package/dist/node/plugins/wx/output/json.js +1 -1
- package/dist/node/plugins/wx/placer/placement.d.ts +51 -0
- package/dist/node/plugins/wx/placer/placement.js +245 -0
- package/dist/node/plugins/wx/placer/placer.d.ts +76 -0
- package/dist/node/plugins/wx/placer/placer.js +147 -0
- package/dist/node/plugins/wx/plugins.js +17 -17
- package/dist/node/plugins/wx/render/capsule.d.ts +35 -3
- package/dist/node/plugins/wx/render/capsule.js +53 -12
- package/dist/node/plugins/wx/render/native.d.ts +20 -2
- package/dist/node/plugins/wx/render/native.js +539 -35
- package/dist/node/plugins/wx/render/system-js/string-editor.d.ts +24 -0
- package/dist/node/plugins/wx/render/system-js/string-editor.js +93 -0
- package/dist/node/plugins/wx/render/system-js/system-js.d.ts +23 -0
- package/dist/node/plugins/wx/render/system-js/system-js.js +602 -0
- package/dist/node/plugins/wx/render/transport.d.ts +7 -3
- package/dist/node/plugins/wx/render/transport.js +19 -10
- package/dist/node/plugins/wx/resolve/resolver.js +1 -1
- package/dist/node/plugins/wx/resolve/specialize-bootstrap.js +1 -1
- package/dist/node/plugins/wx/resolve/specialize-page-capsule.js +1 -1
- package/dist/node/utils/modules.d.ts +0 -2
- package/dist/node/utils/modules.js +0 -7
- package/dist/node/utils/transform.d.ts +0 -3
- package/dist/node/utils/transform.js +0 -23
- package/package.json +8 -6
- package/src/node/plugins/h5/transform-app.ts +1 -1
- package/src/node/plugins/wx/dev/plugins.ts +1 -1
- package/src/node/plugins/wx/dev/wx-dev-options.ts +22 -1
- package/src/node/plugins/wx/module/chunk-path.ts +22 -0
- package/src/node/plugins/wx/{module.ts → module/module.ts} +2 -2
- package/src/node/plugins/wx/native/create-native-component-output.ts +6 -5
- package/src/node/plugins/wx/output/files.ts +5 -3
- package/src/node/plugins/wx/output/json.ts +1 -2
- package/src/node/plugins/wx/placer/placement.ts +364 -0
- package/src/node/plugins/wx/placer/placer.ts +179 -0
- package/src/node/plugins/wx/plugins.ts +19 -19
- package/src/node/plugins/wx/render/capsule.ts +53 -17
- package/src/node/plugins/wx/render/native.ts +670 -55
- package/src/node/plugins/wx/render/system-js/string-editor.ts +115 -0
- package/src/node/plugins/wx/render/system-js/system-js.ts +831 -0
- package/src/node/plugins/wx/render/transport.ts +25 -9
- package/src/node/plugins/wx/resolve/resolver.ts +1 -1
- package/src/node/plugins/wx/resolve/specialize-bootstrap.ts +1 -1
- package/src/node/plugins/wx/resolve/specialize-page-capsule.ts +1 -1
- package/src/node/utils/modules.ts +0 -8
- package/src/node/utils/transform.ts +0 -30
- package/dist/node/plugins/wx/placement/placer.d.ts +0 -78
- package/dist/node/plugins/wx/placement/placer.js +0 -158
- package/dist/node/plugins/wx/placement/plan.d.ts +0 -46
- package/dist/node/plugins/wx/placement/plan.js +0 -210
- package/dist/node/plugins/wx/render/capsule-wrapper.d.ts +0 -3
- package/dist/node/plugins/wx/render/capsule-wrapper.js +0 -64
- package/src/node/plugins/wx/placement/placer.ts +0 -187
- package/src/node/plugins/wx/placement/plan.ts +0 -306
- package/src/node/plugins/wx/render/capsule-wrapper.ts +0 -86
- /package/dist/node/plugins/wx/{module.d.ts → module/module.d.ts} +0 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import type { Rolldown } from 'vite'
|
|
3
|
+
|
|
4
|
+
// Leave headroom below WeChat's 2M subpackage limit for rendered wrappers and native assets.
|
|
5
|
+
export const subpackagePlanningBudget = 1_900_000
|
|
6
|
+
const generatedSubpackageRootPrefix = 'sub/p_'
|
|
7
|
+
|
|
8
|
+
/** Identifies one generated code-only subpackage by its physical output root. */
|
|
9
|
+
export type SubpackageLocation = {
|
|
10
|
+
/** Discriminates generated subpackages from main. */
|
|
11
|
+
kind: 'subpackage'
|
|
12
|
+
/** Native subpackage root relative to the Mini Program output directory. */
|
|
13
|
+
root: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Physical package ownership for one final Rolldown chunk. */
|
|
17
|
+
export type PackageLocation = { kind: 'main' } | SubpackageLocation
|
|
18
|
+
|
|
19
|
+
/** Native app.json declaration for one generated code-only subpackage. */
|
|
20
|
+
export type GeneratedSubpackage = {
|
|
21
|
+
/** Stable native alias derived from the generated root hash. */
|
|
22
|
+
name: string
|
|
23
|
+
/** Physical directory containing this subpackage's emitted capsules. */
|
|
24
|
+
root: string
|
|
25
|
+
/** Marks this as a code-only subpackage with no native Page routes. */
|
|
26
|
+
pages: readonly []
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Immutable ownership and materialization operations for one complete final-chunk graph. */
|
|
30
|
+
export type Placement = Readonly<{
|
|
31
|
+
getPackageLocation(chunk: Rolldown.RenderedChunk | Rolldown.OutputChunk): PackageLocation
|
|
32
|
+
getPhysicalChunkId(chunk: Rolldown.RenderedChunk): string
|
|
33
|
+
getLoadMode(chunk: Rolldown.RenderedChunk): 'sync' | 'async'
|
|
34
|
+
finalize(bundle: Rolldown.OutputBundle): readonly GeneratedSubpackage[]
|
|
35
|
+
}>
|
|
36
|
+
|
|
37
|
+
/** Shared main-package value used for every synchronously reachable chunk. */
|
|
38
|
+
const mainPackage = { kind: 'main' } as const
|
|
39
|
+
|
|
40
|
+
type PlaceableChunk = {
|
|
41
|
+
chunkId: string
|
|
42
|
+
estimatedBytes: number
|
|
43
|
+
transitions: ReadonlySet<number>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type ChunkBin = {
|
|
47
|
+
chunkIds: string[]
|
|
48
|
+
estimatedBytes: number
|
|
49
|
+
transitions: Set<number>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type BinCandidate = {
|
|
53
|
+
bin: ChunkBin
|
|
54
|
+
overlap: number
|
|
55
|
+
remainingBytes: number
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type PackedSubpackage = SubpackageLocation & {
|
|
59
|
+
chunkIds: readonly string[]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Applies Load-Transition Hypergraph Partitioning to Rolldown's final preliminary chunk graph:
|
|
64
|
+
*
|
|
65
|
+
* 1. Sort preliminary filenames to remove callback and object-enumeration order from every later decision.
|
|
66
|
+
* 2. Reserve every explicit entry and its complete static closure in main because native startup must load it synchronously.
|
|
67
|
+
* 3. Treat each dynamic-import edge as one load transition; the target's static closure is that transition's hyperedge.
|
|
68
|
+
* Nested dynamic edges remain separate transitions rather than being folded into the parent closure.
|
|
69
|
+
* 4. Index every lazy chunk by all transitions requiring it. Shared chunks therefore carry global demand rather than being
|
|
70
|
+
* assigned according to the first source module or dynamic root that happens to visit them.
|
|
71
|
+
* 5. Order chunks by transition demand, estimated emitted bytes, then preliminary filename. Place each chunk once into the
|
|
72
|
+
* fitting bin with maximum transition overlap, using best-fit remaining capacity only as a tie-breaker.
|
|
73
|
+
* 6. Hash each bin's sorted preliminary filenames into a deterministic physical package root and return unique ownership.
|
|
74
|
+
*
|
|
75
|
+
* Analysis costs the sum of transition static-closure traversals. Packing scans fitting bins and intersects sparse
|
|
76
|
+
* transition sets; its worst case is O(CBT), while practical graphs have few bins and sparse transition membership.
|
|
77
|
+
*/
|
|
78
|
+
export function createPlacement({
|
|
79
|
+
chunks,
|
|
80
|
+
getAdditionalModuleBytes
|
|
81
|
+
}: {
|
|
82
|
+
chunks: Readonly<Record<string, Rolldown.RenderedChunk>>
|
|
83
|
+
getAdditionalModuleBytes(moduleId: string): number
|
|
84
|
+
}): Placement {
|
|
85
|
+
const chunkById = new Map(Object.entries(chunks).sort(([left], [right]) => left.localeCompare(right)))
|
|
86
|
+
const mainChunkIds = findMainChunkIds(chunkById)
|
|
87
|
+
const transitionsByChunk = collectTransitionsByChunk({ chunks: chunkById, mainChunkIds: mainChunkIds })
|
|
88
|
+
const placeableChunks = [...chunkById]
|
|
89
|
+
.filter(([chunkId]) => !mainChunkIds.has(chunkId))
|
|
90
|
+
.map(([chunkId, chunk]) => ({
|
|
91
|
+
chunkId: chunkId,
|
|
92
|
+
estimatedBytes:
|
|
93
|
+
estimateChunkBytes(chunk) +
|
|
94
|
+
chunk.moduleIds.reduce((bytes, moduleId) => bytes + getAdditionalModuleBytes(moduleId), 0),
|
|
95
|
+
transitions: transitionsByChunk.get(chunkId) ?? new Set<number>()
|
|
96
|
+
}))
|
|
97
|
+
const subpackages = packChunks({
|
|
98
|
+
chunks: placeableChunks,
|
|
99
|
+
planningBudgetBytes: subpackagePlanningBudget
|
|
100
|
+
}).map(createPackedSubpackage)
|
|
101
|
+
|
|
102
|
+
// This local map accumulates the immutable plan returned to output materialization.
|
|
103
|
+
const locationByChunk = new Map<string, PackageLocation>()
|
|
104
|
+
for (const chunkId of mainChunkIds) {
|
|
105
|
+
locationByChunk.set(chunkId, mainPackage)
|
|
106
|
+
}
|
|
107
|
+
for (const subpackage of subpackages) {
|
|
108
|
+
for (const chunkId of subpackage.chunkIds) {
|
|
109
|
+
locationByChunk.set(chunkId, subpackage)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function getLocation(chunkId: string): PackageLocation {
|
|
113
|
+
const location = locationByChunk.get(chunkId)
|
|
114
|
+
if (!location) {
|
|
115
|
+
throw new Error(`wx placement is missing final chunk: ${chunkId}`)
|
|
116
|
+
}
|
|
117
|
+
return location
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Resolves typed ownership from Rolldown's preliminary physical filename before or after finalization. */
|
|
121
|
+
function getPackageLocation(chunk: Rolldown.RenderedChunk | Rolldown.OutputChunk): PackageLocation {
|
|
122
|
+
return getLocation('preliminaryFileName' in chunk ? chunk.preliminaryFileName : chunk.fileName)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
getPackageLocation: getPackageLocation,
|
|
127
|
+
|
|
128
|
+
/** Adds the planned package root to a physical preliminary path without changing the chunk's SystemJS identity. */
|
|
129
|
+
getPhysicalChunkId(chunk: Rolldown.RenderedChunk): string {
|
|
130
|
+
const location = getPackageLocation(chunk)
|
|
131
|
+
return location.kind === 'main' ? chunk.fileName : `${location.root}/${chunk.fileName}`
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
/** Selects the native loading API directly from typed package ownership. */
|
|
135
|
+
getLoadMode(chunk: Rolldown.RenderedChunk): 'sync' | 'async' {
|
|
136
|
+
return getPackageLocation(chunk).kind === 'subpackage' ? 'async' : 'sync'
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
/** Assigns each final chunk's Rolldown-owned physical filename and declares typed owners that survived output. */
|
|
140
|
+
finalize(bundle: Rolldown.OutputBundle): readonly GeneratedSubpackage[] {
|
|
141
|
+
const outputChunks = Object.values(bundle).filter(
|
|
142
|
+
(output): output is Rolldown.OutputChunk => output.type === 'chunk'
|
|
143
|
+
)
|
|
144
|
+
// This local mutable set deduplicates typed package owners that retain at least one final output chunk.
|
|
145
|
+
const roots = new Set<string>()
|
|
146
|
+
for (const chunk of outputChunks) {
|
|
147
|
+
// OutputChunk.fileName contains the resolved content hash. preliminaryFileName preserves the exact physical
|
|
148
|
+
// candidate with placeholders that identified this chunk when placement was created during renderChunk.
|
|
149
|
+
const location = getLocation(chunk.preliminaryFileName)
|
|
150
|
+
if (location.kind !== 'subpackage') {
|
|
151
|
+
continue
|
|
152
|
+
}
|
|
153
|
+
// OutputChunk is mutable in generateBundle. Assigning fileName makes Rolldown retain all chunk metadata and
|
|
154
|
+
// write that same chunk at its physical package path; deleting bundle keys or re-emitting would lose identity.
|
|
155
|
+
chunk.fileName = `${location.root}/${chunk.fileName}`
|
|
156
|
+
roots.add(location.root)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return [...roots].sort().map((root) => ({
|
|
160
|
+
name: root.slice(root.lastIndexOf('/') + 1),
|
|
161
|
+
root: root,
|
|
162
|
+
pages: []
|
|
163
|
+
}))
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Keeps every explicit output entry and its complete static chunk closure in main. */
|
|
169
|
+
function findMainChunkIds(chunks: ReadonlyMap<string, Rolldown.RenderedChunk>): Set<string> {
|
|
170
|
+
const mainChunkIds = new Set<string>()
|
|
171
|
+
// The worklist avoids recursion and visits every eager static edge once.
|
|
172
|
+
const pending = [...chunks].filter(([, chunk]) => chunk.isEntry).map(([chunkId]) => chunkId)
|
|
173
|
+
while (pending.length > 0) {
|
|
174
|
+
const chunkId = pending.pop()
|
|
175
|
+
if (!chunkId || mainChunkIds.has(chunkId)) {
|
|
176
|
+
continue
|
|
177
|
+
}
|
|
178
|
+
const chunk = chunks.get(chunkId)
|
|
179
|
+
if (!chunk) {
|
|
180
|
+
continue
|
|
181
|
+
}
|
|
182
|
+
mainChunkIds.add(chunkId)
|
|
183
|
+
pending.push(...chunk.imports)
|
|
184
|
+
}
|
|
185
|
+
return mainChunkIds
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Creates every load-transition hyperedge and indexes its static closure by chunk. */
|
|
189
|
+
function collectTransitionsByChunk({
|
|
190
|
+
chunks,
|
|
191
|
+
mainChunkIds
|
|
192
|
+
}: {
|
|
193
|
+
chunks: ReadonlyMap<string, Rolldown.RenderedChunk>
|
|
194
|
+
mainChunkIds: ReadonlySet<string>
|
|
195
|
+
}): Map<string, ReadonlySet<number>> {
|
|
196
|
+
const transitionsByChunk = new Map<string, Set<number>>()
|
|
197
|
+
let transitionId = 0
|
|
198
|
+
for (const chunk of chunks.values()) {
|
|
199
|
+
for (const targetId of [...chunk.dynamicImports].sort()) {
|
|
200
|
+
if (!chunks.has(targetId) || mainChunkIds.has(targetId)) {
|
|
201
|
+
continue
|
|
202
|
+
}
|
|
203
|
+
for (const chunkId of collectStaticClosure({
|
|
204
|
+
rootId: targetId,
|
|
205
|
+
chunks: chunks,
|
|
206
|
+
mainChunkIds: mainChunkIds
|
|
207
|
+
})) {
|
|
208
|
+
const transitions = transitionsByChunk.get(chunkId) ?? new Set<number>()
|
|
209
|
+
transitions.add(transitionId)
|
|
210
|
+
transitionsByChunk.set(chunkId, transitions)
|
|
211
|
+
}
|
|
212
|
+
transitionId++
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return transitionsByChunk
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Collects one transition's static closure; nested dynamic imports remain independent transitions. */
|
|
219
|
+
function collectStaticClosure({
|
|
220
|
+
rootId,
|
|
221
|
+
chunks,
|
|
222
|
+
mainChunkIds
|
|
223
|
+
}: {
|
|
224
|
+
rootId: string
|
|
225
|
+
chunks: ReadonlyMap<string, Rolldown.RenderedChunk>
|
|
226
|
+
mainChunkIds: ReadonlySet<string>
|
|
227
|
+
}): string[] {
|
|
228
|
+
const closure = new Set<string>()
|
|
229
|
+
// The worklist follows static edges only; the visited set terminates cycles.
|
|
230
|
+
const pending = [rootId]
|
|
231
|
+
while (pending.length > 0) {
|
|
232
|
+
const chunkId = pending.pop()
|
|
233
|
+
if (!chunkId || closure.has(chunkId) || mainChunkIds.has(chunkId)) {
|
|
234
|
+
continue
|
|
235
|
+
}
|
|
236
|
+
const chunk = chunks.get(chunkId)
|
|
237
|
+
if (!chunk) {
|
|
238
|
+
continue
|
|
239
|
+
}
|
|
240
|
+
closure.add(chunkId)
|
|
241
|
+
pending.push(...chunk.imports)
|
|
242
|
+
}
|
|
243
|
+
return [...closure].sort()
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Partitions final chunks by transition overlap before best-fit capacity. */
|
|
247
|
+
function packChunks({
|
|
248
|
+
chunks,
|
|
249
|
+
planningBudgetBytes
|
|
250
|
+
}: {
|
|
251
|
+
chunks: readonly PlaceableChunk[]
|
|
252
|
+
planningBudgetBytes: number
|
|
253
|
+
}): ChunkBin[] {
|
|
254
|
+
const bins: ChunkBin[] = []
|
|
255
|
+
const orderedChunks = [...chunks].sort(compareChunks)
|
|
256
|
+
for (const chunk of orderedChunks) {
|
|
257
|
+
const bin =
|
|
258
|
+
chunk.estimatedBytes <= planningBudgetBytes
|
|
259
|
+
? (findBestBin({ bins: bins, chunk: chunk, planningBudgetBytes: planningBudgetBytes }) ??
|
|
260
|
+
createBin(bins))
|
|
261
|
+
: createBin(bins)
|
|
262
|
+
placeChunk(bin, chunk)
|
|
263
|
+
}
|
|
264
|
+
return bins
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Gives globally shared transition demand priority, then size and stable preliminary filename. */
|
|
268
|
+
function compareChunks(left: PlaceableChunk, right: PlaceableChunk): number {
|
|
269
|
+
return (
|
|
270
|
+
right.transitions.size - left.transitions.size ||
|
|
271
|
+
right.estimatedBytes - left.estimatedBytes ||
|
|
272
|
+
left.chunkId.localeCompare(right.chunkId)
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Chooses maximum transition overlap, then the fullest fitting package and stable creation order. */
|
|
277
|
+
function findBestBin({
|
|
278
|
+
bins,
|
|
279
|
+
chunk,
|
|
280
|
+
planningBudgetBytes
|
|
281
|
+
}: {
|
|
282
|
+
bins: readonly ChunkBin[]
|
|
283
|
+
chunk: PlaceableChunk
|
|
284
|
+
planningBudgetBytes: number
|
|
285
|
+
}): ChunkBin | undefined {
|
|
286
|
+
let best: BinCandidate | undefined
|
|
287
|
+
for (const bin of bins) {
|
|
288
|
+
if (bin.estimatedBytes + chunk.estimatedBytes > planningBudgetBytes) {
|
|
289
|
+
continue
|
|
290
|
+
}
|
|
291
|
+
const candidate = {
|
|
292
|
+
bin: bin,
|
|
293
|
+
overlap: countOverlap(bin.transitions, chunk.transitions),
|
|
294
|
+
remainingBytes: planningBudgetBytes - bin.estimatedBytes - chunk.estimatedBytes
|
|
295
|
+
}
|
|
296
|
+
if (
|
|
297
|
+
!best ||
|
|
298
|
+
candidate.overlap > best.overlap ||
|
|
299
|
+
(candidate.overlap === best.overlap && candidate.remainingBytes < best.remainingBytes)
|
|
300
|
+
) {
|
|
301
|
+
best = candidate
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return best?.bin
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Counts transition-package incidences removed by one candidate placement. */
|
|
308
|
+
function countOverlap(left: ReadonlySet<number>, right: ReadonlySet<number>): number {
|
|
309
|
+
const [smaller, larger] = left.size <= right.size ? [left, right] : [right, left]
|
|
310
|
+
let overlap = 0
|
|
311
|
+
for (const transition of smaller) {
|
|
312
|
+
if (larger.has(transition)) {
|
|
313
|
+
overlap++
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return overlap
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Creates one planner-local mutable package. */
|
|
320
|
+
function createBin(bins: ChunkBin[]): ChunkBin {
|
|
321
|
+
const bin: ChunkBin = {
|
|
322
|
+
chunkIds: [],
|
|
323
|
+
estimatedBytes: 0,
|
|
324
|
+
transitions: new Set<number>()
|
|
325
|
+
}
|
|
326
|
+
bins.push(bin)
|
|
327
|
+
return bin
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** Applies one irreversible, non-duplicating final chunk assignment. */
|
|
331
|
+
function placeChunk(bin: ChunkBin, chunk: PlaceableChunk): void {
|
|
332
|
+
bin.chunkIds.push(chunk.chunkId)
|
|
333
|
+
bin.estimatedBytes += chunk.estimatedBytes
|
|
334
|
+
for (const transition of chunk.transitions) {
|
|
335
|
+
bin.transitions.add(transition)
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** Estimates final output bytes from tree-shaken modules plus a bounded generated-chunk allowance. */
|
|
340
|
+
function estimateChunkBytes(chunk: Rolldown.RenderedChunk): number {
|
|
341
|
+
const renderedModuleBytes = Object.values(chunk.modules).reduce(
|
|
342
|
+
(bytes, module) => bytes + (module.code ? Buffer.byteLength(module.code, 'utf8') : 0),
|
|
343
|
+
0
|
|
344
|
+
)
|
|
345
|
+
const moduleWrapperBytes = chunk.moduleIds.length * 64
|
|
346
|
+
const referenceBytes = (chunk.imports.length + chunk.dynamicImports.length) * 32
|
|
347
|
+
return renderedModuleBytes + moduleWrapperBytes + referenceBytes + 64
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Freezes membership and derives a stable root from sorted preliminary filenames. */
|
|
351
|
+
function createPackedSubpackage(bin: ChunkBin): PackedSubpackage {
|
|
352
|
+
const chunkIds = [...bin.chunkIds].sort()
|
|
353
|
+
const hash = createHash('sha256').update(chunkIds.join('\0')).digest('hex').slice(0, 8)
|
|
354
|
+
return {
|
|
355
|
+
kind: 'subpackage',
|
|
356
|
+
root: `${generatedSubpackageRootPrefix}${hash}`,
|
|
357
|
+
chunkIds: chunkIds
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** Tests the plugin-owned output prefix that physically identifies every generated subpackage. */
|
|
362
|
+
export function isGeneratedSubpackageFile(fileName: string): boolean {
|
|
363
|
+
return fileName.startsWith(generatedSubpackageRootPrefix)
|
|
364
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { normalizePath, type Plugin, type Rolldown } from 'vite'
|
|
2
|
+
import { getWxExecutionKind, isTransportModule } from '../module/module.ts'
|
|
3
|
+
import { getNativeComponentAssetBytes } from '../native/native-component-assets.ts'
|
|
4
|
+
import { createPlacement, type GeneratedSubpackage, type PackageLocation, type Placement } from './placement.ts'
|
|
5
|
+
|
|
6
|
+
export type { GeneratedSubpackage, Placement } from './placement.ts'
|
|
7
|
+
|
|
8
|
+
const pnpmFrameworkPackagePattern = /\/node_modules\/\.pnpm\/(?:@tarojs\+|react(?:-dom|-reconciler)?@|scheduler@)/
|
|
9
|
+
const workspaceFrameworkPackagePattern = /\/packages\/(?:taro-react|taro-plugin-framework-react)\//
|
|
10
|
+
|
|
11
|
+
/** Selects the explicit React/Taro roots whose complete dependency closure forms the framework vendor chunk. */
|
|
12
|
+
export function isWxFrameworkVendorModule(moduleId: string): boolean {
|
|
13
|
+
const normalizedId = normalizePath(moduleId)
|
|
14
|
+
return pnpmFrameworkPackagePattern.test(normalizedId) || workspaceFrameworkPackagePattern.test(normalizedId)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type PlacementState =
|
|
18
|
+
| { phase: 'idle' }
|
|
19
|
+
| { phase: 'awaiting-chunks' }
|
|
20
|
+
| { phase: 'planned'; placement: Placement }
|
|
21
|
+
| { phase: 'finalized'; placement: Placement; subpackages: readonly GeneratedSubpackage[] }
|
|
22
|
+
|
|
23
|
+
/** Placement services consumed by the later `vpt:wx` rendering and output hooks. */
|
|
24
|
+
export type WxPlacementPlugin = Plugin &
|
|
25
|
+
Readonly<{
|
|
26
|
+
getPackageLocation(chunk: Rolldown.RenderedChunk | Rolldown.OutputChunk): PackageLocation
|
|
27
|
+
getPhysicalChunkId(chunk: Rolldown.RenderedChunk): string
|
|
28
|
+
getLoadMode(chunk: Rolldown.RenderedChunk): 'sync' | 'async'
|
|
29
|
+
getSubpackages(): readonly GeneratedSubpackage[]
|
|
30
|
+
}>
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Rolldown options owned by WX placement. Every field enforces a distinct output invariant. The plugin returns this object
|
|
34
|
+
* from its config hook, while direct Rolldown integration tests reuse the same value to exercise the identical lifecycle.
|
|
35
|
+
*/
|
|
36
|
+
export const placementRolldownOptions = {
|
|
37
|
+
/**
|
|
38
|
+
* Output-stage naming remains under Rolldown's ownership. These options establish physical candidates and hash
|
|
39
|
+
* participation only; LTHP mutates the resulting OutputChunk filenames later without replacing the chunks.
|
|
40
|
+
*/
|
|
41
|
+
output: {
|
|
42
|
+
/**
|
|
43
|
+
* React and Taro form one stable framework boundary shared by the App and every Page capsule. Keeping their complete
|
|
44
|
+
* dependency closure together prevents application edits from invalidating framework chunk identity and makes later
|
|
45
|
+
* development generations eligible to reuse the unchanged vendor. All remaining modules use Rolldown's automatic
|
|
46
|
+
* chunking; physical WX package placement operates on those final chunks without imposing another split strategy.
|
|
47
|
+
*/
|
|
48
|
+
codeSplitting: {
|
|
49
|
+
groups: [
|
|
50
|
+
{
|
|
51
|
+
name: 'vendor',
|
|
52
|
+
test: isWxFrameworkVendorModule,
|
|
53
|
+
priority: 100,
|
|
54
|
+
includeDependenciesRecursively: true
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
/**
|
|
59
|
+
* Native App/Page/Component shells are files addressed directly by WeChat and must retain the exact names configured
|
|
60
|
+
* in `input`, such as `app.js` and `pages/home/index.js`. Transport is excluded even though it is CommonJS:
|
|
61
|
+
* application chunks import its content-hashed path, so it belongs with hashed runtime/capsule entries. `[hash]`
|
|
62
|
+
* remains a Rolldown placeholder here and is resolved only after renderChunk transforms finish.
|
|
63
|
+
*/
|
|
64
|
+
entryFileNames(chunk: Rolldown.PreRenderedChunk): string {
|
|
65
|
+
return getWxExecutionKind(chunk) === 'native' && !isTransportModule(chunk)
|
|
66
|
+
? '[name]'
|
|
67
|
+
: 'assets/[name]-[hash].js'
|
|
68
|
+
},
|
|
69
|
+
/**
|
|
70
|
+
* Leaves chunk identity and collision handling entirely to Rolldown. This package-neutral physical pattern deliberately
|
|
71
|
+
* contains no LTHP owner; generateBundle adds only the selected package root to the existing Rolldown filename.
|
|
72
|
+
*/
|
|
73
|
+
chunkFileNames: 'assets/[name]-[hash].js',
|
|
74
|
+
/**
|
|
75
|
+
* Emits generic Rolldown assets under one collision-resistant hashed namespace. Native-component folders are not
|
|
76
|
+
* governed by this option: createNativeComponentOutput preserves their required relative filenames and relocates the
|
|
77
|
+
* complete folder beside its owning JavaScript chunk after LTHP finalization.
|
|
78
|
+
*/
|
|
79
|
+
assetFileNames: 'assets/[name]-[hash][extname]'
|
|
80
|
+
},
|
|
81
|
+
/**
|
|
82
|
+
* Keeps every native entry's required exports while allowing Rolldown to add cross-chunk bindings created by natural code
|
|
83
|
+
* splitting. `strict` can reject those extensions; `exports-only` can merge away native boundaries; `allow-extension`
|
|
84
|
+
* preserves the shell/capsule contract without forcing source-module placement groups.
|
|
85
|
+
*/
|
|
86
|
+
preserveEntrySignatures: 'allow-extension' as const
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Creates the `vpt:wx-placer` lifecycle owner:
|
|
91
|
+
*
|
|
92
|
+
* 1. Its config hook installs package-neutral Rolldown names and entry-signature semantics.
|
|
93
|
+
* 2. `renderStart` atomically starts a generation in `awaiting-chunks`; no stale placement remains reachable.
|
|
94
|
+
* 3. Its first pre-order `renderChunk` creates one immutable LTHP placement from the complete tree-shaken graph.
|
|
95
|
+
* 4. `vpt:wx` asks this plugin only for package ownership, physical relocation, and native loading mode.
|
|
96
|
+
* 5. Its pre-order `generateBundle` assigns each OutputChunk its package-qualified filename and publishes app.json declarations.
|
|
97
|
+
*
|
|
98
|
+
* The discriminated state is the only generation-local mutation: `idle → awaiting-chunks → planned → finalized`. Each hook
|
|
99
|
+
* performs one whole-state transition, so stale graph state, duplicate planning, and partially reset generations are
|
|
100
|
+
* unrepresentable.
|
|
101
|
+
*/
|
|
102
|
+
export function createWxPlacementPlugin(): WxPlacementPlugin {
|
|
103
|
+
// This one mutable cell is the output-generation state machine described above; hooks replace it atomically by phase.
|
|
104
|
+
let state: PlacementState = { phase: 'idle' }
|
|
105
|
+
|
|
106
|
+
function requirePlacement(): Placement {
|
|
107
|
+
if (state.phase === 'idle' || state.phase === 'awaiting-chunks') {
|
|
108
|
+
throw new Error('wx placement is unavailable before Rolldown exposes the final chunk graph')
|
|
109
|
+
}
|
|
110
|
+
return state.placement
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
name: 'vpt:wx-placer',
|
|
115
|
+
|
|
116
|
+
config() {
|
|
117
|
+
return {
|
|
118
|
+
build: {
|
|
119
|
+
rolldownOptions: placementRolldownOptions
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
renderStart() {
|
|
125
|
+
state = { phase: 'awaiting-chunks' }
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
renderChunk: {
|
|
129
|
+
order: 'pre',
|
|
130
|
+
handler(_code, _chunk, _outputOptions, meta) {
|
|
131
|
+
if (state.phase === 'planned') {
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
if (state.phase !== 'awaiting-chunks') {
|
|
135
|
+
throw new Error(`wx placement received final chunks during the ${state.phase} phase`)
|
|
136
|
+
}
|
|
137
|
+
state = {
|
|
138
|
+
phase: 'planned',
|
|
139
|
+
placement: createPlacement({
|
|
140
|
+
chunks: meta.chunks,
|
|
141
|
+
getAdditionalModuleBytes: (moduleId) =>
|
|
142
|
+
getNativeComponentAssetBytes(this.getModuleInfo(moduleId)?.meta)
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
generateBundle: {
|
|
149
|
+
order: 'pre',
|
|
150
|
+
handler(_outputOptions, bundle) {
|
|
151
|
+
const placement = requirePlacement()
|
|
152
|
+
state = {
|
|
153
|
+
phase: 'finalized',
|
|
154
|
+
placement: placement,
|
|
155
|
+
subpackages: placement.finalize(bundle)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
getPackageLocation(chunk: Rolldown.RenderedChunk | Rolldown.OutputChunk): PackageLocation {
|
|
161
|
+
return requirePlacement().getPackageLocation(chunk)
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
getPhysicalChunkId(chunk: Rolldown.RenderedChunk): string {
|
|
165
|
+
return requirePlacement().getPhysicalChunkId(chunk)
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
getLoadMode(chunk: Rolldown.RenderedChunk): 'sync' | 'async' {
|
|
169
|
+
return requirePlacement().getLoadMode(chunk)
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
getSubpackages(): readonly GeneratedSubpackage[] {
|
|
173
|
+
if (state.phase !== 'finalized') {
|
|
174
|
+
throw new Error('wx subpackages are unavailable before output finalization')
|
|
175
|
+
}
|
|
176
|
+
return state.subpackages
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -4,11 +4,10 @@ import { esTarget } from '../../utils/constant.ts'
|
|
|
4
4
|
import { packageRequire } from '../../utils/packages.ts'
|
|
5
5
|
import { clientTaroNativeId } from '../client/constant.ts'
|
|
6
6
|
import { createWxDevelopmentPlugin } from './dev/plugins.ts'
|
|
7
|
-
import { getWxExecutionKind, isTransportModule } from './module.ts'
|
|
7
|
+
import { getWxExecutionKind, isTransportModule } from './module/module.ts'
|
|
8
8
|
import { compileNativeComponentInterface } from './native/compile-native-component-interface.ts'
|
|
9
|
-
import { getNativeComponentAssetBytes } from './native/native-component-assets.ts'
|
|
10
9
|
import { createOutputFiles } from './output/files.ts'
|
|
11
|
-
import {
|
|
10
|
+
import { createWxPlacementPlugin, type WxPlacementPlugin } from './placer/placer.ts'
|
|
12
11
|
import { renderCapsule } from './render/capsule.ts'
|
|
13
12
|
import { renderNative } from './render/native.ts'
|
|
14
13
|
import { materializeTransport } from './render/transport.ts'
|
|
@@ -23,17 +22,18 @@ export function createWxTargetPlugins(options: VptOptions): PluginOption[] {
|
|
|
23
22
|
|
|
24
23
|
// Reuse the resolver instance's ordered application subset. Rolldown's complete input also contains bootstrap, transport,
|
|
25
24
|
// shell, and component entries; entry membership alone cannot recover which roots define the App/Page CSS cascade.
|
|
25
|
+
const placement = createWxPlacementPlugin()
|
|
26
|
+
|
|
26
27
|
return [
|
|
28
|
+
placement,
|
|
27
29
|
createWxStylePlugins(),
|
|
28
|
-
createWxPlugin(options, resolver),
|
|
30
|
+
createWxPlugin(options, resolver, placement),
|
|
29
31
|
createWxDevelopmentPlugin(options, resolver.applicationEntryIds)
|
|
30
32
|
]
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
/** Configures the complete wx target build pipeline. */
|
|
34
|
-
function createWxPlugin(options: VptOptions, resolver: WxResolver): Plugin {
|
|
35
|
-
const placer = createPlacer()
|
|
36
|
-
|
|
36
|
+
function createWxPlugin(options: VptOptions, resolver: WxResolver, placement: WxPlacementPlugin): Plugin {
|
|
37
37
|
return {
|
|
38
38
|
name: 'vpt:wx',
|
|
39
39
|
|
|
@@ -70,7 +70,8 @@ function createWxPlugin(options: VptOptions, resolver: WxResolver): Plugin {
|
|
|
70
70
|
target: esTarget,
|
|
71
71
|
|
|
72
72
|
rolldownOptions: {
|
|
73
|
-
|
|
73
|
+
// The dedicated vpt:wx-placer plugin owns output naming and entry-signature semantics. This plugin owns
|
|
74
|
+
// only the closed named input set of native shells, lifecycle capsules, bootstrap, and transport entries.
|
|
74
75
|
input: resolver.input
|
|
75
76
|
}
|
|
76
77
|
}
|
|
@@ -99,17 +100,11 @@ function createWxPlugin(options: VptOptions, resolver: WxResolver): Plugin {
|
|
|
99
100
|
}
|
|
100
101
|
},
|
|
101
102
|
|
|
102
|
-
renderStart() {
|
|
103
|
-
placer.analyze({
|
|
104
|
-
moduleIds: this.getModuleIds(),
|
|
105
|
-
getModuleInfo: (moduleId) => this.getModuleInfo(moduleId),
|
|
106
|
-
getAdditionalModuleBytes: (info) => getNativeComponentAssetBytes(info.meta)
|
|
107
|
-
})
|
|
108
|
-
},
|
|
109
|
-
|
|
110
103
|
renderChunk: {
|
|
111
104
|
order: 'post',
|
|
112
105
|
async handler(code, chunk, outputOptions, meta) {
|
|
106
|
+
// vpt:wx-placer runs first and has already created immutable placement from this complete chunk graph.
|
|
107
|
+
|
|
113
108
|
const executionKind = getWxExecutionKind(chunk)
|
|
114
109
|
const sourcemap = Boolean(outputOptions.sourcemap)
|
|
115
110
|
|
|
@@ -126,7 +121,8 @@ function createWxPlugin(options: VptOptions, resolver: WxResolver): Plugin {
|
|
|
126
121
|
code: native.code,
|
|
127
122
|
transportChunk: chunk,
|
|
128
123
|
chunks: meta.chunks,
|
|
129
|
-
getLoadMode:
|
|
124
|
+
getLoadMode: placement.getLoadMode,
|
|
125
|
+
getPhysicalChunkId: placement.getPhysicalChunkId,
|
|
130
126
|
sourcemap
|
|
131
127
|
})
|
|
132
128
|
}
|
|
@@ -144,13 +140,17 @@ function createWxPlugin(options: VptOptions, resolver: WxResolver): Plugin {
|
|
|
144
140
|
*/
|
|
145
141
|
order: 'post',
|
|
146
142
|
async handler(_, bundle) {
|
|
147
|
-
|
|
143
|
+
// LTHP joins OutputChunks to their preliminary logical IDs and assigns Rolldown-owned physical filenames.
|
|
144
|
+
// createOutputFiles then observes those paths to relocate native component folders, emit placeholders, and
|
|
145
|
+
// declare only surviving package roots in app.json. No JavaScript chunk is manually emitted or copied.
|
|
146
|
+
const subpackages = placement.getSubpackages()
|
|
148
147
|
|
|
149
148
|
const outputFiles = await createOutputFiles({
|
|
150
149
|
bundle,
|
|
151
150
|
options,
|
|
152
151
|
subpackages,
|
|
153
|
-
getModuleInfo: (moduleId) => this.getModuleInfo(moduleId)
|
|
152
|
+
getModuleInfo: (moduleId) => this.getModuleInfo(moduleId),
|
|
153
|
+
getPackageLocation: placement.getPackageLocation
|
|
154
154
|
})
|
|
155
155
|
|
|
156
156
|
outputFiles.forEach((file) => {
|