@pix3/vite-plugin 0.0.0-stage → 2.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +38 -3
- package/dist/build/bundle-hooks.d.ts +23 -0
- package/dist/build/bundle-hooks.js +79 -0
- package/dist/build/classic-script.d.ts +11 -0
- package/dist/build/classic-script.js +46 -0
- package/dist/build/compress.d.ts +29 -0
- package/dist/build/compress.js +63 -0
- package/dist/build/dependency-imports.d.ts +53 -0
- package/dist/build/dependency-imports.js +239 -0
- package/dist/build/dev-manifest.d.ts +10 -0
- package/dist/build/dev-manifest.js +25 -0
- package/dist/build/editor-flush.d.ts +42 -0
- package/dist/build/editor-flush.js +91 -0
- package/dist/build/finish.d.ts +11 -0
- package/dist/build/finish.js +126 -0
- package/dist/build/output-plugins.d.ts +26 -0
- package/dist/build/output-plugins.js +88 -0
- package/dist/build/plan.d.ts +75 -0
- package/dist/build/plan.js +162 -0
- package/dist/build/player-modules.d.ts +99 -0
- package/dist/build/player-modules.js +204 -0
- package/dist/build/project-manifest.d.ts +57 -0
- package/dist/build/project-manifest.js +169 -0
- package/dist/build/record.d.ts +25 -0
- package/dist/build/record.js +14 -0
- package/dist/build/report.d.ts +114 -0
- package/dist/build/report.js +102 -0
- package/dist/build/scan.d.ts +84 -0
- package/dist/build/scan.js +375 -0
- package/dist/build/strip-decision.d.ts +27 -0
- package/dist/build/strip-decision.js +82 -0
- package/dist/build/strippable-runtime-modules.d.ts +85 -0
- package/dist/build/strippable-runtime-modules.js +309 -0
- package/dist/build/zip.d.ts +6 -0
- package/dist/build/zip.js +28 -0
- package/dist/client/host-files.d.ts +96 -0
- package/dist/client/host-files.js +169 -0
- package/dist/client/index.d.ts +218 -0
- package/dist/client/index.js +381 -0
- package/dist/dev-info.d.ts +55 -0
- package/dist/dev-info.js +116 -0
- package/dist/dev-server.d.ts +28 -0
- package/dist/dev-server.js +139 -0
- package/dist/editor-page.d.ts +31 -0
- package/dist/editor-page.js +74 -0
- package/dist/files/changeset.d.ts +84 -0
- package/dist/files/changeset.js +321 -0
- package/dist/files/content-type.d.ts +1 -0
- package/dist/files/content-type.js +56 -0
- package/dist/files/history.d.ts +67 -0
- package/dist/files/history.js +162 -0
- package/dist/files/paths.d.ts +41 -0
- package/dist/files/paths.js +144 -0
- package/dist/files/project-files.d.ts +208 -0
- package/dist/files/project-files.js +1062 -0
- package/dist/files/scan.d.ts +71 -0
- package/dist/files/scan.js +252 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +278 -0
- package/dist/module-paths.d.ts +13 -0
- package/dist/module-paths.js +32 -0
- package/dist/player/index.d.ts +31 -0
- package/dist/player/index.js +102 -0
- package/dist/server/editor-socket.d.ts +53 -0
- package/dist/server/editor-socket.js +134 -0
- package/dist/server/guard.d.ts +61 -0
- package/dist/server/guard.js +165 -0
- package/dist/server/http.d.ts +24 -0
- package/dist/server/http.js +70 -0
- package/dist/server/image-keys.d.ts +46 -0
- package/dist/server/image-keys.js +118 -0
- package/dist/server/image-proxy.d.ts +32 -0
- package/dist/server/image-proxy.js +124 -0
- package/dist/server/router.d.ts +54 -0
- package/dist/server/router.js +147 -0
- package/dist/sync/barrier.d.ts +69 -0
- package/dist/sync/barrier.js +240 -0
- package/dist/sync/hot-update.d.ts +16 -0
- package/dist/sync/hot-update.js +58 -0
- package/dist/sync/script-graph.d.ts +79 -0
- package/dist/sync/script-graph.js +214 -0
- package/dist/vite-config.d.ts +12 -0
- package/dist/vite-config.js +62 -0
- package/package.json +60 -6
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
4
|
+
/**
|
|
5
|
+
* The part of `pix3project.yaml` the player and the build read (plan §B.5 «scene-manifest из
|
|
6
|
+
* pix3project.yaml»). A Node-side subset of the editor's `normalizeProjectManifest`
|
|
7
|
+
* (`packages/editor-core/src/core/ProjectManifest.ts`): same defaults, same clamps, so a build and
|
|
8
|
+
* the editor's play mode agree on viewport, quality, fonts and locales.
|
|
9
|
+
*/
|
|
10
|
+
export const PROJECT_MANIFEST_FILE = 'pix3project.yaml';
|
|
11
|
+
const DEFAULT_VIEWPORT = { width: 1920, height: 1080 };
|
|
12
|
+
const MIN_VIEWPORT = 64;
|
|
13
|
+
const MIN_PIXEL_RATIO = 1;
|
|
14
|
+
const MAX_PIXEL_RATIO = 4;
|
|
15
|
+
const isRecord = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
16
|
+
const qualityFor = (platform) => {
|
|
17
|
+
switch (platform) {
|
|
18
|
+
case 'mobile':
|
|
19
|
+
return { antialias: false, shadows: false, maxPixelRatio: 2 };
|
|
20
|
+
case 'desktop':
|
|
21
|
+
return { antialias: true, shadows: true, maxPixelRatio: 3 };
|
|
22
|
+
default:
|
|
23
|
+
return { antialias: true, shadows: true, maxPixelRatio: 2 };
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const normalizeQuality = (input, platform) => {
|
|
27
|
+
const defaults = qualityFor(platform);
|
|
28
|
+
if (!isRecord(input))
|
|
29
|
+
return defaults;
|
|
30
|
+
const rawRatio = Number(input.maxPixelRatio);
|
|
31
|
+
return {
|
|
32
|
+
antialias: typeof input.antialias === 'boolean' ? input.antialias : defaults.antialias,
|
|
33
|
+
shadows: typeof input.shadows === 'boolean' ? input.shadows : defaults.shadows,
|
|
34
|
+
maxPixelRatio: Number.isFinite(rawRatio)
|
|
35
|
+
? Math.min(MAX_PIXEL_RATIO, Math.max(MIN_PIXEL_RATIO, rawRatio))
|
|
36
|
+
: defaults.maxPixelRatio,
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
const normalizeViewport = (input) => {
|
|
40
|
+
const record = isRecord(input) ? input : {};
|
|
41
|
+
const width = Number(record.width);
|
|
42
|
+
const height = Number(record.height);
|
|
43
|
+
return {
|
|
44
|
+
width: Math.max(MIN_VIEWPORT, Number.isFinite(width) ? Math.round(width) : DEFAULT_VIEWPORT.width),
|
|
45
|
+
height: Math.max(MIN_VIEWPORT, Number.isFinite(height) ? Math.round(height) : DEFAULT_VIEWPORT.height),
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
const normalizeFonts = (input) => {
|
|
49
|
+
if (!Array.isArray(input))
|
|
50
|
+
return [];
|
|
51
|
+
const out = [];
|
|
52
|
+
const seen = new Set();
|
|
53
|
+
for (const entry of input) {
|
|
54
|
+
if (!isRecord(entry))
|
|
55
|
+
continue;
|
|
56
|
+
const family = typeof entry.family === 'string' ? entry.family.trim() : '';
|
|
57
|
+
const path = typeof entry.path === 'string' ? stripRes(entry.path.trim()) : '';
|
|
58
|
+
if (!family || !path)
|
|
59
|
+
continue;
|
|
60
|
+
const weight = typeof entry.weight === 'number' || typeof entry.weight === 'string' ? entry.weight : 400;
|
|
61
|
+
const style = entry.style === 'italic' ? 'italic' : 'normal';
|
|
62
|
+
const key = `${family}|${weight}|${style}|${path}`;
|
|
63
|
+
if (seen.has(key))
|
|
64
|
+
continue;
|
|
65
|
+
seen.add(key);
|
|
66
|
+
const unicodeRange = typeof entry.unicodeRange === 'string' && entry.unicodeRange.trim()
|
|
67
|
+
? entry.unicodeRange.trim()
|
|
68
|
+
: undefined;
|
|
69
|
+
out.push({ family, path, weight, style, ...(unicodeRange ? { unicodeRange } : {}) });
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
};
|
|
73
|
+
const normalizeLocalization = (input) => {
|
|
74
|
+
if (!isRecord(input))
|
|
75
|
+
return null;
|
|
76
|
+
const locales = Array.isArray(input.locales)
|
|
77
|
+
? [...new Set(input.locales.filter((l) => typeof l === 'string' && l.length > 0))]
|
|
78
|
+
: [];
|
|
79
|
+
const declaredDefault = typeof input.defaultLocale === 'string' && input.defaultLocale.length > 0
|
|
80
|
+
? input.defaultLocale
|
|
81
|
+
: (locales[0] ?? '');
|
|
82
|
+
if (!declaredDefault)
|
|
83
|
+
return null;
|
|
84
|
+
const finalLocales = locales.includes(declaredDefault) ? locales : [declaredDefault, ...locales];
|
|
85
|
+
const fallbackLocale = typeof input.fallbackLocale === 'string' && input.fallbackLocale.length > 0
|
|
86
|
+
? input.fallbackLocale
|
|
87
|
+
: undefined;
|
|
88
|
+
return {
|
|
89
|
+
defaultLocale: declaredDefault,
|
|
90
|
+
...(fallbackLocale ? { fallbackLocale } : {}),
|
|
91
|
+
locales: finalLocales,
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Same rule as the runtime's `normalizeAutoloads` (this side of the plugin does not import the
|
|
96
|
+
* runtime): entries need a `singleton` and a `scriptPath`, `enabled` defaults to true, a repeated
|
|
97
|
+
* singleton keeps its first entry.
|
|
98
|
+
*/
|
|
99
|
+
const normalizeAutoloads = (input) => {
|
|
100
|
+
if (!Array.isArray(input))
|
|
101
|
+
return [];
|
|
102
|
+
const out = [];
|
|
103
|
+
const seen = new Set();
|
|
104
|
+
for (const entry of input) {
|
|
105
|
+
if (!isRecord(entry))
|
|
106
|
+
continue;
|
|
107
|
+
const scriptPath = typeof entry.scriptPath === 'string' ? entry.scriptPath.trim() : '';
|
|
108
|
+
const singleton = typeof entry.singleton === 'string' ? entry.singleton.trim() : '';
|
|
109
|
+
if (!scriptPath || !singleton || seen.has(singleton))
|
|
110
|
+
continue;
|
|
111
|
+
seen.add(singleton);
|
|
112
|
+
out.push({ singleton, scriptPath, enabled: entry.enabled !== false });
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
};
|
|
116
|
+
/** `res://x`, `./x`, `/x` → `x`. */
|
|
117
|
+
export const stripRes = (path) => path
|
|
118
|
+
.replace(/\\/g, '/')
|
|
119
|
+
.replace(/^res:\/\//i, '')
|
|
120
|
+
.replace(/^\.\/+/, '')
|
|
121
|
+
.replace(/^\/+/, '');
|
|
122
|
+
/** `pix3({ resRoot })` as the build and the server use it: no scheme, no trailing `/`, `'.'` for the root. */
|
|
123
|
+
export const normalizeResRoot = (resRoot) => stripRes(resRoot).replace(/\/+$/, '') || '.';
|
|
124
|
+
/** A project wire path → its `res://`-relative path, or null outside `resRoot`. */
|
|
125
|
+
export const wireToRes = (wirePath, resRoot) => {
|
|
126
|
+
if (resRoot === '.')
|
|
127
|
+
return wirePath;
|
|
128
|
+
return wirePath.startsWith(`${resRoot}/`) ? wirePath.slice(resRoot.length + 1) : null;
|
|
129
|
+
};
|
|
130
|
+
/** The absolute file of a `res://`-relative path. */
|
|
131
|
+
export const resToAbsolute = (root, resRoot, resPath) => join(root, resRoot, ...resPath.split('/'));
|
|
132
|
+
/** Parse the manifest text (an empty or malformed manifest yields the defaults). */
|
|
133
|
+
export const parseProjectManifest = (text, fallbackName) => {
|
|
134
|
+
let parsed = null;
|
|
135
|
+
try {
|
|
136
|
+
parsed = parseYaml(text);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
parsed = null;
|
|
140
|
+
}
|
|
141
|
+
const record = isRecord(parsed) ? parsed : {};
|
|
142
|
+
const metadata = isRecord(record.metadata) ? record.metadata : {};
|
|
143
|
+
const platform = typeof record.targetPlatform === 'string' ? record.targetPlatform : 'universal';
|
|
144
|
+
const defaultScene = typeof record.defaultExportScenePath === 'string' && record.defaultExportScenePath.trim()
|
|
145
|
+
? stripRes(record.defaultExportScenePath.trim())
|
|
146
|
+
: null;
|
|
147
|
+
return {
|
|
148
|
+
projectName: typeof metadata.projectName === 'string' && metadata.projectName.trim()
|
|
149
|
+
? metadata.projectName.trim()
|
|
150
|
+
: fallbackName,
|
|
151
|
+
defaultScenePath: defaultScene,
|
|
152
|
+
viewportBaseSize: normalizeViewport(record.viewportBaseSize),
|
|
153
|
+
quality: normalizeQuality(record.quality, platform.toLowerCase()),
|
|
154
|
+
fonts: normalizeFonts(record.fonts),
|
|
155
|
+
localization: normalizeLocalization(record.localization),
|
|
156
|
+
autoloads: normalizeAutoloads(record.autoloads),
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
/** The manifest of the project at `root` (defaults when there is none). */
|
|
160
|
+
export const readProjectManifest = (root) => {
|
|
161
|
+
let text = '';
|
|
162
|
+
try {
|
|
163
|
+
text = readFileSync(join(root, PROJECT_MANIFEST_FILE), 'utf8');
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
text = '';
|
|
167
|
+
}
|
|
168
|
+
return parseProjectManifest(text, basename(root));
|
|
169
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `.pix3/build.json` — what the last `vite build` produced. Written by the plugin at the end of a
|
|
3
|
+
* build, so whoever ran `npm run build` (the coding agent, a script, CI) gets the artifact's path,
|
|
4
|
+
* size and hash without parsing Vite's output.
|
|
5
|
+
*/
|
|
6
|
+
export interface BuildRecord {
|
|
7
|
+
readonly format: 'html' | 'zip';
|
|
8
|
+
/** Absolute path of the artifact (`dist/index.html` or `dist/<name>.zip`). */
|
|
9
|
+
readonly path: string;
|
|
10
|
+
readonly bytes: number;
|
|
11
|
+
readonly sha256: string;
|
|
12
|
+
readonly at: string;
|
|
13
|
+
readonly entryScene: string;
|
|
14
|
+
readonly assets: number;
|
|
15
|
+
readonly stripped: readonly string[];
|
|
16
|
+
readonly warnings: readonly string[];
|
|
17
|
+
/** Absolute path of `dist/<name>.report.json` (sizes by group, stubs, assets, compression). */
|
|
18
|
+
readonly report?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare const buildRecordPath: (root: string) => string;
|
|
21
|
+
export declare const writeBuildRecord: (root: string, record: BuildRecord) => void;
|
|
22
|
+
export declare const fileDigest: (path: string) => {
|
|
23
|
+
bytes: number;
|
|
24
|
+
sha256: string;
|
|
25
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { RESERVED_ROOT_DIR } from "../files/paths.js";
|
|
5
|
+
export const buildRecordPath = (root) => join(root, RESERVED_ROOT_DIR, 'build.json');
|
|
6
|
+
export const writeBuildRecord = (root, record) => {
|
|
7
|
+
const path = buildRecordPath(root);
|
|
8
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
9
|
+
writeFileSync(path, `${JSON.stringify(record, null, 2)}\n`);
|
|
10
|
+
};
|
|
11
|
+
export const fileDigest = (path) => ({
|
|
12
|
+
bytes: statSync(path).size,
|
|
13
|
+
sha256: createHash('sha256').update(readFileSync(path)).digest('hex'),
|
|
14
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dist/<name>.report.json` (plan §B.6 item 5): why the artifact weighs what it does, written
|
|
3
|
+
* beside it so whoever ran `npm run build` (the coding agent first of all) can answer "why is it
|
|
4
|
+
* this big" without rebuilding — CLAUDE.md «Playable export size» is the prose, this is the
|
|
5
|
+
* numbers for one build.
|
|
6
|
+
*
|
|
7
|
+
* Module sizes come from the bundler's per-chunk `modules` map (`renderedLength`: the module's
|
|
8
|
+
* code after tree-shaking, **before minification**), grouped by where the id lives: `three`,
|
|
9
|
+
* `runtime` (`@pix3/runtime`'s `src/`), `player` (this package's player and the generated
|
|
10
|
+
* `virtual:pix3/*` modules), `project` (the project's own sources), `assets` (the embedded-asset
|
|
11
|
+
* module) and `dependencies` (every other `node_modules` package, listed by name). Each group also
|
|
12
|
+
* carries an estimate of its share of the final minified bundle (its rendered share scaled to the
|
|
13
|
+
* bundle's size), which is the number to quote.
|
|
14
|
+
*/
|
|
15
|
+
export type ModuleGroup = 'three' | 'runtime' | 'player' | 'project' | 'assets' | 'dependencies' | 'other';
|
|
16
|
+
export interface ModuleSize {
|
|
17
|
+
readonly id: string;
|
|
18
|
+
readonly group: ModuleGroup;
|
|
19
|
+
/** Package name for `dependencies` and `three`, null otherwise. */
|
|
20
|
+
readonly package: string | null;
|
|
21
|
+
readonly renderedBytes: number;
|
|
22
|
+
}
|
|
23
|
+
export interface GroupSize {
|
|
24
|
+
readonly modules: number;
|
|
25
|
+
readonly renderedBytes: number;
|
|
26
|
+
/** Rendered share of the chunk, scaled to the final (minified) bundle bytes. */
|
|
27
|
+
readonly estimatedMinifiedBytes: number;
|
|
28
|
+
readonly share: number;
|
|
29
|
+
}
|
|
30
|
+
export interface AssetSizeEntry {
|
|
31
|
+
readonly path: string;
|
|
32
|
+
readonly rawBytes: number;
|
|
33
|
+
readonly base64Bytes: number;
|
|
34
|
+
}
|
|
35
|
+
export interface BuildReport {
|
|
36
|
+
readonly format: 'html' | 'zip';
|
|
37
|
+
readonly path: string;
|
|
38
|
+
readonly at: string;
|
|
39
|
+
readonly entryScene: string;
|
|
40
|
+
readonly scenes: number;
|
|
41
|
+
/** The artifact on disk. */
|
|
42
|
+
readonly bytes: number;
|
|
43
|
+
/** What a gzip channel would transfer for the artifact (zlib level 6). */
|
|
44
|
+
readonly gzipBytes: number;
|
|
45
|
+
readonly compress: {
|
|
46
|
+
readonly enabled: true;
|
|
47
|
+
readonly bundleBytes: number;
|
|
48
|
+
readonly gzipBytes: number;
|
|
49
|
+
readonly base64Bytes: number;
|
|
50
|
+
/** `gzipBytes / bundleBytes`. */
|
|
51
|
+
readonly ratio: number;
|
|
52
|
+
/** `bundleBytes - base64Bytes`: what the file lost (the bootstrap is ~1.5 KiB). */
|
|
53
|
+
readonly savedBytes: number;
|
|
54
|
+
} | {
|
|
55
|
+
readonly enabled: false;
|
|
56
|
+
};
|
|
57
|
+
readonly code: {
|
|
58
|
+
/** Final JS bytes of every chunk (minified). */
|
|
59
|
+
readonly bundleBytes: number;
|
|
60
|
+
/** Sum of the modules' rendered lengths (before minification). */
|
|
61
|
+
readonly renderedBytes: number;
|
|
62
|
+
readonly groups: Record<ModuleGroup, GroupSize>;
|
|
63
|
+
/** `node_modules` packages by rendered bytes (three included), largest first. */
|
|
64
|
+
readonly packages: Record<string, number>;
|
|
65
|
+
readonly largestModules: readonly ModuleSize[];
|
|
66
|
+
};
|
|
67
|
+
readonly strip: {
|
|
68
|
+
readonly enabled: boolean;
|
|
69
|
+
readonly reason: string | null;
|
|
70
|
+
readonly keep: readonly string[];
|
|
71
|
+
/** Runtime modules replaced by stubs (paths under `@pix3/runtime/src`). */
|
|
72
|
+
readonly stripped: readonly string[];
|
|
73
|
+
/** `package → files parsed` for the dependencies that declare the runtime (N11). */
|
|
74
|
+
readonly dependencies: Record<string, number>;
|
|
75
|
+
/** Names a dependency imports from the runtime barrel (kept by that). */
|
|
76
|
+
readonly dependencyImports: readonly string[];
|
|
77
|
+
};
|
|
78
|
+
/** The optional libraries and how each one was resolved. */
|
|
79
|
+
readonly libraries: Record<string, string>;
|
|
80
|
+
readonly assets: {
|
|
81
|
+
readonly count: number;
|
|
82
|
+
readonly rawBytes: number;
|
|
83
|
+
readonly base64Bytes: number;
|
|
84
|
+
/** Largest first. */
|
|
85
|
+
readonly entries: readonly AssetSizeEntry[];
|
|
86
|
+
};
|
|
87
|
+
readonly warnings: readonly string[];
|
|
88
|
+
}
|
|
89
|
+
export interface ModuleClassifier {
|
|
90
|
+
readonly root: string;
|
|
91
|
+
readonly runtimeSrc: string | null;
|
|
92
|
+
readonly playerDir: string;
|
|
93
|
+
readonly embeddedAssetsId: string;
|
|
94
|
+
}
|
|
95
|
+
/** The package name of a `node_modules` id (`@scope/name` or `name`), or null. */
|
|
96
|
+
export declare const packageNameOf: (id: string) => string | null;
|
|
97
|
+
export declare const classifyModule: (id: string, classifier: ModuleClassifier) => {
|
|
98
|
+
group: ModuleGroup;
|
|
99
|
+
package: string | null;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* A readable id: `@pix3/runtime/src/…` for the runtime, `<package>/…` inside `node_modules`,
|
|
103
|
+
* relative to the root for the project's own files.
|
|
104
|
+
*/
|
|
105
|
+
export declare const displayId: (id: string, classifier: ModuleClassifier) => string;
|
|
106
|
+
export interface CodeSizes {
|
|
107
|
+
readonly bundleBytes: number;
|
|
108
|
+
readonly modules: readonly ModuleSize[];
|
|
109
|
+
}
|
|
110
|
+
export declare const summarizeCode: (sizes: CodeSizes, largest?: number) => BuildReport["code"];
|
|
111
|
+
export declare const gzipBytesOf: (bytes: Buffer) => number;
|
|
112
|
+
/** `<outDir>/<artifact basename without extension>.report.json`. */
|
|
113
|
+
export declare const buildReportPath: (artifactPath: string, outDir: string) => string;
|
|
114
|
+
export declare const writeBuildReport: (path: string, report: BuildReport) => void;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs';
|
|
2
|
+
import { basename, extname, join } from 'node:path';
|
|
3
|
+
import { gzipSync } from 'node:zlib';
|
|
4
|
+
import { fileOf, isUnder, posixPath, relativeInside } from "../module-paths.js";
|
|
5
|
+
/** The package name of a `node_modules` id (`@scope/name` or `name`), or null. */
|
|
6
|
+
export const packageNameOf = (id) => {
|
|
7
|
+
const normalized = posixPath(id);
|
|
8
|
+
const marker = normalized.lastIndexOf('/node_modules/');
|
|
9
|
+
if (marker < 0)
|
|
10
|
+
return null;
|
|
11
|
+
const parts = normalized.slice(marker + '/node_modules/'.length).split('/');
|
|
12
|
+
if (parts[0]?.startsWith('@'))
|
|
13
|
+
return parts.length > 1 ? `${parts[0]}/${parts[1]}` : null;
|
|
14
|
+
return parts[0] || null;
|
|
15
|
+
};
|
|
16
|
+
export const classifyModule = (id, classifier) => {
|
|
17
|
+
if (id.startsWith('\0')) {
|
|
18
|
+
const virtual = id.slice(1);
|
|
19
|
+
if (virtual === classifier.embeddedAssetsId)
|
|
20
|
+
return { group: 'assets', package: null };
|
|
21
|
+
if (virtual.startsWith('virtual:pix3/'))
|
|
22
|
+
return { group: 'player', package: null };
|
|
23
|
+
return { group: 'other', package: null };
|
|
24
|
+
}
|
|
25
|
+
const file = fileOf(id);
|
|
26
|
+
if (isUnder(file, classifier.runtimeSrc))
|
|
27
|
+
return { group: 'runtime', package: null };
|
|
28
|
+
if (isUnder(file, classifier.playerDir))
|
|
29
|
+
return { group: 'player', package: null };
|
|
30
|
+
const pkg = packageNameOf(file);
|
|
31
|
+
if (pkg === 'three')
|
|
32
|
+
return { group: 'three', package: pkg };
|
|
33
|
+
if (pkg)
|
|
34
|
+
return { group: 'dependencies', package: pkg };
|
|
35
|
+
if (isUnder(file, classifier.root))
|
|
36
|
+
return { group: 'project', package: null };
|
|
37
|
+
return { group: 'other', package: null };
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* A readable id: `@pix3/runtime/src/…` for the runtime, `<package>/…` inside `node_modules`,
|
|
41
|
+
* relative to the root for the project's own files.
|
|
42
|
+
*/
|
|
43
|
+
export const displayId = (id, classifier) => {
|
|
44
|
+
if (id.startsWith('\0'))
|
|
45
|
+
return id.slice(1);
|
|
46
|
+
const file = fileOf(id);
|
|
47
|
+
const inRuntime = classifier.runtimeSrc && relativeInside(classifier.runtimeSrc, file);
|
|
48
|
+
if (inRuntime)
|
|
49
|
+
return `@pix3/runtime/src/${inRuntime}`;
|
|
50
|
+
const posix = posixPath(file);
|
|
51
|
+
const marker = posix.lastIndexOf('/node_modules/');
|
|
52
|
+
if (marker >= 0)
|
|
53
|
+
return posix.slice(marker + '/node_modules/'.length);
|
|
54
|
+
return relativeInside(classifier.root, file) ?? posix;
|
|
55
|
+
};
|
|
56
|
+
const GROUPS = [
|
|
57
|
+
'three',
|
|
58
|
+
'runtime',
|
|
59
|
+
'player',
|
|
60
|
+
'project',
|
|
61
|
+
'assets',
|
|
62
|
+
'dependencies',
|
|
63
|
+
'other',
|
|
64
|
+
];
|
|
65
|
+
export const summarizeCode = (sizes, largest = 30) => {
|
|
66
|
+
const renderedBytes = sizes.modules.reduce((sum, m) => sum + m.renderedBytes, 0);
|
|
67
|
+
const groups = {};
|
|
68
|
+
for (const group of GROUPS) {
|
|
69
|
+
const members = sizes.modules.filter(m => m.group === group);
|
|
70
|
+
const rendered = members.reduce((sum, m) => sum + m.renderedBytes, 0);
|
|
71
|
+
const share = renderedBytes > 0 ? rendered / renderedBytes : 0;
|
|
72
|
+
groups[group] = {
|
|
73
|
+
modules: members.length,
|
|
74
|
+
renderedBytes: rendered,
|
|
75
|
+
estimatedMinifiedBytes: Math.round(share * sizes.bundleBytes),
|
|
76
|
+
share: Math.round(share * 1000) / 1000,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const packages = {};
|
|
80
|
+
for (const m of sizes.modules) {
|
|
81
|
+
if (m.package && m.renderedBytes > 0) {
|
|
82
|
+
packages[m.package] = (packages[m.package] ?? 0) + m.renderedBytes;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const sortedPackages = Object.fromEntries(Object.entries(packages).sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1)));
|
|
86
|
+
const largestModules = [...sizes.modules]
|
|
87
|
+
.sort((a, b) => b.renderedBytes - a.renderedBytes || (a.id < b.id ? -1 : 1))
|
|
88
|
+
.slice(0, largest);
|
|
89
|
+
return {
|
|
90
|
+
bundleBytes: sizes.bundleBytes,
|
|
91
|
+
renderedBytes,
|
|
92
|
+
groups,
|
|
93
|
+
packages: sortedPackages,
|
|
94
|
+
largestModules,
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
export const gzipBytesOf = (bytes) => gzipSync(bytes, { level: 6 }).byteLength;
|
|
98
|
+
/** `<outDir>/<artifact basename without extension>.report.json`. */
|
|
99
|
+
export const buildReportPath = (artifactPath, outDir) => join(outDir, `${basename(artifactPath, extname(artifactPath))}.report.json`);
|
|
100
|
+
export const writeBuildReport = (path, report) => {
|
|
101
|
+
writeFileSync(path, `${JSON.stringify(report, null, 2)}\n`);
|
|
102
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { LocalizationSettings, ProjectManifestInfo } from './project-manifest.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The build's scan of the project (plan §B.6 item 1): every text source outside `node_modules`,
|
|
4
|
+
* `dist`, `.pix3`, … is read once, and from that one pass come
|
|
5
|
+
*
|
|
6
|
+
* - `mentionedNames` — every identifier-like token (plus `core:Follow`-shaped ids), the superset
|
|
7
|
+
* the strip asks "does anything mention this module?" (a false positive costs kilobytes, a
|
|
8
|
+
* false negative would ship a broken game — `../pix3/.plans/done/playable-export-size.md` §2 Р4);
|
|
9
|
+
* - the asset set: every scene and prefab, everything they and the scripts name by `res://`,
|
|
10
|
+
* directories those references expand to, Spine atlas pages, the declared locale tables and
|
|
11
|
+
* their sprites, the project's fonts (no pre-packed atlas: nothing in 2.x writes one);
|
|
12
|
+
* - whether Spine, `postprocessing` or the multiplayer stack are used at all.
|
|
13
|
+
*
|
|
14
|
+
* Node port of 1.x `ProjectBuildService.collectAssetPaths` / `scanMentionedNames`, widened to
|
|
15
|
+
* `src/**` and every other text source as the plan asks (DeepCore imports runtime nodes from
|
|
16
|
+
* `src/`). Scene paths are `res://`-relative (under `resRoot`); the scan reports them without the
|
|
17
|
+
* scheme.
|
|
18
|
+
*/
|
|
19
|
+
export interface ScanOptions {
|
|
20
|
+
readonly root: string;
|
|
21
|
+
/** Where `res://` points, relative to the root (`'.'` by default). */
|
|
22
|
+
readonly resRoot: string;
|
|
23
|
+
readonly manifest: ProjectManifestInfo;
|
|
24
|
+
/** `pix3({ entryScene })`: a `res://`-relative scene path. */
|
|
25
|
+
readonly entryScene?: string | null;
|
|
26
|
+
/**
|
|
27
|
+
* What the dependencies that declare the runtime mention and import (`scanDependencyImports`):
|
|
28
|
+
* they count for the `uses*` answers as the project's own sources do.
|
|
29
|
+
*/
|
|
30
|
+
readonly dependencies?: SourceMentions;
|
|
31
|
+
}
|
|
32
|
+
/** Identifiers and bare import specifiers found in a set of sources. */
|
|
33
|
+
export interface SourceMentions {
|
|
34
|
+
readonly mentionedNames: ReadonlySet<string>;
|
|
35
|
+
/** `from 'x'`, `import('x')`, `import 'x'`, `require('x')` in code sources. */
|
|
36
|
+
readonly importedSpecifiers: ReadonlySet<string>;
|
|
37
|
+
}
|
|
38
|
+
export interface ProjectScan {
|
|
39
|
+
/** Navigable scenes (`res://`-relative, sorted): every `.pix3scene` that is not a prefab. */
|
|
40
|
+
readonly scenePaths: readonly string[];
|
|
41
|
+
/** The scene the player boots into; `''` when the project has no scene. */
|
|
42
|
+
readonly entryScenePath: string;
|
|
43
|
+
/** Every shipped file, `res://`-relative and sorted (scenes and prefabs included). */
|
|
44
|
+
readonly assetPaths: readonly string[];
|
|
45
|
+
readonly mentionedNames: ReadonlySet<string>;
|
|
46
|
+
/** Module specifiers the project's code sources import (see {@link SourceMentions}). */
|
|
47
|
+
readonly importedSpecifiers: ReadonlySet<string>;
|
|
48
|
+
readonly usesSpine: boolean;
|
|
49
|
+
readonly usesPostProcessing: boolean;
|
|
50
|
+
readonly usesNetwork: boolean;
|
|
51
|
+
/** The effective localization config (manifest block, else discovered `locales/`). */
|
|
52
|
+
readonly localization: LocalizationSettings | null;
|
|
53
|
+
/** Prefab paths for the multiplayer kind table, sorted by code point. */
|
|
54
|
+
readonly netKindPrefabs: readonly string[];
|
|
55
|
+
readonly warnings: readonly string[];
|
|
56
|
+
/** How many text sources were read (for the build log). */
|
|
57
|
+
readonly textSourceCount: number;
|
|
58
|
+
}
|
|
59
|
+
/** Every identifier-like token of a text (the strip's mention superset) into `names`. */
|
|
60
|
+
export declare const collectMentionedNames: (text: string, names: Set<string>) => void;
|
|
61
|
+
/** Every module specifier a code text imports, statically or not, into `specifiers`. */
|
|
62
|
+
export declare const collectImportedSpecifiers: (text: string, specifiers: Set<string>) => void;
|
|
63
|
+
/** Names that mean "this project talks to the multiplayer stack". */
|
|
64
|
+
export declare const NETWORK_MENTION_NAMES: readonly ["network", "netNodes", "Network", "NetworkService", "NetworkedNode", "ReplicatedTransform", "NetworkedNodeBehavior", "ReplicatedTransformBehavior", "core:NetworkedNode", "core:ReplicatedTransform"];
|
|
65
|
+
/**
|
|
66
|
+
* A `.pix3scene` that is instantiated rather than navigated to: `prefabs/`, `.prefab`, and
|
|
67
|
+
* `scenes/ui/` overlays (the conventions of the 1.x exporter and `pix3 smoke`).
|
|
68
|
+
*/
|
|
69
|
+
export declare const isPrefabPath: (path: string) => boolean;
|
|
70
|
+
/** A scene the player can boot into or `changeScene` to: a `.pix3scene` that is not a prefab. */
|
|
71
|
+
export declare const isPlayableScenePath: (path: string) => boolean;
|
|
72
|
+
/** Page image names declared inside an `.atlas` file (port of `parseSpineAtlasPageNames`). */
|
|
73
|
+
export declare const parseSpineAtlasPageNames: (atlasText: string) => string[];
|
|
74
|
+
/** Every file under `root` as a wire path, excluding tooling, build output and `.pix3/`. */
|
|
75
|
+
export declare const listProjectFiles: (root: string) => Promise<string[]>;
|
|
76
|
+
export declare const scanProject: (options: ScanOptions) => Promise<ProjectScan>;
|
|
77
|
+
/** Zero-config localization: `locales/*.json` → `en` if present, else the first (editor rule). */
|
|
78
|
+
export declare const discoverLocalization: (resFiles: ReadonlySet<string>) => LocalizationSettings | null;
|
|
79
|
+
/**
|
|
80
|
+
* The scene the player boots into, the same in dev and in a build: `pix3({ entryScene })`, else
|
|
81
|
+
* `pix3project.yaml` `defaultExportScenePath`, else `scenes/main.pix3scene`, else the first scene.
|
|
82
|
+
* A named scene that is not one of `scenePaths` is skipped with a warning.
|
|
83
|
+
*/
|
|
84
|
+
export declare const resolveEntryScene: (scenePaths: readonly string[], manifest: ProjectManifestInfo, requested: string | null | undefined, warnings: string[]) => string;
|