vite-plugin-taro 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.en.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/node/plugins/wx/chunk-path.d.ts +8 -0
  4. package/dist/node/plugins/wx/chunk-path.js +18 -0
  5. package/dist/node/plugins/wx/dev/wx-dev-options.js +21 -0
  6. package/dist/node/plugins/wx/native/create-native-component-output.d.ts +3 -1
  7. package/dist/node/plugins/wx/native/create-native-component-output.js +3 -5
  8. package/dist/node/plugins/wx/output/files.d.ts +3 -2
  9. package/dist/node/plugins/wx/output/files.js +2 -2
  10. package/dist/node/plugins/wx/output/json.d.ts +1 -1
  11. package/dist/node/plugins/wx/output/json.js +1 -1
  12. package/dist/node/plugins/wx/placer/placement.d.ts +50 -0
  13. package/dist/node/plugins/wx/placer/placement.js +245 -0
  14. package/dist/node/plugins/wx/placer/placer.d.ts +60 -0
  15. package/dist/node/plugins/wx/placer/placer.js +123 -0
  16. package/dist/node/plugins/wx/plugins.js +16 -16
  17. package/dist/node/plugins/wx/render/capsule-wrapper.js +3 -3
  18. package/dist/node/plugins/wx/render/native.js +5 -5
  19. package/dist/node/plugins/wx/render/transport.d.ts +7 -3
  20. package/dist/node/plugins/wx/render/transport.js +17 -8
  21. package/dist/node/utils/modules.d.ts +0 -2
  22. package/dist/node/utils/modules.js +0 -7
  23. package/package.json +3 -3
  24. package/src/node/plugins/wx/chunk-path.ts +22 -0
  25. package/src/node/plugins/wx/dev/wx-dev-options.ts +21 -0
  26. package/src/node/plugins/wx/native/create-native-component-output.ts +6 -5
  27. package/src/node/plugins/wx/output/files.ts +5 -3
  28. package/src/node/plugins/wx/output/json.ts +1 -2
  29. package/src/node/plugins/wx/placer/placement.ts +364 -0
  30. package/src/node/plugins/wx/placer/placer.ts +154 -0
  31. package/src/node/plugins/wx/plugins.ts +18 -18
  32. package/src/node/plugins/wx/render/capsule-wrapper.ts +3 -3
  33. package/src/node/plugins/wx/render/native.ts +5 -5
  34. package/src/node/plugins/wx/render/transport.ts +23 -7
  35. package/src/node/utils/modules.ts +0 -8
  36. package/dist/node/plugins/wx/placement/placer.d.ts +0 -78
  37. package/dist/node/plugins/wx/placement/placer.js +0 -158
  38. package/dist/node/plugins/wx/placement/plan.d.ts +0 -46
  39. package/dist/node/plugins/wx/placement/plan.js +0 -210
  40. package/src/node/plugins/wx/placement/placer.ts +0 -187
  41. package/src/node/plugins/wx/placement/plan.ts +0 -306
package/README.en.md CHANGED
@@ -36,7 +36,7 @@ Continue with the [Quick Start guide](https://vpt.js.org/guides/quick-start/).
36
36
  - [Hot module replacement](https://vpt.js.org/guides/hot-module-replacement/)
37
37
  - [Skyline mode](https://vpt.js.org/guides/skyline-mode/)
38
38
  - [Migrate from Taro CLI](https://vpt.js.org/guides/migrate-from-taro/)
39
- - [Configuration reference](https://vpt.js.org/references/configuration/)
39
+ - [Configuration reference](https://vpt.js.org/guides/configuration/)
40
40
  - [Repository management](https://vpt.js.org/references/repository-management/)
41
41
 
42
42
  ## License
package/README.md CHANGED
@@ -37,7 +37,7 @@ npm create vite-taro@latest my-app
37
37
  - [开发者工具热更新](https://vpt.js.org/guides/hot-module-replacement/)
38
38
  - [Skyline 模式](https://vpt.js.org/guides/skyline-mode/)
39
39
  - [从 Taro CLI 迁移](https://vpt.js.org/guides/migrate-from-taro/)
40
- - [配置参考](https://vpt.js.org/references/configuration/)
40
+ - [配置参考](https://vpt.js.org/guides/configuration/)
41
41
  - [仓库维护](https://vpt.js.org/references/repository-management/)
42
42
 
43
43
  ## 许可证
@@ -0,0 +1,8 @@
1
+ /** Physical directory in which Rolldown writes generated JavaScript chunks inside each native package. */
2
+ export declare const generatedChunkDirectory = "assets";
3
+ /** Projects one Rolldown-owned physical candidate path into the package-neutral SystemJS identity. */
4
+ export declare function toLogicalChunkId(physicalChunkId: string): string;
5
+ /** Resolves one relative Rolldown-generated import to its preliminary physical chunk path. */
6
+ export declare function resolvePhysicalChunkReference(importerChunkId: string, reference: string): string;
7
+ /** Projects one relative Rolldown-generated import into its package-neutral SystemJS identity. */
8
+ export declare function resolveLogicalChunkReference(importerChunkId: string, reference: string): string;
@@ -0,0 +1,18 @@
1
+ import path from 'node:path';
2
+ /** Physical directory in which Rolldown writes generated JavaScript chunks inside each native package. */
3
+ export const generatedChunkDirectory = 'assets';
4
+ /** Projects one Rolldown-owned physical candidate path into the package-neutral SystemJS identity. */
5
+ export function toLogicalChunkId(physicalChunkId) {
6
+ return path.posix.relative(generatedChunkDirectory, physicalChunkId);
7
+ }
8
+ /** Resolves one relative Rolldown-generated import to its preliminary physical chunk path. */
9
+ export function resolvePhysicalChunkReference(importerChunkId, reference) {
10
+ if (!reference.startsWith('./') && !reference.startsWith('../')) {
11
+ throw new Error(`Expected a relative chunk reference in ${importerChunkId}: ${reference}`);
12
+ }
13
+ return path.posix.join(path.posix.dirname(importerChunkId), reference);
14
+ }
15
+ /** Projects one relative Rolldown-generated import into its package-neutral SystemJS identity. */
16
+ export function resolveLogicalChunkReference(importerChunkId, reference) {
17
+ return toLogicalChunkId(resolvePhysicalChunkReference(importerChunkId, reference));
18
+ }
@@ -35,12 +35,26 @@ export function installWxDevOptions({ bundledDev, server, options, hostPlugins }
35
35
  * mutating configuredOutput itself would leak development normalization back into the user's resolved Vite config.
36
36
  */
37
37
  Object.assign(output, configured, {
38
+ // Development output is overwritten in place after every complete build. Strip hash placeholders from the
39
+ // configured asset pattern so old files cannot accumulate and native JSON/WXML references remain stable.
38
40
  assetFileNames: createStableFileNames(configured.assetFileNames, 'assets/[name][extname]'),
41
+ // Banners create physical CommonJS edges after graph analysis: App initializes the dev runtime and each Page
42
+ // consumes the stable patch journal without allowing those host-only files into the application chunk graph.
39
43
  banner: createEntryBanner(pageFiles),
44
+ // Preserve the configured directory/name shape while removing content hashes. Stable chunk paths let DevTools
45
+ // overwrite executable files and let cumulative HMR patches address one persistent physical module identity.
40
46
  chunkFileNames: createStableFileNames(configured.chunkFileNames, 'assets/[name].js'),
47
+ // Native entry paths are public Mini Program routes (`app.js`, `pages/.../index.js`); development must never hash
48
+ // or relocate them because DevTools determines App/Page reload behavior from those exact filenames.
41
49
  entryFileNames: createStableFileNames(configured.entryFileNames, '[name]'),
50
+ // Keep ESM until the existing WX renderChunk pipeline classifies each final chunk and converts capsules to
51
+ // System.register data or native/amphibious entries to CommonJS. Choosing CommonJS here would erase that boundary.
42
52
  format: 'es',
53
+ // Bundled development emits complete physical output repeatedly. Minifying bounds disk transfer and DevTools
54
+ // compile work; source-level HMR diagnostics still come from Vite/Rolldown before this final output pass.
43
55
  minify: true,
56
+ // DevTools executes physical WX files and HMR applies module factories rather than browser source maps. Disabling
57
+ // maps avoids extra output files and prevents Vite's Oxc sourcemap transform from touching generated host code.
44
58
  sourcemap: false
45
59
  });
46
60
  /*
@@ -50,9 +64,16 @@ export function installWxDevOptions({ bundledDev, server, options, hostPlugins }
50
64
  rolldownOptions.experimental ??= {};
51
65
  const existingDevMode = rolldownOptions.experimental.devMode;
52
66
  rolldownOptions.experimental.devMode = {
67
+ // Retain unknown user/forward-compatible devMode fields while the three explicit WX invariants below win.
53
68
  ...(typeof existingDevMode === 'object' ? existingDevMode : {}),
69
+ // Install the WX-adapted self-contained Rolldown runtime. It consumes physical patch journals and reports
70
+ // acknowledgements/rebuild requests through the host bridge instead of relying on browser globals or sockets.
54
71
  implement: await bundleRuntimeSource(),
72
+ // Produce a complete output graph on the initial build. Lazy per-request compilation cannot establish the closed
73
+ // App/Page graph, native companions, style sidecars, and build identity required before any patch is admitted.
55
74
  lazy: false,
75
+ // Keep Rolldown's common runtime injection because generated application factories call its module registry and
76
+ // HMR primitives. Skipping it would leave the custom implementation without the runtime surface it extends.
56
77
  skipCommonRuntimeInjection: false
57
78
  };
58
79
  /*
@@ -1,10 +1,12 @@
1
1
  import type { Rolldown } from 'vite';
2
+ import type { PackageLocation } from '../placer/placement.ts';
2
3
  /** Creates opaque native files and their registrations from each surviving JSX interface module. */
3
- export declare function createNativeComponentOutput({ bundle, getModuleInfo }: {
4
+ export declare function createNativeComponentOutput({ bundle, getModuleInfo, getPackageLocation }: {
4
5
  bundle: Rolldown.OutputBundle;
5
6
  getModuleInfo: (moduleId: string) => {
6
7
  meta: Rolldown.CustomPluginOptions;
7
8
  } | null;
9
+ getPackageLocation(chunk: Rolldown.OutputChunk): PackageLocation;
8
10
  }): Promise<{
9
11
  files: Rolldown.EmittedAsset[];
10
12
  registrations: {
@@ -1,9 +1,8 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { isGeneratedSubpackageFile } from '../placement/plan.js';
4
3
  import { getNativeComponentSources } from './native-component-assets.js';
5
4
  /** Creates opaque native files and their registrations from each surviving JSX interface module. */
6
- export async function createNativeComponentOutput({ bundle, getModuleInfo }) {
5
+ export async function createNativeComponentOutput({ bundle, getModuleInfo, getPackageLocation }) {
7
6
  // Files and registrations accumulate in final chunk order for deterministic output.
8
7
  const files = [];
9
8
  const registrations = [];
@@ -11,9 +10,8 @@ export async function createNativeComponentOutput({ bundle, getModuleInfo }) {
11
10
  if (output.type !== 'chunk') {
12
11
  continue;
13
12
  }
14
- const packageRoot = isGeneratedSubpackageFile(output.fileName)
15
- ? path.posix.dirname(path.posix.dirname(output.fileName))
16
- : undefined;
13
+ const location = getPackageLocation(output);
14
+ const packageRoot = location.kind === 'subpackage' ? location.root : undefined;
17
15
  for (const moduleId of output.moduleIds) {
18
16
  const sources = getNativeComponentSources(getModuleInfo(moduleId)?.meta);
19
17
  for (const source of sources) {
@@ -1,12 +1,13 @@
1
1
  import type { Rolldown } from 'vite';
2
2
  import type { VptOptions } from '../../../../options.ts';
3
- import type { GeneratedSubpackage } from '../placement/placer.ts';
3
+ import type { GeneratedSubpackage, PackageLocation } from '../placer/placement.ts';
4
4
  /** Creates every compiler-owned WX file derived from the final Rolldown bundle. */
5
- export declare function createOutputFiles({ bundle, options, subpackages, getModuleInfo }: {
5
+ export declare function createOutputFiles({ bundle, options, subpackages, getModuleInfo, getPackageLocation }: {
6
6
  bundle: Rolldown.OutputBundle;
7
7
  options: VptOptions;
8
8
  subpackages: readonly GeneratedSubpackage[];
9
9
  getModuleInfo: (moduleId: string) => {
10
10
  meta: Rolldown.CustomPluginOptions;
11
11
  } | null;
12
+ getPackageLocation(chunk: Rolldown.OutputChunk): PackageLocation;
12
13
  }): Promise<Rolldown.EmittedFile[]>;
@@ -2,8 +2,8 @@ import { createNativeComponentOutput } from '../native/create-native-component-o
2
2
  import { createJsonAssets } from './json.js';
3
3
  import { createTemplateAssets } from './templates.js';
4
4
  /** Creates every compiler-owned WX file derived from the final Rolldown bundle. */
5
- export async function createOutputFiles({ bundle, options, subpackages, getModuleInfo }) {
6
- const nativeOutput = await createNativeComponentOutput({ bundle, getModuleInfo });
5
+ export async function createOutputFiles({ bundle, options, subpackages, getModuleInfo, getPackageLocation }) {
6
+ const nativeOutput = await createNativeComponentOutput({ bundle, getModuleInfo, getPackageLocation });
7
7
  return [
8
8
  {
9
9
  type: 'asset',
@@ -1,6 +1,6 @@
1
1
  import type { Rolldown } from 'vite';
2
2
  import type { VptJsonObject, VptOptions } from '../../../../options.ts';
3
- import type { GeneratedSubpackage } from '../placement/placer.ts';
3
+ import { type GeneratedSubpackage } from '../placer/placement.ts';
4
4
  /** Creates every configured native JSON asset. */
5
5
  export declare function createJsonAssets({ options, subpackages, nativeComponents }: {
6
6
  options: VptOptions;
@@ -1,5 +1,5 @@
1
1
  import { createAppConfig } from '../../../utils/project-config.js';
2
- import { isGeneratedSubpackageFile } from '../placement/plan.js';
2
+ import { isGeneratedSubpackageFile } from '../placer/placement.js';
3
3
  import { toRootRelativePath } from './relative-root.js';
4
4
  /** Creates every configured native JSON asset. */
5
5
  export function createJsonAssets({ options, subpackages, nativeComponents }) {
@@ -0,0 +1,50 @@
1
+ import type { Rolldown } from 'vite';
2
+ /** Identifies one generated code-only subpackage by its physical output root. */
3
+ export type SubpackageLocation = {
4
+ /** Discriminates generated subpackages from main. */
5
+ kind: 'subpackage';
6
+ /** Native subpackage root relative to the Mini Program output directory. */
7
+ root: string;
8
+ };
9
+ /** Physical package ownership for one final Rolldown chunk. */
10
+ export type PackageLocation = {
11
+ kind: 'main';
12
+ } | SubpackageLocation;
13
+ /** Native app.json declaration for one generated code-only subpackage. */
14
+ export type GeneratedSubpackage = {
15
+ /** Stable native alias derived from the generated root hash. */
16
+ name: string;
17
+ /** Physical directory containing this subpackage's emitted capsules. */
18
+ root: string;
19
+ /** Marks this as a code-only subpackage with no native Page routes. */
20
+ pages: readonly [];
21
+ };
22
+ /** Immutable ownership and materialization operations for one complete final-chunk graph. */
23
+ export type Placement = Readonly<{
24
+ getPackageLocation(chunk: Rolldown.RenderedChunk | Rolldown.OutputChunk): PackageLocation;
25
+ getPhysicalChunkId(chunk: Rolldown.RenderedChunk): string;
26
+ getLoadMode(chunk: Rolldown.RenderedChunk): 'sync' | 'async';
27
+ finalize(bundle: Rolldown.OutputBundle): readonly GeneratedSubpackage[];
28
+ }>;
29
+ /**
30
+ * Applies Load-Transition Hypergraph Partitioning to Rolldown's final preliminary chunk graph:
31
+ *
32
+ * 1. Sort preliminary filenames to remove callback and object-enumeration order from every later decision.
33
+ * 2. Reserve every explicit entry and its complete static closure in main because native startup must load it synchronously.
34
+ * 3. Treat each dynamic-import edge as one load transition; the target's static closure is that transition's hyperedge.
35
+ * Nested dynamic edges remain separate transitions rather than being folded into the parent closure.
36
+ * 4. Index every lazy chunk by all transitions requiring it. Shared chunks therefore carry global demand rather than being
37
+ * assigned according to the first source module or dynamic root that happens to visit them.
38
+ * 5. Order chunks by transition demand, estimated emitted bytes, then preliminary filename. Place each chunk once into the
39
+ * fitting bin with maximum transition overlap, using best-fit remaining capacity only as a tie-breaker.
40
+ * 6. Hash each bin's sorted preliminary filenames into a deterministic physical package root and return unique ownership.
41
+ *
42
+ * Analysis costs the sum of transition static-closure traversals. Packing scans fitting bins and intersects sparse
43
+ * transition sets; its worst case is O(CBT), while practical graphs have few bins and sparse transition membership.
44
+ */
45
+ export declare function createPlacement({ chunks, getAdditionalModuleBytes }: {
46
+ chunks: Readonly<Record<string, Rolldown.RenderedChunk>>;
47
+ getAdditionalModuleBytes(moduleId: string): number;
48
+ }): Placement;
49
+ /** Tests the plugin-owned output prefix that physically identifies every generated subpackage. */
50
+ export declare function isGeneratedSubpackageFile(fileName: string): boolean;
@@ -0,0 +1,245 @@
1
+ import { createHash } from 'node:crypto';
2
+ // Leave headroom below WeChat's 2M subpackage limit for rendered wrappers and native assets.
3
+ const subpackagePlanningBudget = 1_900_000;
4
+ const generatedSubpackageRootPrefix = 'sub/p_';
5
+ /** Shared main-package value used for every synchronously reachable chunk. */
6
+ const mainPackage = { kind: 'main' };
7
+ /**
8
+ * Applies Load-Transition Hypergraph Partitioning to Rolldown's final preliminary chunk graph:
9
+ *
10
+ * 1. Sort preliminary filenames to remove callback and object-enumeration order from every later decision.
11
+ * 2. Reserve every explicit entry and its complete static closure in main because native startup must load it synchronously.
12
+ * 3. Treat each dynamic-import edge as one load transition; the target's static closure is that transition's hyperedge.
13
+ * Nested dynamic edges remain separate transitions rather than being folded into the parent closure.
14
+ * 4. Index every lazy chunk by all transitions requiring it. Shared chunks therefore carry global demand rather than being
15
+ * assigned according to the first source module or dynamic root that happens to visit them.
16
+ * 5. Order chunks by transition demand, estimated emitted bytes, then preliminary filename. Place each chunk once into the
17
+ * fitting bin with maximum transition overlap, using best-fit remaining capacity only as a tie-breaker.
18
+ * 6. Hash each bin's sorted preliminary filenames into a deterministic physical package root and return unique ownership.
19
+ *
20
+ * Analysis costs the sum of transition static-closure traversals. Packing scans fitting bins and intersects sparse
21
+ * transition sets; its worst case is O(CBT), while practical graphs have few bins and sparse transition membership.
22
+ */
23
+ export function createPlacement({ chunks, getAdditionalModuleBytes }) {
24
+ const chunkById = new Map(Object.entries(chunks).sort(([left], [right]) => left.localeCompare(right)));
25
+ const mainChunkIds = findMainChunkIds(chunkById);
26
+ const transitionsByChunk = collectTransitionsByChunk({ chunks: chunkById, mainChunkIds: mainChunkIds });
27
+ const placeableChunks = [...chunkById]
28
+ .filter(([chunkId]) => !mainChunkIds.has(chunkId))
29
+ .map(([chunkId, chunk]) => ({
30
+ chunkId: chunkId,
31
+ estimatedBytes: estimateChunkBytes(chunk) +
32
+ chunk.moduleIds.reduce((bytes, moduleId) => bytes + getAdditionalModuleBytes(moduleId), 0),
33
+ transitions: transitionsByChunk.get(chunkId) ?? new Set()
34
+ }));
35
+ const subpackages = packChunks({
36
+ chunks: placeableChunks,
37
+ planningBudgetBytes: subpackagePlanningBudget
38
+ }).map(createPackedSubpackage);
39
+ // This local map accumulates the immutable plan returned to output materialization.
40
+ const locationByChunk = new Map();
41
+ for (const chunkId of mainChunkIds) {
42
+ locationByChunk.set(chunkId, mainPackage);
43
+ }
44
+ for (const subpackage of subpackages) {
45
+ for (const chunkId of subpackage.chunkIds) {
46
+ locationByChunk.set(chunkId, subpackage);
47
+ }
48
+ }
49
+ function getLocation(chunkId) {
50
+ const location = locationByChunk.get(chunkId);
51
+ if (!location) {
52
+ throw new Error(`wx placement is missing final chunk: ${chunkId}`);
53
+ }
54
+ return location;
55
+ }
56
+ /** Resolves typed ownership from Rolldown's preliminary physical filename before or after finalization. */
57
+ function getPackageLocation(chunk) {
58
+ return getLocation('preliminaryFileName' in chunk ? chunk.preliminaryFileName : chunk.fileName);
59
+ }
60
+ return {
61
+ getPackageLocation: getPackageLocation,
62
+ /** Adds the planned package root to a physical preliminary path without changing the chunk's SystemJS identity. */
63
+ getPhysicalChunkId(chunk) {
64
+ const location = getPackageLocation(chunk);
65
+ return location.kind === 'main' ? chunk.fileName : `${location.root}/${chunk.fileName}`;
66
+ },
67
+ /** Selects the native loading API directly from typed package ownership. */
68
+ getLoadMode(chunk) {
69
+ return getPackageLocation(chunk).kind === 'subpackage' ? 'async' : 'sync';
70
+ },
71
+ /** Assigns each final chunk's Rolldown-owned physical filename and declares typed owners that survived output. */
72
+ finalize(bundle) {
73
+ const outputChunks = Object.values(bundle).filter((output) => output.type === 'chunk');
74
+ // This local mutable set deduplicates typed package owners that retain at least one final output chunk.
75
+ const roots = new Set();
76
+ for (const chunk of outputChunks) {
77
+ // OutputChunk.fileName contains the resolved content hash. preliminaryFileName preserves the exact physical
78
+ // candidate with placeholders that identified this chunk when placement was created during renderChunk.
79
+ const location = getLocation(chunk.preliminaryFileName);
80
+ if (location.kind !== 'subpackage') {
81
+ continue;
82
+ }
83
+ // OutputChunk is mutable in generateBundle. Assigning fileName makes Rolldown retain all chunk metadata and
84
+ // write that same chunk at its physical package path; deleting bundle keys or re-emitting would lose identity.
85
+ chunk.fileName = `${location.root}/${chunk.fileName}`;
86
+ roots.add(location.root);
87
+ }
88
+ return [...roots].sort().map((root) => ({
89
+ name: root.slice(root.lastIndexOf('/') + 1),
90
+ root: root,
91
+ pages: []
92
+ }));
93
+ }
94
+ };
95
+ }
96
+ /** Keeps every explicit output entry and its complete static chunk closure in main. */
97
+ function findMainChunkIds(chunks) {
98
+ const mainChunkIds = new Set();
99
+ // The worklist avoids recursion and visits every eager static edge once.
100
+ const pending = [...chunks].filter(([, chunk]) => chunk.isEntry).map(([chunkId]) => chunkId);
101
+ while (pending.length > 0) {
102
+ const chunkId = pending.pop();
103
+ if (!chunkId || mainChunkIds.has(chunkId)) {
104
+ continue;
105
+ }
106
+ const chunk = chunks.get(chunkId);
107
+ if (!chunk) {
108
+ continue;
109
+ }
110
+ mainChunkIds.add(chunkId);
111
+ pending.push(...chunk.imports);
112
+ }
113
+ return mainChunkIds;
114
+ }
115
+ /** Creates every load-transition hyperedge and indexes its static closure by chunk. */
116
+ function collectTransitionsByChunk({ chunks, mainChunkIds }) {
117
+ const transitionsByChunk = new Map();
118
+ let transitionId = 0;
119
+ for (const chunk of chunks.values()) {
120
+ for (const targetId of [...chunk.dynamicImports].sort()) {
121
+ if (!chunks.has(targetId) || mainChunkIds.has(targetId)) {
122
+ continue;
123
+ }
124
+ for (const chunkId of collectStaticClosure({
125
+ rootId: targetId,
126
+ chunks: chunks,
127
+ mainChunkIds: mainChunkIds
128
+ })) {
129
+ const transitions = transitionsByChunk.get(chunkId) ?? new Set();
130
+ transitions.add(transitionId);
131
+ transitionsByChunk.set(chunkId, transitions);
132
+ }
133
+ transitionId++;
134
+ }
135
+ }
136
+ return transitionsByChunk;
137
+ }
138
+ /** Collects one transition's static closure; nested dynamic imports remain independent transitions. */
139
+ function collectStaticClosure({ rootId, chunks, mainChunkIds }) {
140
+ const closure = new Set();
141
+ // The worklist follows static edges only; the visited set terminates cycles.
142
+ const pending = [rootId];
143
+ while (pending.length > 0) {
144
+ const chunkId = pending.pop();
145
+ if (!chunkId || closure.has(chunkId) || mainChunkIds.has(chunkId)) {
146
+ continue;
147
+ }
148
+ const chunk = chunks.get(chunkId);
149
+ if (!chunk) {
150
+ continue;
151
+ }
152
+ closure.add(chunkId);
153
+ pending.push(...chunk.imports);
154
+ }
155
+ return [...closure].sort();
156
+ }
157
+ /** Partitions final chunks by transition overlap before best-fit capacity. */
158
+ function packChunks({ chunks, planningBudgetBytes }) {
159
+ const bins = [];
160
+ const orderedChunks = [...chunks].sort(compareChunks);
161
+ for (const chunk of orderedChunks) {
162
+ const bin = chunk.estimatedBytes <= planningBudgetBytes
163
+ ? (findBestBin({ bins: bins, chunk: chunk, planningBudgetBytes: planningBudgetBytes }) ??
164
+ createBin(bins))
165
+ : createBin(bins);
166
+ placeChunk(bin, chunk);
167
+ }
168
+ return bins;
169
+ }
170
+ /** Gives globally shared transition demand priority, then size and stable preliminary filename. */
171
+ function compareChunks(left, right) {
172
+ return (right.transitions.size - left.transitions.size ||
173
+ right.estimatedBytes - left.estimatedBytes ||
174
+ left.chunkId.localeCompare(right.chunkId));
175
+ }
176
+ /** Chooses maximum transition overlap, then the fullest fitting package and stable creation order. */
177
+ function findBestBin({ bins, chunk, planningBudgetBytes }) {
178
+ let best;
179
+ for (const bin of bins) {
180
+ if (bin.estimatedBytes + chunk.estimatedBytes > planningBudgetBytes) {
181
+ continue;
182
+ }
183
+ const candidate = {
184
+ bin: bin,
185
+ overlap: countOverlap(bin.transitions, chunk.transitions),
186
+ remainingBytes: planningBudgetBytes - bin.estimatedBytes - chunk.estimatedBytes
187
+ };
188
+ if (!best ||
189
+ candidate.overlap > best.overlap ||
190
+ (candidate.overlap === best.overlap && candidate.remainingBytes < best.remainingBytes)) {
191
+ best = candidate;
192
+ }
193
+ }
194
+ return best?.bin;
195
+ }
196
+ /** Counts transition-package incidences removed by one candidate placement. */
197
+ function countOverlap(left, right) {
198
+ const [smaller, larger] = left.size <= right.size ? [left, right] : [right, left];
199
+ let overlap = 0;
200
+ for (const transition of smaller) {
201
+ if (larger.has(transition)) {
202
+ overlap++;
203
+ }
204
+ }
205
+ return overlap;
206
+ }
207
+ /** Creates one planner-local mutable package. */
208
+ function createBin(bins) {
209
+ const bin = {
210
+ chunkIds: [],
211
+ estimatedBytes: 0,
212
+ transitions: new Set()
213
+ };
214
+ bins.push(bin);
215
+ return bin;
216
+ }
217
+ /** Applies one irreversible, non-duplicating final chunk assignment. */
218
+ function placeChunk(bin, chunk) {
219
+ bin.chunkIds.push(chunk.chunkId);
220
+ bin.estimatedBytes += chunk.estimatedBytes;
221
+ for (const transition of chunk.transitions) {
222
+ bin.transitions.add(transition);
223
+ }
224
+ }
225
+ /** Estimates final output bytes from tree-shaken modules plus a bounded generated-chunk allowance. */
226
+ function estimateChunkBytes(chunk) {
227
+ const renderedModuleBytes = Object.values(chunk.modules).reduce((bytes, module) => bytes + (module.code ? Buffer.byteLength(module.code, 'utf8') : 0), 0);
228
+ const moduleWrapperBytes = chunk.moduleIds.length * 64;
229
+ const referenceBytes = (chunk.imports.length + chunk.dynamicImports.length) * 32;
230
+ return renderedModuleBytes + moduleWrapperBytes + referenceBytes + 64;
231
+ }
232
+ /** Freezes membership and derives a stable root from sorted preliminary filenames. */
233
+ function createPackedSubpackage(bin) {
234
+ const chunkIds = [...bin.chunkIds].sort();
235
+ const hash = createHash('sha256').update(chunkIds.join('\0')).digest('hex').slice(0, 8);
236
+ return {
237
+ kind: 'subpackage',
238
+ root: `${generatedSubpackageRootPrefix}${hash}`,
239
+ chunkIds: chunkIds
240
+ };
241
+ }
242
+ /** Tests the plugin-owned output prefix that physically identifies every generated subpackage. */
243
+ export function isGeneratedSubpackageFile(fileName) {
244
+ return fileName.startsWith(generatedSubpackageRootPrefix);
245
+ }
@@ -0,0 +1,60 @@
1
+ import type { Plugin, Rolldown } from 'vite';
2
+ import { type GeneratedSubpackage, type PackageLocation } from './placement.ts';
3
+ export type { GeneratedSubpackage, Placement } from './placement.ts';
4
+ /** Placement services consumed by the later `vpt:wx` rendering and output hooks. */
5
+ export type WxPlacementPlugin = Plugin & Readonly<{
6
+ getPackageLocation(chunk: Rolldown.RenderedChunk | Rolldown.OutputChunk): PackageLocation;
7
+ getPhysicalChunkId(chunk: Rolldown.RenderedChunk): string;
8
+ getLoadMode(chunk: Rolldown.RenderedChunk): 'sync' | 'async';
9
+ getSubpackages(): readonly GeneratedSubpackage[];
10
+ }>;
11
+ /**
12
+ * Rolldown options owned by WX placement. Every field enforces a distinct output invariant. The plugin returns this object
13
+ * from its config hook, while direct Rolldown integration tests reuse the same value to exercise the identical lifecycle.
14
+ */
15
+ export declare const placementRolldownOptions: {
16
+ /**
17
+ * Output-stage naming remains under Rolldown's ownership. These options establish physical candidates and hash
18
+ * participation only; LTHP mutates the resulting OutputChunk filenames later without replacing the chunks.
19
+ */
20
+ output: {
21
+ /**
22
+ * Native App/Page/Component shells are files addressed directly by WeChat and must retain the exact names configured
23
+ * in `input`, such as `app.js` and `pages/home/index.js`. Transport is excluded even though it is CommonJS:
24
+ * application chunks import its content-hashed path, so it belongs with hashed runtime/capsule entries. `[hash]`
25
+ * remains a Rolldown placeholder here and is resolved only after renderChunk transforms finish.
26
+ */
27
+ entryFileNames(chunk: Rolldown.PreRenderedChunk): string;
28
+ /**
29
+ * Leaves chunk identity and collision handling entirely to Rolldown. This package-neutral physical pattern deliberately
30
+ * contains no LTHP owner; generateBundle adds only the selected package root to the existing Rolldown filename.
31
+ */
32
+ chunkFileNames: string;
33
+ /**
34
+ * Emits generic Rolldown assets under one collision-resistant hashed namespace. Native-component folders are not
35
+ * governed by this option: createNativeComponentOutput preserves their required relative filenames and relocates the
36
+ * complete folder beside its owning JavaScript chunk after LTHP finalization.
37
+ */
38
+ assetFileNames: string;
39
+ };
40
+ /**
41
+ * Keeps every native entry's required exports while allowing Rolldown to add cross-chunk bindings created by natural code
42
+ * splitting. `strict` can reject those extensions; `exports-only` can merge away native boundaries; `allow-extension`
43
+ * preserves the shell/capsule contract without forcing source-module placement groups.
44
+ */
45
+ preserveEntrySignatures: 'allow-extension';
46
+ };
47
+ /**
48
+ * Creates the `vpt:wx-placer` lifecycle owner:
49
+ *
50
+ * 1. Its config hook installs package-neutral Rolldown names and entry-signature semantics.
51
+ * 2. `renderStart` atomically starts a generation in `awaiting-chunks`; no stale placement remains reachable.
52
+ * 3. Its first pre-order `renderChunk` creates one immutable LTHP placement from the complete tree-shaken graph.
53
+ * 4. `vpt:wx` asks this plugin only for package ownership, physical relocation, and native loading mode.
54
+ * 5. Its pre-order `generateBundle` assigns each OutputChunk its package-qualified filename and publishes app.json declarations.
55
+ *
56
+ * The discriminated state is the only generation-local mutation: `idle → awaiting-chunks → planned → finalized`. Each hook
57
+ * performs one whole-state transition, so stale graph state, duplicate planning, and partially reset generations are
58
+ * unrepresentable.
59
+ */
60
+ export declare function createWxPlacementPlugin(): WxPlacementPlugin;