svelte-shaker 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/svelte_shaker_engine.js +74 -0
- package/dist/svelte_shaker_engine_bg.wasm +0 -0
- package/dist/transform.js +60 -7
- package/dist/vite.d.ts +11 -13
- package/dist/vite.js +19 -16
- package/dist/wasm-engine.d.ts +24 -1
- package/dist/wasm-engine.js +55 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -110,11 +110,11 @@ shaker({
|
|
|
110
110
|
shaker({ include: ['src'], level: 1 }); // L2 off (L0/L1/L1.5 only)
|
|
111
111
|
shaker({ include: ['src'], monomorphize: { maxVariants: 16 } }); // raise the variant cap
|
|
112
112
|
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
shaker({ include: ['src'],
|
|
113
|
+
// Engine: the native Rust (WASM) engine runs the whole shake INCLUDING L2 (it
|
|
114
|
+
// calls back to JS only for the net-win gate's compiled-size proxy) and is
|
|
115
|
+
// differentially tested to be byte-identical to the JS engine, so it only changes
|
|
116
|
+
// speed. It is the default ('auto'); force it (or the JS engine) explicitly with:
|
|
117
|
+
shaker({ include: ['src'], engine: 'rust' }); // or engine: 'js'
|
|
118
118
|
|
|
119
119
|
// Opt into the faster rsvelte parser (~1.46x full build, ~2.2x parse).
|
|
120
120
|
// Requires the optional peer `@rsvelte/vite-plugin-svelte-native` (install it
|
|
@@ -73,9 +73,56 @@ function shake_program(input_json) {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
exports.shake_program = shake_program;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Whole-program shake WITH L2 monomorphization. `input` is the same shape as
|
|
79
|
+
* `shake_program`; `options_json` is `{enabled, maxVariants, minSavings}`;
|
|
80
|
+
* `own_size(source) -> number | null` is the per-module compiled-byte proxy the
|
|
81
|
+
* net-win gate uses (the JS side runs svelte/compiler, so decisions match the TS
|
|
82
|
+
* engine). Returns `{ files: {id: code}, variants: {specifier: code} }`.
|
|
83
|
+
* @param {string} input_json
|
|
84
|
+
* @param {string} options_json
|
|
85
|
+
* @param {Function} own_size
|
|
86
|
+
* @returns {string}
|
|
87
|
+
*/
|
|
88
|
+
function shake_program_with_mono(input_json, options_json, own_size) {
|
|
89
|
+
let deferred3_0;
|
|
90
|
+
let deferred3_1;
|
|
91
|
+
try {
|
|
92
|
+
const ptr0 = passStringToWasm0(input_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
93
|
+
const len0 = WASM_VECTOR_LEN;
|
|
94
|
+
const ptr1 = passStringToWasm0(options_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
95
|
+
const len1 = WASM_VECTOR_LEN;
|
|
96
|
+
const ret = wasm.shake_program_with_mono(ptr0, len0, ptr1, len1, own_size);
|
|
97
|
+
deferred3_0 = ret[0];
|
|
98
|
+
deferred3_1 = ret[1];
|
|
99
|
+
return getStringFromWasm0(ret[0], ret[1]);
|
|
100
|
+
} finally {
|
|
101
|
+
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
exports.shake_program_with_mono = shake_program_with_mono;
|
|
76
105
|
function __wbg_get_imports() {
|
|
77
106
|
const import0 = {
|
|
78
107
|
__proto__: null,
|
|
108
|
+
__wbg___wbindgen_number_get_9bb1761122181af2: function(arg0, arg1) {
|
|
109
|
+
const obj = arg1;
|
|
110
|
+
const ret = typeof(obj) === 'number' ? obj : undefined;
|
|
111
|
+
getDataViewMemory0().setFloat64(arg0 + 8 * 1, isLikeNone(ret) ? 0 : ret, true);
|
|
112
|
+
getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);
|
|
113
|
+
},
|
|
114
|
+
__wbg___wbindgen_throw_1506f2235d1bdba0: function(arg0, arg1) {
|
|
115
|
+
throw new Error(getStringFromWasm0(arg0, arg1));
|
|
116
|
+
},
|
|
117
|
+
__wbg_call_40e4174f169eaca7: function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
|
118
|
+
const ret = arg0.call(arg1, arg2, arg3);
|
|
119
|
+
return ret;
|
|
120
|
+
}, arguments); },
|
|
121
|
+
__wbindgen_cast_0000000000000001: function(arg0, arg1) {
|
|
122
|
+
// Cast intrinsic for `Ref(String) -> Externref`.
|
|
123
|
+
const ret = getStringFromWasm0(arg0, arg1);
|
|
124
|
+
return ret;
|
|
125
|
+
},
|
|
79
126
|
__wbindgen_init_externref_table: function() {
|
|
80
127
|
const table = wasm.__wbindgen_externrefs;
|
|
81
128
|
const offset = table.grow(4);
|
|
@@ -92,6 +139,20 @@ function __wbg_get_imports() {
|
|
|
92
139
|
};
|
|
93
140
|
}
|
|
94
141
|
|
|
142
|
+
function addToExternrefTable0(obj) {
|
|
143
|
+
const idx = wasm.__externref_table_alloc();
|
|
144
|
+
wasm.__wbindgen_externrefs.set(idx, obj);
|
|
145
|
+
return idx;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let cachedDataViewMemory0 = null;
|
|
149
|
+
function getDataViewMemory0() {
|
|
150
|
+
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
|
|
151
|
+
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
|
|
152
|
+
}
|
|
153
|
+
return cachedDataViewMemory0;
|
|
154
|
+
}
|
|
155
|
+
|
|
95
156
|
function getStringFromWasm0(ptr, len) {
|
|
96
157
|
return decodeText(ptr >>> 0, len);
|
|
97
158
|
}
|
|
@@ -104,6 +165,19 @@ function getUint8ArrayMemory0() {
|
|
|
104
165
|
return cachedUint8ArrayMemory0;
|
|
105
166
|
}
|
|
106
167
|
|
|
168
|
+
function handleError(f, args) {
|
|
169
|
+
try {
|
|
170
|
+
return f.apply(this, args);
|
|
171
|
+
} catch (e) {
|
|
172
|
+
const idx = addToExternrefTable0(e);
|
|
173
|
+
wasm.__wbindgen_exn_store(idx);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function isLikeNone(x) {
|
|
178
|
+
return x === undefined || x === null;
|
|
179
|
+
}
|
|
180
|
+
|
|
107
181
|
function passStringToWasm0(arg, malloc, realloc) {
|
|
108
182
|
if (realloc === undefined) {
|
|
109
183
|
const buf = cachedTextEncoder.encode(arg);
|
|
Binary file
|
package/dist/transform.js
CHANGED
|
@@ -274,12 +274,17 @@ outDead) {
|
|
|
274
274
|
* or gain a space.
|
|
275
275
|
*/
|
|
276
276
|
function foldIfBlocks(fragment, env, setEnv, code, s, dead) {
|
|
277
|
-
walk(fragment, { parent: null, preserve: hasPreserveWhitespaceOption(fragment) }, {
|
|
277
|
+
walk(fragment, { parent: null, preserve: hasPreserveWhitespaceOption(fragment), element: null }, {
|
|
278
278
|
_(node, { state, next }) {
|
|
279
279
|
if (node.type !== 'IfBlock') {
|
|
280
|
-
// Descend, recording this node as the children's parent
|
|
281
|
-
// opens a preserved-whitespace context
|
|
282
|
-
|
|
280
|
+
// Descend, recording this node as the children's parent, whether it
|
|
281
|
+
// opens a preserved-whitespace context, and the content-model element
|
|
282
|
+
// their seam would land in (for the `{" "}` validity check).
|
|
283
|
+
next({
|
|
284
|
+
parent: node,
|
|
285
|
+
preserve: state.preserve || isPreserveElement(node),
|
|
286
|
+
element: childParentElement(node, state.element),
|
|
287
|
+
});
|
|
283
288
|
return;
|
|
284
289
|
}
|
|
285
290
|
// `elseif` IfBlocks are the *continuation* of a chain we already own from
|
|
@@ -294,9 +299,12 @@ function foldIfBlocks(fragment, env, setEnv, code, s, dead) {
|
|
|
294
299
|
// node as its children's parent), so its `nodes` are the chain's siblings.
|
|
295
300
|
index: state.parent?.nodes?.indexOf(node) ?? -1,
|
|
296
301
|
preserve: state.preserve,
|
|
302
|
+
element: state.element,
|
|
297
303
|
});
|
|
304
|
+
// kept head: descend for nested blocks. The `{#if}` is transparent to the
|
|
305
|
+
// content model, so children stay in the same parent element.
|
|
298
306
|
if (decision.recurse)
|
|
299
|
-
next({ parent: node, preserve: state.preserve });
|
|
307
|
+
next({ parent: node, preserve: state.preserve, element: state.element });
|
|
300
308
|
// otherwise the subtree is gone or re-emitted verbatim — do not recurse.
|
|
301
309
|
},
|
|
302
310
|
});
|
|
@@ -366,10 +374,12 @@ function isFullRemoval(decision) {
|
|
|
366
374
|
* edge-trimmed, so it renders exactly one space wherever it lands, matching the
|
|
367
375
|
* original. Otherwise plain deletion already preserves space presence (only the
|
|
368
376
|
* run LENGTH can differ, which the SSR oracle normalizes). Never compensate
|
|
369
|
-
* under preserved whitespace — there plain deletion is byte-exact
|
|
377
|
+
* under preserved whitespace — there plain deletion is byte-exact — nor inside a
|
|
378
|
+
* text-free parent (`<tr>`, `<tbody>`, …), where Svelte rejects the `{" "}` text
|
|
379
|
+
* child outright and the whitespace rendered nothing to begin with.
|
|
370
380
|
*/
|
|
371
381
|
function removeChain(removed, span, code, s, dead, ctx) {
|
|
372
|
-
if (!ctx.preserve && ctx.parent?.nodes && ctx.index >= 0) {
|
|
382
|
+
if (!ctx.preserve && !isTextFreeParent(ctx.element) && ctx.parent?.nodes && ctx.index >= 0) {
|
|
373
383
|
const seam = analyzeSeam(ctx.parent.nodes, ctx.index, span, code, dead);
|
|
374
384
|
if (seam) {
|
|
375
385
|
s.overwrite(seam[0], seam[1], '{" "}');
|
|
@@ -431,6 +441,49 @@ function isRenderingSibling(node, code) {
|
|
|
431
441
|
function isPreserveElement(node) {
|
|
432
442
|
return node.type === 'RegularElement' && (node.name === 'pre' || node.name === 'textarea');
|
|
433
443
|
}
|
|
444
|
+
/**
|
|
445
|
+
* Parent elements whose HTML content model forbids a text child: Svelte's
|
|
446
|
+
* `is_tag_valid_with_parent('#text', …)` rejects a `#text`/`{" "}` here with
|
|
447
|
+
* `node_invalid_placement` — these are exactly its `disallowed_children` entries
|
|
448
|
+
* that carry an `only` list (html/head/frameset/#document can't appear as
|
|
449
|
+
* elements inside a component, so only the table parts remain). Svelte never
|
|
450
|
+
* renders inter-child whitespace inside them either, so a removed chain's seam
|
|
451
|
+
* needs plain deletion: emitting the `{" "}` compensation would produce a
|
|
452
|
+
* component that fails to compile. See {@link removeChain}.
|
|
453
|
+
*/
|
|
454
|
+
const TEXT_FREE_PARENTS = new Set(['table', 'thead', 'tbody', 'tfoot', 'tr', 'colgroup']);
|
|
455
|
+
/**
|
|
456
|
+
* Node types that reset the content-model parent to "unknown" (text allowed
|
|
457
|
+
* again), mirroring svelte's `parent_element: null` reset in the SvelteElement /
|
|
458
|
+
* SvelteFragment / SnippetBlock / Component visitors. A `{" "}` seam in any of
|
|
459
|
+
* these contexts is valid, so the seam compensation may proceed.
|
|
460
|
+
*/
|
|
461
|
+
const PARENT_ELEMENT_RESET = new Set([
|
|
462
|
+
'SvelteElement',
|
|
463
|
+
'SvelteFragment',
|
|
464
|
+
'SnippetBlock',
|
|
465
|
+
'Component',
|
|
466
|
+
'SvelteSelf',
|
|
467
|
+
'SvelteComponent',
|
|
468
|
+
]);
|
|
469
|
+
/**
|
|
470
|
+
* The content-model parent element a seam would land in for `node`'s children,
|
|
471
|
+
* given the element the walk is currently inside. Mirrors svelte's
|
|
472
|
+
* `parent_element` threading: a `RegularElement` becomes the parent, the reset
|
|
473
|
+
* node types clear it, and every other node (Fragment, blocks, …) is transparent
|
|
474
|
+
* and inherits. `null` means "text allowed" (root or a reset context).
|
|
475
|
+
*/
|
|
476
|
+
function childParentElement(node, current) {
|
|
477
|
+
if (node.type === 'RegularElement')
|
|
478
|
+
return node.name ?? null;
|
|
479
|
+
if (PARENT_ELEMENT_RESET.has(node.type))
|
|
480
|
+
return null;
|
|
481
|
+
return current;
|
|
482
|
+
}
|
|
483
|
+
/** True when an `{" "}` seam would be an invalid text child of `element`. */
|
|
484
|
+
function isTextFreeParent(element) {
|
|
485
|
+
return element !== null && TEXT_FREE_PARENTS.has(element);
|
|
486
|
+
}
|
|
434
487
|
/** Does the component opt into preserved whitespace via `<svelte:options>`? */
|
|
435
488
|
function hasPreserveWhitespaceOption(fragment) {
|
|
436
489
|
let preserve = false;
|
package/dist/vite.d.ts
CHANGED
|
@@ -14,8 +14,7 @@ export interface ShakerOptions {
|
|
|
14
14
|
* additionally enables L2 per-call-site monomorphization. **Default `2`** — L2
|
|
15
15
|
* is ON by default because it is bail-safe and never bloats (the measured
|
|
16
16
|
* net-win gate, docs §3 L2). Set `level: 1` (or `0`) to turn L2 OFF, e.g. to
|
|
17
|
-
* trade a little compression for faster builds
|
|
18
|
-
* engine} (which implements L0/L1/L1.5 only).
|
|
17
|
+
* trade a little compression for faster builds.
|
|
19
18
|
*/
|
|
20
19
|
level?: 0 | 1 | 2;
|
|
21
20
|
/**
|
|
@@ -28,17 +27,16 @@ export interface ShakerOptions {
|
|
|
28
27
|
*/
|
|
29
28
|
monomorphize?: boolean | Partial<Omit<MonomorphizeOptions, 'enabled'>>;
|
|
30
29
|
/**
|
|
31
|
-
* Which engine runs the
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* the JS engine, so the choice only affects speed, never what is shaken.
|
|
30
|
+
* Which engine runs the whole shake (analysis + transform, INCLUDING L2).
|
|
31
|
+
* Default `'auto'`. The native Rust (WASM) engine implements every level — for
|
|
32
|
+
* L2 it calls back into JS only for the per-module compiled-size proxy the
|
|
33
|
+
* net-win gate needs — so it is the default fast path:
|
|
34
|
+
* - `'auto'` — use the native Rust engine when it can be loaded; otherwise fall
|
|
35
|
+
* back to the JS engine.
|
|
36
|
+
* - `'rust'` — force the Rust engine; throws if the WASM module can't be loaded.
|
|
37
|
+
* - `'js'` — force the JS engine.
|
|
38
|
+
* Both engines are differentially tested to produce byte-identical output, so the
|
|
39
|
+
* choice only affects speed, never what is shaken.
|
|
42
40
|
*/
|
|
43
41
|
engine?: 'auto' | 'js' | 'rust';
|
|
44
42
|
/**
|
package/dist/vite.js
CHANGED
|
@@ -6,7 +6,7 @@ import { DevShaker } from './engine.js';
|
|
|
6
6
|
import { collectSvelteFiles, fsResolve } from './scan.js';
|
|
7
7
|
import { DEFAULT_MONO_OPTIONS } from './mono.js';
|
|
8
8
|
import { tryLoadRsvelteParser } from './rsvelte-parse.js';
|
|
9
|
-
import { svelteShakerWasm, tryLoadWasmEngine } from './wasm-engine.js';
|
|
9
|
+
import { svelteShakerWasm, svelteShakerWasmWithMono, tryLoadWasmEngine } from './wasm-engine.js';
|
|
10
10
|
/** kB with two decimals, the unit Vite itself uses in its build size report. */
|
|
11
11
|
function formatKB(bytes) {
|
|
12
12
|
return `${(bytes / 1024).toFixed(2)} kB`;
|
|
@@ -269,41 +269,44 @@ export function shaker(options = {}) {
|
|
|
269
269
|
return null;
|
|
270
270
|
return resolved.id.split('?')[0];
|
|
271
271
|
};
|
|
272
|
-
// Decide the engine.
|
|
273
|
-
//
|
|
274
|
-
//
|
|
272
|
+
// Decide the engine. The native Rust engine now implements every level
|
|
273
|
+
// INCLUDING L2 (it calls back to JS only for the compiled-size proxy), so it
|
|
274
|
+
// is the default: `'auto'` uses it whenever it can be loaded and falls back
|
|
275
|
+
// to JS otherwise, `'rust'` forces it (throwing if it can't load), `'js'`
|
|
276
|
+
// forces the JS engine. Both engines produce byte-identical output.
|
|
275
277
|
const engineChoice = options.engine ?? 'auto';
|
|
276
|
-
let effectiveMono = mono;
|
|
277
278
|
let wasm = null;
|
|
278
279
|
if (engineChoice === 'rust') {
|
|
279
|
-
if (mono.enabled) {
|
|
280
|
-
log('engine: "rust" runs L0/L1/L1.5 only — L2 monomorphization (JS-only) is skipped');
|
|
281
|
-
effectiveMono = DEFAULT_MONO_OPTIONS;
|
|
282
|
-
}
|
|
283
280
|
wasm = tryLoadWasmEngine();
|
|
284
281
|
if (!wasm)
|
|
285
282
|
throw new Error('[vite-plugin-svelte-shaker] engine: "rust" was requested but the WASM engine ' +
|
|
286
283
|
'could not be loaded. Remove the option (or use engine: "js") to use the JS engine.');
|
|
287
284
|
}
|
|
288
|
-
else if (engineChoice === 'auto'
|
|
289
|
-
// Auto: prefer the native engine on the L2-off path; fall back to JS silently.
|
|
285
|
+
else if (engineChoice === 'auto') {
|
|
290
286
|
wasm = tryLoadWasmEngine();
|
|
291
287
|
}
|
|
292
288
|
if (wasm) {
|
|
293
|
-
// Native Rust
|
|
294
|
-
|
|
295
|
-
|
|
289
|
+
// Native Rust engine — byte-identical to the JS engine, including L2.
|
|
290
|
+
if (mono.enabled) {
|
|
291
|
+
const result = await svelteShakerWasmWithMono(wasm, entries, resolve, read, mono, getParse());
|
|
292
|
+
shaken = result.files;
|
|
293
|
+
variantSources = result.variants;
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
shaken = await svelteShakerWasm(wasm, entries, resolve, read, getParse());
|
|
297
|
+
variantSources = new Map();
|
|
298
|
+
}
|
|
296
299
|
reportSizes(shaken, read, root, options.verbose === true, log);
|
|
297
300
|
return;
|
|
298
301
|
}
|
|
299
|
-
if (!
|
|
302
|
+
if (!mono.enabled) {
|
|
300
303
|
// JS engine, L2 off: byte-for-byte the L0/L1/L1.5 output.
|
|
301
304
|
shaken = await svelteShaker(entries, resolve, read, getParse());
|
|
302
305
|
variantSources = new Map();
|
|
303
306
|
reportSizes(shaken, read, root, options.verbose === true, log);
|
|
304
307
|
return;
|
|
305
308
|
}
|
|
306
|
-
const result = await svelteShakerWithMono(entries, resolve, read,
|
|
309
|
+
const result = await svelteShakerWithMono(entries, resolve, read, mono, variantSpecifier, getParse());
|
|
307
310
|
shaken = result.files;
|
|
308
311
|
variantSources = new Map();
|
|
309
312
|
for (const v of result.mono.variants.values())
|
package/dist/wasm-engine.d.ts
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
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
4
|
import type { ComponentId } from './ir.js';
|
|
4
|
-
/** The subset of the WASM exports the plugin uses (docs/RUST-MIGRATION.md M5). */
|
|
5
|
+
/** The subset of the WASM exports the plugin uses (docs/RUST-MIGRATION.md M5+). */
|
|
5
6
|
interface WasmEngine {
|
|
6
7
|
/** Whole-program L0/L1/L1.5 shake: input JSON in, `{id: shakenCode}` JSON out. */
|
|
7
8
|
shake_program: (inputJson: string) => string;
|
|
9
|
+
/**
|
|
10
|
+
* Whole-program shake WITH L2 monomorphization. `ownSize(id, source)` is the
|
|
11
|
+
* per-module compiled-byte proxy the net-win gate calls back into JS for (the
|
|
12
|
+
* Svelte compiler has no in-WASM equivalent); returns
|
|
13
|
+
* `{ files: {id: code}, variants: {specifier: code} }` JSON.
|
|
14
|
+
*/
|
|
15
|
+
shake_program_with_mono: (inputJson: string, optionsJson: string, ownSize: (id: string, source: string) => number | null) => string;
|
|
8
16
|
}
|
|
9
17
|
/**
|
|
10
18
|
* Load the native Rust (WASM) engine, or `null` if it can't be loaded (no built
|
|
@@ -30,4 +38,19 @@ export declare function tryLoadWasmEngine(): WasmEngine | null;
|
|
|
30
38
|
* the build.
|
|
31
39
|
*/
|
|
32
40
|
export declare function svelteShakerWasm(engine: WasmEngine, entries: ComponentId | ComponentId[], resolve: Resolve, readFile: ReadFile, parse?: Parse): Promise<Record<ComponentId, string>>;
|
|
41
|
+
/** The output of a Rust L2 shake: the wired owner files + the variant residuals
|
|
42
|
+
* keyed by their request specifier (what the Shell's `load` hook serves). */
|
|
43
|
+
export interface WasmMonoResult {
|
|
44
|
+
files: Record<ComponentId, string>;
|
|
45
|
+
variants: Map<string, string>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Whole-program shake WITH L2 monomorphization, run entirely in the native Rust
|
|
49
|
+
* engine — the counterpart of {@link svelteShakerWithMono}. The crawl/resolution
|
|
50
|
+
* stays in JS; the Rust engine does the analysis, the L2 graph/gate, and the
|
|
51
|
+
* call-site rewrite, calling back into JS only for {@link ownSize} (the Svelte
|
|
52
|
+
* compiler). Feeding it the same compiler the JS engine uses makes the result
|
|
53
|
+
* byte-identical (pinned by the differential `wasm-mono` test).
|
|
54
|
+
*/
|
|
55
|
+
export declare function svelteShakerWasmWithMono(engine: WasmEngine, entries: ComponentId | ComponentId[], resolve: Resolve, readFile: ReadFile, mono: MonomorphizeOptions, parse?: Parse): Promise<WasmMonoResult>;
|
|
33
56
|
export {};
|
package/dist/wasm-engine.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
+
import { compile } from 'svelte/compiler';
|
|
2
3
|
import { buildAnalyzeInput } from './analyze.js';
|
|
3
4
|
import { parseCached, parseSvelte } from './parse.js';
|
|
5
|
+
import {} from './mono.js';
|
|
4
6
|
// NODE-ONLY: loads the native Rust (WASM) engine and drives it from the Vite
|
|
5
7
|
// plugin. Imported only by `vite.ts` (a Node entry), never by the environment-free
|
|
6
8
|
// engine (`index.ts`/`analyze.ts`), so the browser playground build stays clean.
|
|
@@ -19,8 +21,12 @@ export function tryLoadWasmEngine() {
|
|
|
19
21
|
try {
|
|
20
22
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
21
23
|
const mod = require(spec);
|
|
22
|
-
if (typeof mod.shake_program === 'function'
|
|
23
|
-
|
|
24
|
+
if (typeof mod.shake_program === 'function' &&
|
|
25
|
+
typeof mod.shake_program_with_mono === 'function')
|
|
26
|
+
return {
|
|
27
|
+
shake_program: mod.shake_program.bind(mod),
|
|
28
|
+
shake_program_with_mono: mod.shake_program_with_mono.bind(mod),
|
|
29
|
+
};
|
|
24
30
|
}
|
|
25
31
|
catch {
|
|
26
32
|
// Try the next location.
|
|
@@ -28,6 +34,16 @@ export function tryLoadWasmEngine() {
|
|
|
28
34
|
}
|
|
29
35
|
return null;
|
|
30
36
|
}
|
|
37
|
+
/** The compiled-byte size proxy the L2 net-win gate uses — the same call
|
|
38
|
+
* `mono.ts` makes, so the Rust gate decides byte-for-byte like the JS engine. */
|
|
39
|
+
function ownSize(id, source) {
|
|
40
|
+
try {
|
|
41
|
+
return compile(source, { generate: 'client', dev: false, filename: id }).js.code.length;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
31
47
|
/**
|
|
32
48
|
* Whole-program shake via the native Rust engine — the L0/L1/L1.5 counterpart of
|
|
33
49
|
* {@link svelteShaker} (L2 lives only in the JS engine). The crawl/resolution
|
|
@@ -54,7 +70,43 @@ export async function svelteShakerWasm(engine, entries, resolve, readFile, parse
|
|
|
54
70
|
entries: input.entries,
|
|
55
71
|
};
|
|
56
72
|
const out = JSON.parse(engine.shake_program(JSON.stringify(programInput)));
|
|
57
|
-
|
|
73
|
+
revertUnparseable(out, input.files);
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Whole-program shake WITH L2 monomorphization, run entirely in the native Rust
|
|
78
|
+
* engine — the counterpart of {@link svelteShakerWithMono}. The crawl/resolution
|
|
79
|
+
* stays in JS; the Rust engine does the analysis, the L2 graph/gate, and the
|
|
80
|
+
* call-site rewrite, calling back into JS only for {@link ownSize} (the Svelte
|
|
81
|
+
* compiler). Feeding it the same compiler the JS engine uses makes the result
|
|
82
|
+
* byte-identical (pinned by the differential `wasm-mono` test).
|
|
83
|
+
*/
|
|
84
|
+
export async function svelteShakerWasmWithMono(engine, entries, resolve, readFile, mono, parse) {
|
|
85
|
+
const cache = new Map();
|
|
86
|
+
const input = await buildAnalyzeInput(entries, resolve, readFile, cache, parse);
|
|
87
|
+
const programInput = {
|
|
88
|
+
files: input.files.map((f) => ({
|
|
89
|
+
id: f.id,
|
|
90
|
+
ast: parseCached(f.id, f.code, cache, parse),
|
|
91
|
+
code: f.code,
|
|
92
|
+
})),
|
|
93
|
+
edges: input.edges,
|
|
94
|
+
entries: input.entries,
|
|
95
|
+
};
|
|
96
|
+
const options = JSON.stringify({
|
|
97
|
+
enabled: mono.enabled,
|
|
98
|
+
maxVariants: mono.maxVariants,
|
|
99
|
+
minSavings: mono.minSavings,
|
|
100
|
+
});
|
|
101
|
+
const result = JSON.parse(engine.shake_program_with_mono(JSON.stringify(programInput), options, ownSize));
|
|
102
|
+
revertUnparseable(result.files, input.files);
|
|
103
|
+
return { files: result.files, variants: new Map(Object.entries(result.variants)) };
|
|
104
|
+
}
|
|
105
|
+
/** Self-check: revert any emitted file that no longer parses to its original — a
|
|
106
|
+
* sound "did not shake this component", mirroring the JS engine's last line of
|
|
107
|
+
* defense ({@link svelteShaker}'s `revertUnparseable`). */
|
|
108
|
+
function revertUnparseable(out, files) {
|
|
109
|
+
for (const file of files) {
|
|
58
110
|
const code = out[file.id];
|
|
59
111
|
if (code === undefined || code === file.code)
|
|
60
112
|
continue;
|
|
@@ -65,5 +117,4 @@ export async function svelteShakerWasm(engine, entries, resolve, readFile, parse
|
|
|
65
117
|
out[file.id] = file.code;
|
|
66
118
|
}
|
|
67
119
|
}
|
|
68
|
-
return out;
|
|
69
120
|
}
|