svelte-shaker 0.16.1 → 0.17.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/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/mono.d.ts +14 -1
- package/dist/mono.js +7 -14
- package/dist/native-engine.d.ts +17 -5
- package/dist/native-engine.js +24 -28
- package/dist/rsvelte-parse.d.ts +10 -0
- package/dist/rsvelte-parse.js +21 -0
- package/dist/vite.js +29 -8
- package/dist/wasm-engine.d.ts +5 -5
- package/dist/wasm-engine.js +4 -15
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ReadFile, type Resolve } from './analyze.js';
|
|
2
2
|
import { type Parse } from './parse.js';
|
|
3
|
-
import { type MonomorphizeOptions, type MonomorphizeResult } from './mono.js';
|
|
3
|
+
import { type MonomorphizeOptions, type MonomorphizeResult, type OwnSize } from './mono.js';
|
|
4
4
|
import type { ComponentId } from './ir.js';
|
|
5
5
|
export type { ComponentId, AnalyzeInput, InputFile, ResolvedEdge, EdgeKind, EditResult, } from './ir.js';
|
|
6
6
|
export type { Resolve, ReadFile, ResolveSync, ReadFileSync } from './analyze.js';
|
|
@@ -9,7 +9,7 @@ export { analyze, analyzeInput, buildAnalyzeInput, buildAnalyzeInputSync, deadSp
|
|
|
9
9
|
export type { UnpassedProp } from './analyze.js';
|
|
10
10
|
export { DevShaker, type DevMode, type DevShakerChange } from './engine.js';
|
|
11
11
|
export { transformAll, transformAllWithMono } from './transform.js';
|
|
12
|
-
export { monomorphize, DEFAULT_MONO_OPTIONS, type MonomorphizeOptions, type MonomorphizeResult, type Variant, type CallSiteBinding, } from './mono.js';
|
|
12
|
+
export { monomorphize, DEFAULT_MONO_OPTIONS, type MonomorphizeOptions, type MonomorphizeResult, type OwnSize, type Variant, type CallSiteBinding, } from './mono.js';
|
|
13
13
|
/**
|
|
14
14
|
* Whole-program shake: crawl the component graph from `entry`, decide what to
|
|
15
15
|
* fold, and return the shaken source for every reachable `.svelte` file.
|
|
@@ -45,4 +45,4 @@ export type VariantSpecifier = (variantId: string) => string;
|
|
|
45
45
|
* specialized and `files` equals the unused-prop fold / constant fold / value-set narrowing output exactly — a strict
|
|
46
46
|
* superset of the default behavior, so existing consumers are unaffected.
|
|
47
47
|
*/
|
|
48
|
-
export declare function svelteShakerWithMono(entries: ComponentId | ComponentId[], resolve: Resolve, readFile: ReadFile, mono?: MonomorphizeOptions, variantSpecifier?: VariantSpecifier, parse?: Parse, escaped?: ComponentId[]): Promise<ShakeResult>;
|
|
48
|
+
export declare function svelteShakerWithMono(entries: ComponentId | ComponentId[], resolve: Resolve, readFile: ReadFile, mono?: MonomorphizeOptions, variantSpecifier?: VariantSpecifier, parse?: Parse, escaped?: ComponentId[], ownSize?: OwnSize): Promise<ShakeResult>;
|
package/dist/index.js
CHANGED
|
@@ -46,7 +46,7 @@ async function analyzeWith(entries, resolve, readFile, parse, escaped = []) {
|
|
|
46
46
|
* specialized and `files` equals the unused-prop fold / constant fold / value-set narrowing output exactly — a strict
|
|
47
47
|
* superset of the default behavior, so existing consumers are unaffected.
|
|
48
48
|
*/
|
|
49
|
-
export async function svelteShakerWithMono(entries, resolve, readFile, mono = DEFAULT_MONO_OPTIONS, variantSpecifier = (id) => id, parse, escaped = []) {
|
|
49
|
+
export async function svelteShakerWithMono(entries, resolve, readFile, mono = DEFAULT_MONO_OPTIONS, variantSpecifier = (id) => id, parse, escaped = [], ownSize) {
|
|
50
50
|
const { models, plans } = await analyzeWith(entries, resolve, readFile, parse, escaped);
|
|
51
51
|
// The cascade may re-run the transform with force-bailed plans, so recompute
|
|
52
52
|
// monomorphization inside it: a bailed component must not be specialized either.
|
|
@@ -58,7 +58,7 @@ export async function svelteShakerWithMono(entries, resolve, readFile, mono = DE
|
|
|
58
58
|
const files = shakeWithRevertCascade(models, plans, (p) => {
|
|
59
59
|
// Thread the shake entries through so the net-win gate can compute module
|
|
60
60
|
// reachability from them (docs §3 monomorphization, §13.2).
|
|
61
|
-
lastResult = monomorphize(models, p, mono, entries);
|
|
61
|
+
lastResult = monomorphize(models, p, mono, entries, ownSize);
|
|
62
62
|
// With no bindings the wired pass and the base pass are identical, so reuse
|
|
63
63
|
// the plain transform to keep the default path byte-for-byte unchanged.
|
|
64
64
|
return lastResult.bindings.length === 0
|
package/dist/mono.d.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { type FileModel } from './analyze.js';
|
|
2
2
|
import { type AnyNode } from './parse.js';
|
|
3
3
|
import { type ComponentId, type ComponentPlan, type Literal } from './ir.js';
|
|
4
|
+
/**
|
|
5
|
+
* The monomorphization net-win gate's per-module compiled-byte size proxy (docs §3):
|
|
6
|
+
* `(id, source) -> byte count | null`, where `null` means the module can't be sized
|
|
7
|
+
* (a compile error) and the gate declines the child rather than bloat.
|
|
8
|
+
*
|
|
9
|
+
* INJECTED by the Shell so the environment-free engine holds no compiler: the Vite
|
|
10
|
+
* plugin passes an rsvelte-backed sizer (`@rsvelte/compiler`'s `compile_client`),
|
|
11
|
+
* and the native engine computes the SAME proxy in-process (`session::own_size`), so
|
|
12
|
+
* all three engines' gates decide byte-for-byte alike (parity is test-gated). The
|
|
13
|
+
* default (`() => null`) sizes nothing, so an environment without a sizer simply
|
|
14
|
+
* specializes nothing — sound, just unoptimized.
|
|
15
|
+
*/
|
|
16
|
+
export type OwnSize = (id: ComponentId, source: string) => number | null;
|
|
4
17
|
/** Tuning knobs for monomorphization (docs §8.1, §13.2). All have sound defaults. */
|
|
5
18
|
export interface MonomorphizeOptions {
|
|
6
19
|
/** Master switch. Default OFF — every existing behavior is unchanged. */
|
|
@@ -79,4 +92,4 @@ export interface MonomorphizeResult {
|
|
|
79
92
|
* inputs and never touches the base transform, so with monomorphization off (or when no child
|
|
80
93
|
* passes the gate) the default whole-program output is byte-for-byte unchanged.
|
|
81
94
|
*/
|
|
82
|
-
export declare function monomorphize(models: Map<ComponentId, FileModel>, plans: Map<ComponentId, ComponentPlan>, options?: MonomorphizeOptions, entries?: ComponentId | ComponentId[]): MonomorphizeResult;
|
|
95
|
+
export declare function monomorphize(models: Map<ComponentId, FileModel>, plans: Map<ComponentId, ComponentPlan>, options?: MonomorphizeOptions, entries?: ComponentId | ComponentId[], measureSize?: OwnSize): MonomorphizeResult;
|
package/dist/mono.js
CHANGED
|
@@ -55,7 +55,6 @@
|
|
|
55
55
|
// — a strict, measured net reduction. When in doubt we keep the base.
|
|
56
56
|
// ----------------------------------------------------------------------
|
|
57
57
|
import MagicString from 'magic-string';
|
|
58
|
-
import { compile } from 'svelte/compiler';
|
|
59
58
|
import { inSpans } from './dead.js';
|
|
60
59
|
import { shakeBody } from './transform.js';
|
|
61
60
|
import { readCallSite, deadSpansForPlans, isFoldBlockedName, } from './analyze.js';
|
|
@@ -79,7 +78,7 @@ export const DEFAULT_MONO_OPTIONS = {
|
|
|
79
78
|
* inputs and never touches the base transform, so with monomorphization off (or when no child
|
|
80
79
|
* passes the gate) the default whole-program output is byte-for-byte unchanged.
|
|
81
80
|
*/
|
|
82
|
-
export function monomorphize(models, plans, options = DEFAULT_MONO_OPTIONS, entries = []) {
|
|
81
|
+
export function monomorphize(models, plans, options = DEFAULT_MONO_OPTIONS, entries = [], measureSize = () => null) {
|
|
83
82
|
const variants = new Map();
|
|
84
83
|
const bindings = [];
|
|
85
84
|
if (!options.enabled)
|
|
@@ -163,23 +162,17 @@ export function monomorphize(models, plans, options = DEFAULT_MONO_OPTIONS, entr
|
|
|
163
162
|
incoming.add(c);
|
|
164
163
|
const entryList = (Array.isArray(entries) ? entries : [entries]).filter((e) => models.has(e));
|
|
165
164
|
const roots = entryList.filter((e) => !incoming.has(e));
|
|
165
|
+
// Memoize the injected size proxy by source string (measuring is the hot cost).
|
|
166
|
+
// `measureSize` is the rsvelte compiled-byte proxy the Shell injects (docs §3);
|
|
167
|
+
// its default (`() => null`) makes every module un-sizable, so the gate below
|
|
168
|
+
// declines every child — an environment with no sizer specializes nothing, which
|
|
169
|
+
// is sound (never bloat), just unoptimized.
|
|
166
170
|
const sizeCache = new Map();
|
|
167
171
|
const ownSize = (id, source) => {
|
|
168
172
|
const cached = sizeCache.get(source);
|
|
169
173
|
if (cached !== undefined)
|
|
170
174
|
return cached;
|
|
171
|
-
|
|
172
|
-
try {
|
|
173
|
-
const { js } = compile(source, {
|
|
174
|
-
generate: 'client',
|
|
175
|
-
dev: false,
|
|
176
|
-
filename: id,
|
|
177
|
-
});
|
|
178
|
-
size = js.code.length;
|
|
179
|
-
}
|
|
180
|
-
catch {
|
|
181
|
-
size = null; // un-sizable -> caller skips the child
|
|
182
|
-
}
|
|
175
|
+
const size = measureSize(id, source);
|
|
183
176
|
if (size !== null)
|
|
184
177
|
sizeCache.set(source, size);
|
|
185
178
|
return size;
|
package/dist/native-engine.d.ts
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
import { type ReadFile, type Resolve } from './analyze.js';
|
|
2
2
|
import { type MonomorphizeOptions } from './mono.js';
|
|
3
3
|
import type { ComponentId } from './ir.js';
|
|
4
|
-
/** The long-lived native session: parse + retain ASTs, then shake to edits.
|
|
4
|
+
/** The long-lived native session: parse + retain ASTs, then shake to edits. The
|
|
5
|
+
* shake needs no size callback — the monomorphization net-win gate's compiled-byte
|
|
6
|
+
* proxy is computed in-process by rsvelte (`session::own_size`), so the native path
|
|
7
|
+
* never calls back into a JS compiler. */
|
|
5
8
|
interface ShakeSession {
|
|
6
9
|
parse: (inputJson: string) => string;
|
|
7
10
|
parseMore: (inputJson: string) => string;
|
|
8
|
-
shake: (configJson: string
|
|
11
|
+
shake: (configJson: string) => string;
|
|
9
12
|
}
|
|
10
13
|
/** The subset of the napi addon the plugin uses. */
|
|
11
14
|
interface NativeEngine {
|
|
12
15
|
ShakeSession: new () => ShakeSession;
|
|
16
|
+
engineApiVersion: () => number;
|
|
13
17
|
}
|
|
18
|
+
/** Whether a loaded module is a native addon this engine can drive: the right ABI
|
|
19
|
+
* generation AND the `ShakeSession` shape (`parse` + `parseMore` + `shake`). An OLDER
|
|
20
|
+
* published `svelte-shaker-engine-scan-native` — one with no `engineApiVersion`, a
|
|
21
|
+
* different generation, or predating `parseMore` — is REJECTED so the caller degrades
|
|
22
|
+
* to WASM/JS instead of mis-calling an incompatible binary and crashing the build. */
|
|
23
|
+
export declare function hasSessionApi(mod: Partial<NativeEngine>): mod is NativeEngine;
|
|
14
24
|
/**
|
|
15
25
|
* Load the native Rust (napi) engine, or `null` if it can't be loaded (then the caller
|
|
16
26
|
* falls back to the WASM / JS engine). Two locations are tried in order:
|
|
@@ -39,9 +49,11 @@ export interface NativeMonoResult {
|
|
|
39
49
|
* parse); the crawl then resolves edges reading those facts instead of re-parsing in
|
|
40
50
|
* JS, parsing any file discovered outside the seed on demand (`parseMore`). The
|
|
41
51
|
* session retains every AST, so the shake needs no AST at the boundary — only the
|
|
42
|
-
* resolved graph and the
|
|
43
|
-
*
|
|
44
|
-
*
|
|
52
|
+
* resolved graph crosses, and the monomorphization size proxy is computed in-process
|
|
53
|
+
* by rsvelte (`session::own_size`), so nothing calls back into a JS compiler. A final
|
|
54
|
+
* svelte/compiler revert cascade (the AUTHORITY) force-bails any residual unparseable
|
|
55
|
+
* output; the session's own inner rsvelte cascade means valid programs settle in one
|
|
56
|
+
* outer pass.
|
|
45
57
|
*/
|
|
46
58
|
export declare function svelteShakerNativeWithMono(engine: NativeEngine, entries: ComponentId | ComponentId[], resolve: Resolve, readFile: ReadFile, mono: MonomorphizeOptions, escaped?: ComponentId[]): Promise<NativeMonoResult>;
|
|
47
59
|
export {};
|
package/dist/native-engine.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
-
import { compile } from 'svelte/compiler';
|
|
3
2
|
import { buildAnalyzeInput, } from './analyze.js';
|
|
4
3
|
import {} from './mono.js';
|
|
5
4
|
import { revertCascade } from './revert-cascade.js';
|
|
@@ -10,12 +9,23 @@ import { revertCascade } from './revert-cascade.js';
|
|
|
10
9
|
// whole-program AST ever crosses the JS boundary — the crawl reads per-file FACTS from
|
|
11
10
|
// the session instead of re-parsing in JS (docs/RUST-MIGRATION.md M3).
|
|
12
11
|
const require = createRequire(import.meta.url);
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
15
|
-
* `
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
|
|
12
|
+
/** The addon ABI generation this driver speaks (native `engine_api_version`). Bump
|
|
13
|
+
* in lockstep with a breaking change to the exported methods. The 0.2.x addon's
|
|
14
|
+
* `shake` took a JS `ownSize` callback; 0.3.x computes the size proxy in Rust and
|
|
15
|
+
* `shake` takes ONE argument — calling a 0.2.x binary the 0.3.x way throws a napi
|
|
16
|
+
* TypeError that crashes `vite build`, so a version mismatch MUST be rejected here. */
|
|
17
|
+
const EXPECTED_ENGINE_API_VERSION = 3;
|
|
18
|
+
/** Whether a loaded module is a native addon this engine can drive: the right ABI
|
|
19
|
+
* generation AND the `ShakeSession` shape (`parse` + `parseMore` + `shake`). An OLDER
|
|
20
|
+
* published `svelte-shaker-engine-scan-native` — one with no `engineApiVersion`, a
|
|
21
|
+
* different generation, or predating `parseMore` — is REJECTED so the caller degrades
|
|
22
|
+
* to WASM/JS instead of mis-calling an incompatible binary and crashing the build. */
|
|
23
|
+
export function hasSessionApi(mod) {
|
|
24
|
+
const versionFn = mod.engineApiVersion;
|
|
25
|
+
// The 0.2.x addon has no `engineApiVersion` export, so this rejects it — the exact
|
|
26
|
+
// skew that would otherwise throw on the new one-argument `shake` call.
|
|
27
|
+
if (typeof versionFn !== 'function' || versionFn() !== EXPECTED_ENGINE_API_VERSION)
|
|
28
|
+
return false;
|
|
19
29
|
const ctor = mod.ShakeSession;
|
|
20
30
|
const proto = ctor?.prototype;
|
|
21
31
|
return (typeof ctor === 'function' &&
|
|
@@ -41,7 +51,7 @@ export function tryLoadNativeEngine() {
|
|
|
41
51
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
42
52
|
const mod = require(spec);
|
|
43
53
|
if (hasSessionApi(mod))
|
|
44
|
-
return { ShakeSession: mod.ShakeSession };
|
|
54
|
+
return { ShakeSession: mod.ShakeSession, engineApiVersion: mod.engineApiVersion };
|
|
45
55
|
}
|
|
46
56
|
catch {
|
|
47
57
|
// Not resolvable here — try the next location.
|
|
@@ -49,22 +59,6 @@ export function tryLoadNativeEngine() {
|
|
|
49
59
|
}
|
|
50
60
|
return null;
|
|
51
61
|
}
|
|
52
|
-
/** The compiled-byte size proxy the monomorphization net-win gate uses — the same call
|
|
53
|
-
* `mono.ts` / the WASM engine make, so the Rust gate decides byte-for-byte alike. */
|
|
54
|
-
function ownSize(id, source) {
|
|
55
|
-
try {
|
|
56
|
-
return compile(source, { generate: 'client', dev: false, filename: id }).js.code.length;
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
/** `ShakeSession.shake`'s single-arg `ownSize` form: `[id, source]` JSON in (a napi
|
|
63
|
-
* multi-arg marshaling bug makes the single-arg payload the reliable shape). */
|
|
64
|
-
function ownSizePayload(payload) {
|
|
65
|
-
const [id, source] = JSON.parse(payload);
|
|
66
|
-
return ownSize(id, source);
|
|
67
|
-
}
|
|
68
62
|
/**
|
|
69
63
|
* One native facts record → the crawl's {@link CrawlFacts}. An unparseable file is
|
|
70
64
|
* treated as contributing nothing (its retained AST is Null, so the shake skips it —
|
|
@@ -89,9 +83,11 @@ function toCrawlFacts(f) {
|
|
|
89
83
|
* parse); the crawl then resolves edges reading those facts instead of re-parsing in
|
|
90
84
|
* JS, parsing any file discovered outside the seed on demand (`parseMore`). The
|
|
91
85
|
* session retains every AST, so the shake needs no AST at the boundary — only the
|
|
92
|
-
* resolved graph and the
|
|
93
|
-
*
|
|
94
|
-
*
|
|
86
|
+
* resolved graph crosses, and the monomorphization size proxy is computed in-process
|
|
87
|
+
* by rsvelte (`session::own_size`), so nothing calls back into a JS compiler. A final
|
|
88
|
+
* svelte/compiler revert cascade (the AUTHORITY) force-bails any residual unparseable
|
|
89
|
+
* output; the session's own inner rsvelte cascade means valid programs settle in one
|
|
90
|
+
* outer pass.
|
|
95
91
|
*/
|
|
96
92
|
export async function svelteShakerNativeWithMono(engine, entries, resolve, readFile, mono, escaped = []) {
|
|
97
93
|
const seedIds = Array.isArray(entries) ? entries : [entries];
|
|
@@ -135,7 +131,7 @@ export async function svelteShakerNativeWithMono(engine, entries, resolve, readF
|
|
|
135
131
|
};
|
|
136
132
|
let last;
|
|
137
133
|
const files = revertCascade(input.files, (forceBail) => {
|
|
138
|
-
last = JSON.parse(session.shake(JSON.stringify({ ...config, forceBail: [...forceBail] })
|
|
134
|
+
last = JSON.parse(session.shake(JSON.stringify({ ...config, forceBail: [...forceBail] })));
|
|
139
135
|
return last.files;
|
|
140
136
|
});
|
|
141
137
|
// The engine already keys each variant by its `?shaker_variant=` request specifier
|
package/dist/rsvelte-parse.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Parse } from './parse.js';
|
|
2
|
+
import type { OwnSize } from './mono.js';
|
|
2
3
|
/**
|
|
3
4
|
* Build a {@link Parse} backed by rsvelte's parser (`@rsvelte/compiler`, a
|
|
4
5
|
* bundled WASM dependency), or `null` if that module can't be loaded/initialized
|
|
@@ -13,3 +14,12 @@ import type { Parse } from './parse.js';
|
|
|
13
14
|
* a load/init failure returns `null`.
|
|
14
15
|
*/
|
|
15
16
|
export declare function tryLoadRsvelteParser(): Parse | null;
|
|
17
|
+
/**
|
|
18
|
+
* Build the rsvelte-backed {@link OwnSize} for the JS/WASM engines, or `null` if
|
|
19
|
+
* `@rsvelte/compiler` can't be loaded/initialized. The native engine computes the
|
|
20
|
+
* SAME proxy in-process (`session::own_size` over the pinned rsvelte crate), so all
|
|
21
|
+
* three engines' monomorphization gates decide byte-for-byte alike (parity is
|
|
22
|
+
* test-gated). `name` is passed as the component id — its exact string is immaterial
|
|
23
|
+
* to the SIZE as long as every engine passes the same one, which they do.
|
|
24
|
+
*/
|
|
25
|
+
export declare function tryLoadRsvelteOwnSize(): OwnSize | null;
|
package/dist/rsvelte-parse.js
CHANGED
|
@@ -56,3 +56,24 @@ export function tryLoadRsvelteParser() {
|
|
|
56
56
|
return JSON.parse(result.ast);
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Build the rsvelte-backed {@link OwnSize} for the JS/WASM engines, or `null` if
|
|
61
|
+
* `@rsvelte/compiler` can't be loaded/initialized. The native engine computes the
|
|
62
|
+
* SAME proxy in-process (`session::own_size` over the pinned rsvelte crate), so all
|
|
63
|
+
* three engines' monomorphization gates decide byte-for-byte alike (parity is
|
|
64
|
+
* test-gated). `name` is passed as the component id — its exact string is immaterial
|
|
65
|
+
* to the SIZE as long as every engine passes the same one, which they do.
|
|
66
|
+
*/
|
|
67
|
+
export function tryLoadRsvelteOwnSize() {
|
|
68
|
+
let compiler;
|
|
69
|
+
try {
|
|
70
|
+
compiler = loadCompiler();
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return (id, source) => {
|
|
76
|
+
const result = compiler.compile_client(source, id);
|
|
77
|
+
return result.success ? result.js.length : null;
|
|
78
|
+
};
|
|
79
|
+
}
|
package/dist/vite.js
CHANGED
|
@@ -10,7 +10,7 @@ import { compileExclude } from './exclude.js';
|
|
|
10
10
|
// Re-export so a user can extend the default dev-only list: `devOnly: [...DEFAULT_DEV_ONLY, '…']`.
|
|
11
11
|
export { DEFAULT_DEV_ONLY } from './dev-only.js';
|
|
12
12
|
import { DEFAULT_MONO_OPTIONS } from './mono.js';
|
|
13
|
-
import { tryLoadRsvelteParser } from './rsvelte-parse.js';
|
|
13
|
+
import { tryLoadRsvelteParser, tryLoadRsvelteOwnSize } from './rsvelte-parse.js';
|
|
14
14
|
import { svelteShakerWasm, svelteShakerWasmWithMono, tryLoadWasmEngine } from './wasm-engine.js';
|
|
15
15
|
import { svelteShakerNativeWithMono, tryLoadNativeEngine } from './native-engine.js';
|
|
16
16
|
/** kB with two decimals, the unit Vite itself uses in its build size report. */
|
|
@@ -474,6 +474,13 @@ export function shaker(options = {}) {
|
|
|
474
474
|
});
|
|
475
475
|
reportEscapeDiagnostics(escapeScan);
|
|
476
476
|
const escaped = escapeScan.escaped;
|
|
477
|
+
// The monomorphization net-win gate's size proxy, computed by rsvelte's client
|
|
478
|
+
// codegen (`@rsvelte/compiler`), for the JS and WASM engines so their gate decides
|
|
479
|
+
// byte-for-byte like the native engine (which computes the SAME proxy in-process).
|
|
480
|
+
// Loaded lazily by the js/wasm mono branches only — the native path sizes in Rust
|
|
481
|
+
// and never needs it. If `@rsvelte/compiler` can't load, sizing returns null and
|
|
482
|
+
// the gate specializes nothing — sound (never bloat), just unoptimized.
|
|
483
|
+
const loadOwnSize = () => tryLoadRsvelteOwnSize() ?? (() => null);
|
|
477
484
|
// Decide the engine. The native Rust engine now implements every pass
|
|
478
485
|
// INCLUDING monomorphization (it calls back to JS only for the compiled-size
|
|
479
486
|
// proxy), so it
|
|
@@ -518,17 +525,31 @@ export function shaker(options = {}) {
|
|
|
518
525
|
// retained ASTs, and returns only the edits — byte-identical to the JS engine,
|
|
519
526
|
// monomorphization included (mono off -> an empty variant set). The `parser`
|
|
520
527
|
// option does not apply here (the session always parses in-process rsvelte).
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
528
|
+
//
|
|
529
|
+
// Defense in depth: `tryLoadNativeEngine` already rejects an ABI-incompatible
|
|
530
|
+
// binary (`engineApiVersion`), but any OTHER native failure (a napi marshaling
|
|
531
|
+
// error, an unforeseen throw) must NOT crash the build — degrade to the JS
|
|
532
|
+
// engine with a warning. `session.shake` is `catch_unwind`, so a native panic
|
|
533
|
+
// surfaces here as a throw rather than aborting the process.
|
|
534
|
+
try {
|
|
535
|
+
const result = await svelteShakerNativeWithMono(native, entryComponents, resolve, read, mono, escaped);
|
|
536
|
+
shaken = result.files;
|
|
537
|
+
variantSources = result.variants;
|
|
538
|
+
reportSizes(shaken, read, root, options.verbose === true, log);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
catch (err) {
|
|
542
|
+
warn(`the native engine failed at runtime (${err instanceof Error ? err.message : String(err)}); ` +
|
|
543
|
+
`falling back to the JS engine for this build`);
|
|
544
|
+
native = null;
|
|
545
|
+
wasm = null; // fall through to the always-available JS engine below
|
|
546
|
+
}
|
|
526
547
|
}
|
|
527
548
|
if (wasm) {
|
|
528
549
|
// Native Rust engine — byte-identical to the JS engine, including
|
|
529
550
|
// monomorphization.
|
|
530
551
|
if (mono.enabled) {
|
|
531
|
-
const result = await svelteShakerWasmWithMono(wasm, entryComponents, resolve, read, mono, getParse(true), escaped);
|
|
552
|
+
const result = await svelteShakerWasmWithMono(wasm, entryComponents, resolve, read, mono, loadOwnSize(), getParse(true), escaped);
|
|
532
553
|
shaken = result.files;
|
|
533
554
|
variantSources = result.variants;
|
|
534
555
|
}
|
|
@@ -547,7 +568,7 @@ export function shaker(options = {}) {
|
|
|
547
568
|
reportSizes(shaken, read, root, options.verbose === true, log);
|
|
548
569
|
return;
|
|
549
570
|
}
|
|
550
|
-
const result = await svelteShakerWithMono(entryComponents, resolve, read, mono, variantSpecifier, getParse(false), escaped);
|
|
571
|
+
const result = await svelteShakerWithMono(entryComponents, resolve, read, mono, variantSpecifier, getParse(false), escaped, loadOwnSize());
|
|
551
572
|
shaken = result.files;
|
|
552
573
|
variantSources = new Map();
|
|
553
574
|
for (const v of result.mono.variants.values())
|
package/dist/wasm-engine.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ReadFile, type Resolve } from './analyze.js';
|
|
2
2
|
import { type Parse } from './parse.js';
|
|
3
|
-
import { type MonomorphizeOptions } from './mono.js';
|
|
3
|
+
import { type MonomorphizeOptions, type OwnSize } from './mono.js';
|
|
4
4
|
import type { ComponentId } from './ir.js';
|
|
5
5
|
/** The subset of the WASM exports the plugin uses (docs/RUST-MIGRATION.md M5+). */
|
|
6
6
|
interface WasmEngine {
|
|
@@ -48,9 +48,9 @@ export interface WasmMonoResult {
|
|
|
48
48
|
* Whole-program shake WITH monomorphization, run entirely in the native Rust
|
|
49
49
|
* engine — the counterpart of {@link svelteShakerWithMono}. The crawl/resolution
|
|
50
50
|
* stays in JS; the Rust engine does the analysis, the monomorphization graph/gate, and the
|
|
51
|
-
* call-site rewrite, calling back into JS only for
|
|
52
|
-
*
|
|
53
|
-
* byte-identical (pinned by the differential `wasm-mono`
|
|
51
|
+
* call-site rewrite, calling back into JS only for `ownSize` (the injected rsvelte
|
|
52
|
+
* size proxy). The native engine computes the SAME proxy in-process, so the results
|
|
53
|
+
* are byte-identical (pinned by the differential `wasm-mono` / native parity tests).
|
|
54
54
|
*/
|
|
55
|
-
export declare function svelteShakerWasmWithMono(engine: WasmEngine, entries: ComponentId | ComponentId[], resolve: Resolve, readFile: ReadFile, mono: MonomorphizeOptions, parse?: Parse, escaped?: ComponentId[]): Promise<WasmMonoResult>;
|
|
55
|
+
export declare function svelteShakerWasmWithMono(engine: WasmEngine, entries: ComponentId | ComponentId[], resolve: Resolve, readFile: ReadFile, mono: MonomorphizeOptions, ownSize: OwnSize, parse?: Parse, escaped?: ComponentId[]): Promise<WasmMonoResult>;
|
|
56
56
|
export {};
|
package/dist/wasm-engine.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
-
import { compile } from 'svelte/compiler';
|
|
3
2
|
import { buildAnalyzeInput } from './analyze.js';
|
|
4
3
|
import { parseCached } from './parse.js';
|
|
5
4
|
import {} from './mono.js';
|
|
@@ -35,16 +34,6 @@ export function tryLoadWasmEngine() {
|
|
|
35
34
|
}
|
|
36
35
|
return null;
|
|
37
36
|
}
|
|
38
|
-
/** The compiled-byte size proxy the monomorphization net-win gate uses — the same call
|
|
39
|
-
* `mono.ts` makes, so the Rust gate decides byte-for-byte like the JS engine. */
|
|
40
|
-
function ownSize(id, source) {
|
|
41
|
-
try {
|
|
42
|
-
return compile(source, { generate: 'client', dev: false, filename: id }).js.code.length;
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
37
|
/**
|
|
49
38
|
* Whole-program shake via the native Rust engine — the unused-prop fold / constant fold / value-set narrowing counterpart of
|
|
50
39
|
* {@link svelteShaker} (monomorphization lives only in the JS engine). The crawl/resolution
|
|
@@ -77,11 +66,11 @@ export async function svelteShakerWasm(engine, entries, resolve, readFile, parse
|
|
|
77
66
|
* Whole-program shake WITH monomorphization, run entirely in the native Rust
|
|
78
67
|
* engine — the counterpart of {@link svelteShakerWithMono}. The crawl/resolution
|
|
79
68
|
* stays in JS; the Rust engine does the analysis, the monomorphization graph/gate, and the
|
|
80
|
-
* call-site rewrite, calling back into JS only for
|
|
81
|
-
*
|
|
82
|
-
* byte-identical (pinned by the differential `wasm-mono`
|
|
69
|
+
* call-site rewrite, calling back into JS only for `ownSize` (the injected rsvelte
|
|
70
|
+
* size proxy). The native engine computes the SAME proxy in-process, so the results
|
|
71
|
+
* are byte-identical (pinned by the differential `wasm-mono` / native parity tests).
|
|
83
72
|
*/
|
|
84
|
-
export async function svelteShakerWasmWithMono(engine, entries, resolve, readFile, mono, parse, escaped = []) {
|
|
73
|
+
export async function svelteShakerWasmWithMono(engine, entries, resolve, readFile, mono, ownSize, parse, escaped = []) {
|
|
85
74
|
const cache = new Map();
|
|
86
75
|
const input = await buildAnalyzeInput(entries, resolve, readFile, cache, parse, escaped);
|
|
87
76
|
const programInput = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-shaker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Tree shaking for Svelte components",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dead-code-elimination",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"svelte": "^5.56.6"
|
|
65
65
|
},
|
|
66
66
|
"optionalDependencies": {
|
|
67
|
-
"svelte-shaker-engine-scan-native": "~0.
|
|
67
|
+
"svelte-shaker-engine-scan-native": "~0.3.0"
|
|
68
68
|
},
|
|
69
69
|
"engines": {
|
|
70
70
|
"node": ">=22.23.1"
|