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
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/
|
|
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/
|
|
40
|
+
- [配置参考](https://vpt.js.org/guides/configuration/)
|
|
41
41
|
- [仓库维护](https://vpt.js.org/references/repository-management/)
|
|
42
42
|
|
|
43
43
|
## 许可证
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as types from '@babel/types';
|
|
2
2
|
import { createPageComponentImportPath } from '../../utils/modules.js';
|
|
3
3
|
import { createAppConfig } from '../../utils/project-config.js';
|
|
4
4
|
import { replaceWithAst } from '../../utils/transform.js';
|
|
@@ -2,7 +2,7 @@ import { normalizePath, transformWithOxc } from 'vite';
|
|
|
2
2
|
import { esTarget } from '../../../utils/constant.js';
|
|
3
3
|
import { memoize } from '../../../utils/memoize.js';
|
|
4
4
|
import { normalizeModuleId } from '../../../utils/modules.js';
|
|
5
|
-
import { appCapsulePath, pageCapsulePath, rolldownRuntimeId, taroRuntimePath } from '../module.js';
|
|
5
|
+
import { appCapsulePath, pageCapsulePath, rolldownRuntimeId, taroRuntimePath } from '../module/module.js';
|
|
6
6
|
import { createWxDevHost } from './dev-host.js';
|
|
7
7
|
import { developmentAppWxssFileName } from './hmr-files.js';
|
|
8
8
|
import { createWxReactRefreshTransforms } from './react-refresh.js';
|
|
@@ -3,7 +3,7 @@ import { build } from 'rolldown';
|
|
|
3
3
|
import { viteReporterPlugin } from 'rolldown/experimental';
|
|
4
4
|
import { once } from '../../../utils/once.js';
|
|
5
5
|
import { resolvePackageFile } from '../../../utils/packages.js';
|
|
6
|
-
import { appShellFileName } from '../module.js';
|
|
6
|
+
import { appShellFileName } from '../module/module.js';
|
|
7
7
|
/**
|
|
8
8
|
* Installs physical WX output and runtime conventions over Vite's browser-oriented bundled-development options.
|
|
9
9
|
* Build completion deliberately remains outside this options adapter: DevEngine onOutput is the single lifecycle authority.
|
|
@@ -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
|
/*
|
|
@@ -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
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { normalizePath } from 'vite';
|
|
3
|
-
import { normalizeModuleId } from '
|
|
4
|
-
import { packageRequire, resolvePackageFile } from '
|
|
3
|
+
import { normalizeModuleId } from '../../../utils/modules.js';
|
|
4
|
+
import { packageRequire, resolvePackageFile } from '../../../utils/packages.js';
|
|
5
5
|
/** Identifies Rolldown's generated helper module independently of its unstable output filename. */
|
|
6
6
|
export const rolldownRuntimeId = '\0rolldown/runtime.js';
|
|
7
7
|
/** Resolves the direct React Reconciler dependency without assuming pnpm's layout. */
|
|
@@ -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
|
|
15
|
-
|
|
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
|
|
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
|
|
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
|
|
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,51 @@
|
|
|
1
|
+
import type { Rolldown } from 'vite';
|
|
2
|
+
export declare const subpackagePlanningBudget = 1900000;
|
|
3
|
+
/** Identifies one generated code-only subpackage by its physical output root. */
|
|
4
|
+
export type SubpackageLocation = {
|
|
5
|
+
/** Discriminates generated subpackages from main. */
|
|
6
|
+
kind: 'subpackage';
|
|
7
|
+
/** Native subpackage root relative to the Mini Program output directory. */
|
|
8
|
+
root: string;
|
|
9
|
+
};
|
|
10
|
+
/** Physical package ownership for one final Rolldown chunk. */
|
|
11
|
+
export type PackageLocation = {
|
|
12
|
+
kind: 'main';
|
|
13
|
+
} | SubpackageLocation;
|
|
14
|
+
/** Native app.json declaration for one generated code-only subpackage. */
|
|
15
|
+
export type GeneratedSubpackage = {
|
|
16
|
+
/** Stable native alias derived from the generated root hash. */
|
|
17
|
+
name: string;
|
|
18
|
+
/** Physical directory containing this subpackage's emitted capsules. */
|
|
19
|
+
root: string;
|
|
20
|
+
/** Marks this as a code-only subpackage with no native Page routes. */
|
|
21
|
+
pages: readonly [];
|
|
22
|
+
};
|
|
23
|
+
/** Immutable ownership and materialization operations for one complete final-chunk graph. */
|
|
24
|
+
export type Placement = Readonly<{
|
|
25
|
+
getPackageLocation(chunk: Rolldown.RenderedChunk | Rolldown.OutputChunk): PackageLocation;
|
|
26
|
+
getPhysicalChunkId(chunk: Rolldown.RenderedChunk): string;
|
|
27
|
+
getLoadMode(chunk: Rolldown.RenderedChunk): 'sync' | 'async';
|
|
28
|
+
finalize(bundle: Rolldown.OutputBundle): readonly GeneratedSubpackage[];
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* Applies Load-Transition Hypergraph Partitioning to Rolldown's final preliminary chunk graph:
|
|
32
|
+
*
|
|
33
|
+
* 1. Sort preliminary filenames to remove callback and object-enumeration order from every later decision.
|
|
34
|
+
* 2. Reserve every explicit entry and its complete static closure in main because native startup must load it synchronously.
|
|
35
|
+
* 3. Treat each dynamic-import edge as one load transition; the target's static closure is that transition's hyperedge.
|
|
36
|
+
* Nested dynamic edges remain separate transitions rather than being folded into the parent closure.
|
|
37
|
+
* 4. Index every lazy chunk by all transitions requiring it. Shared chunks therefore carry global demand rather than being
|
|
38
|
+
* assigned according to the first source module or dynamic root that happens to visit them.
|
|
39
|
+
* 5. Order chunks by transition demand, estimated emitted bytes, then preliminary filename. Place each chunk once into the
|
|
40
|
+
* fitting bin with maximum transition overlap, using best-fit remaining capacity only as a tie-breaker.
|
|
41
|
+
* 6. Hash each bin's sorted preliminary filenames into a deterministic physical package root and return unique ownership.
|
|
42
|
+
*
|
|
43
|
+
* Analysis costs the sum of transition static-closure traversals. Packing scans fitting bins and intersects sparse
|
|
44
|
+
* transition sets; its worst case is O(CBT), while practical graphs have few bins and sparse transition membership.
|
|
45
|
+
*/
|
|
46
|
+
export declare function createPlacement({ chunks, getAdditionalModuleBytes }: {
|
|
47
|
+
chunks: Readonly<Record<string, Rolldown.RenderedChunk>>;
|
|
48
|
+
getAdditionalModuleBytes(moduleId: string): number;
|
|
49
|
+
}): Placement;
|
|
50
|
+
/** Tests the plugin-owned output prefix that physically identifies every generated subpackage. */
|
|
51
|
+
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
|
+
export 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,76 @@
|
|
|
1
|
+
import { type Plugin, type Rolldown } from 'vite';
|
|
2
|
+
import { type GeneratedSubpackage, type PackageLocation } from './placement.ts';
|
|
3
|
+
export type { GeneratedSubpackage, Placement } from './placement.ts';
|
|
4
|
+
/** Selects the explicit React/Taro roots whose complete dependency closure forms the framework vendor chunk. */
|
|
5
|
+
export declare function isWxFrameworkVendorModule(moduleId: string): boolean;
|
|
6
|
+
/** Placement services consumed by the later `vpt:wx` rendering and output hooks. */
|
|
7
|
+
export type WxPlacementPlugin = Plugin & Readonly<{
|
|
8
|
+
getPackageLocation(chunk: Rolldown.RenderedChunk | Rolldown.OutputChunk): PackageLocation;
|
|
9
|
+
getPhysicalChunkId(chunk: Rolldown.RenderedChunk): string;
|
|
10
|
+
getLoadMode(chunk: Rolldown.RenderedChunk): 'sync' | 'async';
|
|
11
|
+
getSubpackages(): readonly GeneratedSubpackage[];
|
|
12
|
+
}>;
|
|
13
|
+
/**
|
|
14
|
+
* Rolldown options owned by WX placement. Every field enforces a distinct output invariant. The plugin returns this object
|
|
15
|
+
* from its config hook, while direct Rolldown integration tests reuse the same value to exercise the identical lifecycle.
|
|
16
|
+
*/
|
|
17
|
+
export declare const placementRolldownOptions: {
|
|
18
|
+
/**
|
|
19
|
+
* Output-stage naming remains under Rolldown's ownership. These options establish physical candidates and hash
|
|
20
|
+
* participation only; LTHP mutates the resulting OutputChunk filenames later without replacing the chunks.
|
|
21
|
+
*/
|
|
22
|
+
output: {
|
|
23
|
+
/**
|
|
24
|
+
* React and Taro form one stable framework boundary shared by the App and every Page capsule. Keeping their complete
|
|
25
|
+
* dependency closure together prevents application edits from invalidating framework chunk identity and makes later
|
|
26
|
+
* development generations eligible to reuse the unchanged vendor. All remaining modules use Rolldown's automatic
|
|
27
|
+
* chunking; physical WX package placement operates on those final chunks without imposing another split strategy.
|
|
28
|
+
*/
|
|
29
|
+
codeSplitting: {
|
|
30
|
+
groups: {
|
|
31
|
+
name: string;
|
|
32
|
+
test: typeof isWxFrameworkVendorModule;
|
|
33
|
+
priority: number;
|
|
34
|
+
includeDependenciesRecursively: boolean;
|
|
35
|
+
}[];
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Native App/Page/Component shells are files addressed directly by WeChat and must retain the exact names configured
|
|
39
|
+
* in `input`, such as `app.js` and `pages/home/index.js`. Transport is excluded even though it is CommonJS:
|
|
40
|
+
* application chunks import its content-hashed path, so it belongs with hashed runtime/capsule entries. `[hash]`
|
|
41
|
+
* remains a Rolldown placeholder here and is resolved only after renderChunk transforms finish.
|
|
42
|
+
*/
|
|
43
|
+
entryFileNames(chunk: Rolldown.PreRenderedChunk): string;
|
|
44
|
+
/**
|
|
45
|
+
* Leaves chunk identity and collision handling entirely to Rolldown. This package-neutral physical pattern deliberately
|
|
46
|
+
* contains no LTHP owner; generateBundle adds only the selected package root to the existing Rolldown filename.
|
|
47
|
+
*/
|
|
48
|
+
chunkFileNames: string;
|
|
49
|
+
/**
|
|
50
|
+
* Emits generic Rolldown assets under one collision-resistant hashed namespace. Native-component folders are not
|
|
51
|
+
* governed by this option: createNativeComponentOutput preserves their required relative filenames and relocates the
|
|
52
|
+
* complete folder beside its owning JavaScript chunk after LTHP finalization.
|
|
53
|
+
*/
|
|
54
|
+
assetFileNames: string;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Keeps every native entry's required exports while allowing Rolldown to add cross-chunk bindings created by natural code
|
|
58
|
+
* splitting. `strict` can reject those extensions; `exports-only` can merge away native boundaries; `allow-extension`
|
|
59
|
+
* preserves the shell/capsule contract without forcing source-module placement groups.
|
|
60
|
+
*/
|
|
61
|
+
preserveEntrySignatures: 'allow-extension';
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Creates the `vpt:wx-placer` lifecycle owner:
|
|
65
|
+
*
|
|
66
|
+
* 1. Its config hook installs package-neutral Rolldown names and entry-signature semantics.
|
|
67
|
+
* 2. `renderStart` atomically starts a generation in `awaiting-chunks`; no stale placement remains reachable.
|
|
68
|
+
* 3. Its first pre-order `renderChunk` creates one immutable LTHP placement from the complete tree-shaken graph.
|
|
69
|
+
* 4. `vpt:wx` asks this plugin only for package ownership, physical relocation, and native loading mode.
|
|
70
|
+
* 5. Its pre-order `generateBundle` assigns each OutputChunk its package-qualified filename and publishes app.json declarations.
|
|
71
|
+
*
|
|
72
|
+
* The discriminated state is the only generation-local mutation: `idle → awaiting-chunks → planned → finalized`. Each hook
|
|
73
|
+
* performs one whole-state transition, so stale graph state, duplicate planning, and partially reset generations are
|
|
74
|
+
* unrepresentable.
|
|
75
|
+
*/
|
|
76
|
+
export declare function createWxPlacementPlugin(): WxPlacementPlugin;
|