svelte-shaker 0.14.0 → 0.15.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/README.md +29 -0
- package/dist/analyze.d.ts +7 -4
- package/dist/analyze.js +57 -20
- package/dist/dev-only.d.ts +23 -0
- package/dist/dev-only.js +43 -0
- package/dist/engine.d.ts +1 -1
- package/dist/engine.js +1 -1
- package/dist/escape-scan.d.ts +8 -0
- package/dist/escape-scan.js +14 -9
- package/dist/ir.d.ts +3 -2
- package/dist/rsvelte-parse.d.ts +3 -3
- package/dist/rsvelte-parse.js +9 -4
- package/dist/scan.d.ts +12 -1
- package/dist/scan.js +23 -5
- package/dist/unread.js +2 -1
- package/dist/vite.d.ts +30 -0
- package/dist/vite.js +20 -4
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -92,6 +92,9 @@ shaker({
|
|
|
92
92
|
entries: ['src'], // dirs (relative to root) the crawl starts from; they must
|
|
93
93
|
// hold every .svelte call site in the app. Not a glob, not a filter.
|
|
94
94
|
preserve: [], // components whose props must never be folded (see below)
|
|
95
|
+
devOnly: [...], // glob patterns of files that never ship (tests, stories); they
|
|
96
|
+
// stop counting as call sites. Defaults to tests/mocks/stories; replaces, spread
|
|
97
|
+
// to extend.
|
|
95
98
|
monomorphize: true, // default on; `false` disables it for faster builds,
|
|
96
99
|
// or { maxVariants: 16, minSavings: 0.05 } to tune
|
|
97
100
|
verbose: false, // true = per-file size breakdown after the build
|
|
@@ -171,6 +174,32 @@ options that do exist. A typo would otherwise be ignored — and a misspelled
|
|
|
171
174
|
The build **warns** (with the file path) about any module the scan couldn't
|
|
172
175
|
parse — so a mounted component isn't silently left unprotected — and about
|
|
173
176
|
`preserve` entries that matched no component.
|
|
177
|
+
- **`devOnly`** — glob patterns (matched with
|
|
178
|
+
[`picomatch`](https://github.com/micromatch/picomatch) against each file's path
|
|
179
|
+
relative to the Vite root) naming files that **never ship in the production
|
|
180
|
+
bundle** — colocated tests, mocks, Storybook stories. A matched file **stops
|
|
181
|
+
counting as a component consumer** in **both directory scans** (the `.svelte` seed
|
|
182
|
+
scan and the non-`.svelte` escape scan), so a `Foo.test.svelte` or a
|
|
183
|
+
`Button.test.ts` can no longer pessimize the shake. It defaults to:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
// the built-in default (import DEFAULT_DEV_ONLY to extend it)
|
|
187
|
+
devOnly: ['**/*.test.*', '**/*.spec.*', '**/__tests__/**', '**/__mocks__/**', '**/*.stories.*'];
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Passing `devOnly` **replaces** this list (predictable semantics) — spread it to
|
|
191
|
+
extend: `devOnly: [...DEFAULT_DEV_ONLY, 'src/dev/**']` (import `DEFAULT_DEV_ONLY`
|
|
192
|
+
from `svelte-shaker/vite`). Pass `devOnly: []` to count every file (the pre-`devOnly`
|
|
193
|
+
behavior).
|
|
194
|
+
|
|
195
|
+
**List only files that never ship.** A matched file isn't excluded from the shake —
|
|
196
|
+
one the app actually imports is still crawled and shaken; it just stops _counting_
|
|
197
|
+
as a call site. So a file that really ships but matches a pattern (a `+page.svelte`
|
|
198
|
+
under a route dir named `__tests__`) has its distinct prop values stop blocking
|
|
199
|
+
folds, the same failure mode as leaving it out of `entries` — which is why the
|
|
200
|
+
defaults are narrow. See
|
|
201
|
+
[`docs/ARCHITECTURE.md` §8.1.1](https://github.com/baseballyama/svelte-shaker/blob/main/docs/ARCHITECTURE.md)
|
|
202
|
+
for the full argument.
|
|
174
203
|
|
|
175
204
|
## What it removes
|
|
176
205
|
|
package/dist/analyze.d.ts
CHANGED
|
@@ -18,8 +18,10 @@ export type ReadFileSync = (id: ComponentId) => string;
|
|
|
18
18
|
* never be seen and is safe to drop;
|
|
19
19
|
* - `{ kind: 'all' }` — anything we cannot pin down: a `...rest` (captures
|
|
20
20
|
* undeclared inputs), an Identifier/Array binding (`let p = $props()`),
|
|
21
|
-
* more than one `$props()` call,
|
|
22
|
-
* declarator
|
|
21
|
+
* more than one `$props()` call, `$props()` outside a `let <pat> = …`
|
|
22
|
+
* declarator, or a component that observes slotted content outside `$props()`
|
|
23
|
+
* — a legacy `<slot>` element or a `$$slots` read (both legal in runes mode).
|
|
24
|
+
* Then any input might be observed, so nothing is dropped.
|
|
23
25
|
*/
|
|
24
26
|
export type ReachableInputs = {
|
|
25
27
|
kind: 'all';
|
|
@@ -275,8 +277,9 @@ export interface UnpassedProp {
|
|
|
275
277
|
* spread that could set it (`readCallSite` already folds `bind:`, known
|
|
276
278
|
* spreads, and `children`/snippet body into `explicit`/`hadSpread`);
|
|
277
279
|
* - a component in `input.escaped` — one the Shell knows has a consumer OUTSIDE
|
|
278
|
-
* the `.svelte` graph (a
|
|
279
|
-
* §4.2) — is skipped, because that consumer may pass a prop
|
|
280
|
+
* the `.svelte` graph (a call site in a non-`.svelte` module, or a user
|
|
281
|
+
* `preserve`, docs §4.2) — is skipped, because that consumer may pass a prop
|
|
282
|
+
* the crawl cannot see.
|
|
280
283
|
*
|
|
281
284
|
* Missing a `.svelte` EDGE (e.g. an unfollowed barrel) only DROPS call sites, so it
|
|
282
285
|
* can only make this UNDER-report (the component looks unused and is skipped). The
|
package/dist/analyze.js
CHANGED
|
@@ -29,22 +29,22 @@ function fixpointIterationBound(componentCount) {
|
|
|
29
29
|
/** Bail reason stamped on a component leaked as a value (docs §4.1 escape). */
|
|
30
30
|
const ESCAPE_REASON = 'escapes as value (e.g. <svelte:component this={X}>)';
|
|
31
31
|
/** Bail reason stamped on a component with a consumer OUTSIDE the analyzed
|
|
32
|
-
* `.svelte` graph — a
|
|
33
|
-
* user-declared `preserve` (docs §4.2, {@link AnalyzeInput.escaped}).
|
|
34
|
-
* byte-identical to the Rust engine's constant so the two agree. */
|
|
35
|
-
const
|
|
32
|
+
* `.svelte` graph — a call site in a non-`.svelte` module the crawl cannot
|
|
33
|
+
* parse, or a user-declared `preserve` (docs §4.2, {@link AnalyzeInput.escaped}).
|
|
34
|
+
* Kept byte-identical to the Rust engine's constant so the two agree. */
|
|
35
|
+
const MODULE_ESCAPE_REASON = 'has a consumer outside the analyzed .svelte graph';
|
|
36
36
|
/**
|
|
37
|
-
* Stamp {@link
|
|
37
|
+
* Stamp {@link MODULE_ESCAPE_REASON} on every model in `escaped` that exists in
|
|
38
38
|
* the program — the single injection point both the whole-program shake and
|
|
39
39
|
* {@link findNeverPassedProps} share (docs §4.2). Ids not in the program are
|
|
40
|
-
* ignored (a stale `preserve` entry or a scanned
|
|
41
|
-
*
|
|
40
|
+
* ignored (a stale `preserve` entry or a scanned import to a component outside
|
|
41
|
+
* the crawl is simply a no-op, never an error).
|
|
42
42
|
*/
|
|
43
|
-
function
|
|
43
|
+
function stampModuleEscapes(models, escaped) {
|
|
44
44
|
for (const id of escaped ?? []) {
|
|
45
45
|
const model = models.get(id);
|
|
46
|
-
if (model && !model.bailReasons.includes(
|
|
47
|
-
model.bailReasons.push(
|
|
46
|
+
if (model && !model.bailReasons.includes(MODULE_ESCAPE_REASON))
|
|
47
|
+
model.bailReasons.push(MODULE_ESCAPE_REASON);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
@@ -86,9 +86,10 @@ export function analyzeInput(input, parseCache) {
|
|
|
86
86
|
if (model && !model.bailReasons.includes(ESCAPE_REASON))
|
|
87
87
|
model.bailReasons.push(ESCAPE_REASON);
|
|
88
88
|
}
|
|
89
|
-
// Components with consumers outside the `.svelte` graph (a
|
|
90
|
-
// or a user `preserve`, docs §4.2) join the same
|
|
91
|
-
|
|
89
|
+
// Components with consumers outside the `.svelte` graph (a call site in a
|
|
90
|
+
// non-`.svelte` module or a user `preserve`, docs §4.2) join the same
|
|
91
|
+
// whole-component escape bail.
|
|
92
|
+
stampModuleEscapes(models, input.escaped);
|
|
92
93
|
return { models, plans: planFixpoint(models) };
|
|
93
94
|
}
|
|
94
95
|
/**
|
|
@@ -499,8 +500,9 @@ export function deadSpansForPlans(models, plans) {
|
|
|
499
500
|
* spread that could set it (`readCallSite` already folds `bind:`, known
|
|
500
501
|
* spreads, and `children`/snippet body into `explicit`/`hadSpread`);
|
|
501
502
|
* - a component in `input.escaped` — one the Shell knows has a consumer OUTSIDE
|
|
502
|
-
* the `.svelte` graph (a
|
|
503
|
-
* §4.2) — is skipped, because that consumer may pass a prop
|
|
503
|
+
* the `.svelte` graph (a call site in a non-`.svelte` module, or a user
|
|
504
|
+
* `preserve`, docs §4.2) — is skipped, because that consumer may pass a prop
|
|
505
|
+
* the crawl cannot see.
|
|
504
506
|
*
|
|
505
507
|
* Missing a `.svelte` EDGE (e.g. an unfollowed barrel) only DROPS call sites, so it
|
|
506
508
|
* can only make this UNDER-report (the component looks unused and is skipped). The
|
|
@@ -521,9 +523,10 @@ export function findNeverPassedProps(input) {
|
|
|
521
523
|
if (model && !model.bailReasons.includes(ESCAPE_REASON))
|
|
522
524
|
model.bailReasons.push(ESCAPE_REASON);
|
|
523
525
|
}
|
|
524
|
-
//
|
|
525
|
-
// they pass is never mis-reported as
|
|
526
|
-
|
|
526
|
+
// Consumers outside the `.svelte` graph (non-`.svelte` module call sites or
|
|
527
|
+
// `preserve`) escape too, so a prop they pass is never mis-reported as
|
|
528
|
+
// never-passed (docs §4.2).
|
|
529
|
+
stampModuleEscapes(models, input.escaped);
|
|
527
530
|
// Every textual call site counts (no cascade dead-span filtering): a prop passed
|
|
528
531
|
// only at a folded-away site is still author-written, so we do not flag it.
|
|
529
532
|
const usage = buildUsage(models, new Map());
|
|
@@ -666,7 +669,7 @@ function buildModelFromInput(file, edges, parseCache) {
|
|
|
666
669
|
}
|
|
667
670
|
}
|
|
668
671
|
}
|
|
669
|
-
const reachableInputs = computeReachableInputs(instance, props, hasRestProp, propsPattern);
|
|
672
|
+
const reachableInputs = computeReachableInputs(instance, props, hasRestProp, propsPattern, usesLegacySlotInputs(ast));
|
|
670
673
|
const childCalls = collectChildCalls(ast, imports);
|
|
671
674
|
const { shadowedNames, debugNames, writtenNames } = collectTemplateBindings(ast, instance, propsDeclaration);
|
|
672
675
|
const unreadDeclaredProps = computeUnreadDeclaredProps(ast, instance, props, propsPattern, shadowedNames, debugNames, writtenNames);
|
|
@@ -1832,7 +1835,15 @@ function parseModuleBody(code, id) {
|
|
|
1832
1835
|
* `$props.id()` (a member call) is NOT a `$props()` call — the props object never
|
|
1833
1836
|
* leaks through it — so it does not count and does not affect the result.
|
|
1834
1837
|
*/
|
|
1835
|
-
function computeReachableInputs(instance, props, hasRestProp, propsPattern) {
|
|
1838
|
+
function computeReachableInputs(instance, props, hasRestProp, propsPattern, usesSlotInputs) {
|
|
1839
|
+
// A legacy `<slot>` (or a bare `$$slots` read) observes slotted content —
|
|
1840
|
+
// inputs that arrive OUTSIDE `$props()` (in Svelte 5 terms, the synthetic
|
|
1841
|
+
// `children` input and named-slot / `let:` inputs a call site supplies as body
|
|
1842
|
+
// content). The `$props()` shape cannot model them, so the reverse pass must
|
|
1843
|
+
// treat every input as observable, or it would delete the slot-carrying body at
|
|
1844
|
+
// each call site. This holds whether or not an instance script exists.
|
|
1845
|
+
if (usesSlotInputs)
|
|
1846
|
+
return { kind: 'all' };
|
|
1836
1847
|
// No instance script -> no `$props()` -> the component reads no input at all.
|
|
1837
1848
|
if (!instance)
|
|
1838
1849
|
return { kind: 'names', names: new Set() };
|
|
@@ -1853,6 +1864,32 @@ function computeReachableInputs(instance, props, hasRestProp, propsPattern) {
|
|
|
1853
1864
|
return { kind: 'all' };
|
|
1854
1865
|
return { kind: 'names', names: new Set(props.map((p) => p.name)) };
|
|
1855
1866
|
}
|
|
1867
|
+
/** True when the component observes slotted content outside `$props()`: a legacy
|
|
1868
|
+
* `<slot>` element, or a read of the `$$slots` identifier (legal in runes mode,
|
|
1869
|
+
* unlike `$$props`/`$$restProps`). Either signal means {@link
|
|
1870
|
+
* computeReachableInputs} cannot model the inputs and must fall back to ALL.
|
|
1871
|
+
* `$$slots` can appear in the instance script OR a template expression
|
|
1872
|
+
* (`{#if $$slots.default}`), so both trees are scanned; its `$$` prefix cannot be
|
|
1873
|
+
* a user binding, so no shadowing check is needed. */
|
|
1874
|
+
function usesLegacySlotInputs(ast) {
|
|
1875
|
+
return (nodeSignalsSlotInputs(ast.fragment) || (!!ast.instance && nodeSignalsSlotInputs(ast.instance)));
|
|
1876
|
+
}
|
|
1877
|
+
function nodeSignalsSlotInputs(root) {
|
|
1878
|
+
let found = false;
|
|
1879
|
+
walk(root, null, {
|
|
1880
|
+
SlotElement(_node, { stop }) {
|
|
1881
|
+
found = true;
|
|
1882
|
+
stop();
|
|
1883
|
+
},
|
|
1884
|
+
Identifier(node, { stop }) {
|
|
1885
|
+
if (node.name === '$$slots') {
|
|
1886
|
+
found = true;
|
|
1887
|
+
stop();
|
|
1888
|
+
}
|
|
1889
|
+
},
|
|
1890
|
+
});
|
|
1891
|
+
return found;
|
|
1892
|
+
}
|
|
1856
1893
|
/** True when a `$props()` ObjectPattern binds a prop whose external name is not a
|
|
1857
1894
|
* plain identifier (a string-literal or computed key), so {@link declared_props}
|
|
1858
1895
|
* did not capture it. */
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ComponentId } from './ir.js';
|
|
2
|
+
/**
|
|
3
|
+
* Files treated as DEV-ONLY by default: colocated tests, mocks, and Storybook
|
|
4
|
+
* stories — files that never ship, so their call sites must not count toward the
|
|
5
|
+
* shake. Discounting them is sound precisely because they never reach production;
|
|
6
|
+
* see docs/ARCHITECTURE.md §8.1.1 for the full argument (why a glob is safe here but
|
|
7
|
+
* not for narrowing app coverage, and the shipped-file-matching failure mode).
|
|
8
|
+
* Passing `devOnly` REPLACES this list; `devOnly: []` counts every file.
|
|
9
|
+
*/
|
|
10
|
+
export declare const DEFAULT_DEV_ONLY: readonly string[];
|
|
11
|
+
/** Predicate over an ABSOLUTE path: `true` when the file is dev-only (discounted by a scan). */
|
|
12
|
+
export type DevOnlyFilter = (file: ComponentId) => boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Compile dev-only `patterns` into a {@link DevOnlyFilter}. Each candidate path is
|
|
15
|
+
* matched (with `picomatch`) as its `base`-relative, posix-normalized form, so the
|
|
16
|
+
* result is OS-independent (backslashes on Windows are folded to `/`). `base` is
|
|
17
|
+
* the Vite root for the plugin and the scanned dir for a standalone `svelte-shaker/node`
|
|
18
|
+
* caller. Compile ONCE and reuse across the whole walk — never per file.
|
|
19
|
+
*
|
|
20
|
+
* Defaults to {@link DEFAULT_DEV_ONLY}; an empty `patterns` array matches nothing, so
|
|
21
|
+
* `devOnly: []` counts every file (the pre-`devOnly` behavior).
|
|
22
|
+
*/
|
|
23
|
+
export declare function compileDevOnly(base: string, patterns?: readonly string[]): DevOnlyFilter;
|
package/dist/dev-only.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// ----------------------------------------------------------------------
|
|
2
|
+
// Shell-side glob for the two directory scans (docs/ARCHITECTURE.md §4.2, §8.1.1):
|
|
3
|
+
// which files are DEV-ONLY — they never ship in the production bundle, so their
|
|
4
|
+
// call sites must not count toward the shake. Kept OUT of the env-free engine
|
|
5
|
+
// core (docs §5): it depends on `picomatch` and `node:path`. Both scans — the
|
|
6
|
+
// `.svelte` seed scan (`collectSvelteFiles`) and the non-`.svelte` escape scan
|
|
7
|
+
// (`collectNonSvelteModules`) — take the SAME compiled predicate, so a dev-only
|
|
8
|
+
// file is discounted by both.
|
|
9
|
+
// ----------------------------------------------------------------------
|
|
10
|
+
import picomatch from 'picomatch';
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
/**
|
|
13
|
+
* Files treated as DEV-ONLY by default: colocated tests, mocks, and Storybook
|
|
14
|
+
* stories — files that never ship, so their call sites must not count toward the
|
|
15
|
+
* shake. Discounting them is sound precisely because they never reach production;
|
|
16
|
+
* see docs/ARCHITECTURE.md §8.1.1 for the full argument (why a glob is safe here but
|
|
17
|
+
* not for narrowing app coverage, and the shipped-file-matching failure mode).
|
|
18
|
+
* Passing `devOnly` REPLACES this list; `devOnly: []` counts every file.
|
|
19
|
+
*/
|
|
20
|
+
export const DEFAULT_DEV_ONLY = [
|
|
21
|
+
'**/*.test.*',
|
|
22
|
+
'**/*.spec.*',
|
|
23
|
+
'**/__tests__/**',
|
|
24
|
+
'**/__mocks__/**',
|
|
25
|
+
'**/*.stories.*',
|
|
26
|
+
];
|
|
27
|
+
const NONE_DEV_ONLY = () => false;
|
|
28
|
+
/**
|
|
29
|
+
* Compile dev-only `patterns` into a {@link DevOnlyFilter}. Each candidate path is
|
|
30
|
+
* matched (with `picomatch`) as its `base`-relative, posix-normalized form, so the
|
|
31
|
+
* result is OS-independent (backslashes on Windows are folded to `/`). `base` is
|
|
32
|
+
* the Vite root for the plugin and the scanned dir for a standalone `svelte-shaker/node`
|
|
33
|
+
* caller. Compile ONCE and reuse across the whole walk — never per file.
|
|
34
|
+
*
|
|
35
|
+
* Defaults to {@link DEFAULT_DEV_ONLY}; an empty `patterns` array matches nothing, so
|
|
36
|
+
* `devOnly: []` counts every file (the pre-`devOnly` behavior).
|
|
37
|
+
*/
|
|
38
|
+
export function compileDevOnly(base, patterns = DEFAULT_DEV_ONLY) {
|
|
39
|
+
if (patterns.length === 0)
|
|
40
|
+
return NONE_DEV_ONLY;
|
|
41
|
+
const isMatch = picomatch([...patterns]);
|
|
42
|
+
return (file) => isMatch(path.relative(base, file).split(path.sep).join('/'));
|
|
43
|
+
}
|
package/dist/engine.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ export declare class DevShaker {
|
|
|
53
53
|
/** Current slimmed output per `.svelte` id (the live shake result). */
|
|
54
54
|
private output;
|
|
55
55
|
constructor(files: ComponentId | ComponentId[], resolve: Resolve, readFile: ReadFile, mode?: DevMode, parse?: Parse, escaped?: ComponentId[]);
|
|
56
|
-
/** Replace the
|
|
56
|
+
/** Replace the module-escape set (docs §4.2). The Shell calls this before
|
|
57
57
|
* {@link update} when a non-`.svelte` module changed the set of components
|
|
58
58
|
* reached from `.ts`/`.js`, so the next shake bails them. */
|
|
59
59
|
setEscaped(escaped: ComponentId[]): void;
|
package/dist/engine.js
CHANGED
|
@@ -38,7 +38,7 @@ export class DevShaker {
|
|
|
38
38
|
this.parse = parse;
|
|
39
39
|
this.escaped = escaped;
|
|
40
40
|
}
|
|
41
|
-
/** Replace the
|
|
41
|
+
/** Replace the module-escape set (docs §4.2). The Shell calls this before
|
|
42
42
|
* {@link update} when a non-`.svelte` module changed the set of components
|
|
43
43
|
* reached from `.ts`/`.js`, so the next shake bails them. */
|
|
44
44
|
setEscaped(escaped) {
|
package/dist/escape-scan.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ComponentId } from './ir.js';
|
|
2
2
|
import type { Resolve, ReadFile } from './analyze.js';
|
|
3
|
+
import { type DevOnlyFilter } from './dev-only.js';
|
|
3
4
|
/**
|
|
4
5
|
* Is `file` a module the escape scan reads — a non-`.svelte` JS/TS source
|
|
5
6
|
* ({@link NON_SVELTE_MODULE_EXTS}), excluding `.d.ts` declaration files (types-only,
|
|
@@ -45,6 +46,13 @@ export declare function computeEscapedComponents(opts: {
|
|
|
45
46
|
preserve?: string[] | undefined;
|
|
46
47
|
/** The crawled `.svelte` component ids, for matching `preserve` prefixes. */
|
|
47
48
|
components: Iterable<ComponentId>;
|
|
49
|
+
/**
|
|
50
|
+
* Dev-only files to discount in the escape scan (docs §8.1.1) — the SAME predicate
|
|
51
|
+
* the seed scan (`collectSvelteFiles`) applies, so a `Button.test.ts` neither seeds
|
|
52
|
+
* a component nor escapes one. Omitted, it defaults to {@link DEFAULT_DEV_ONLY}
|
|
53
|
+
* matched relative to `root`. Pass `compileDevOnly(root, [])` to scan everything.
|
|
54
|
+
*/
|
|
55
|
+
devOnly?: DevOnlyFilter | undefined;
|
|
48
56
|
resolve: Resolve;
|
|
49
57
|
readFile: ReadFile;
|
|
50
58
|
}): Promise<EscapeScanResult>;
|
package/dist/escape-scan.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import * as fs from 'node:fs';
|
|
9
9
|
import * as path from 'node:path';
|
|
10
10
|
import { parseSvelte, walk } from './parse.js';
|
|
11
|
+
import { compileDevOnly } from './dev-only.js';
|
|
11
12
|
/**
|
|
12
13
|
* The non-`.svelte` module extensions we scan for `.svelte` call sites (docs
|
|
13
14
|
* §4.2). A component imported by any of these has a consumer the `.svelte`-only
|
|
@@ -31,27 +32,27 @@ export function isScannableModule(file) {
|
|
|
31
32
|
* Recursively collect every non-`.svelte` module under `dir` (skipping
|
|
32
33
|
* `node_modules` and dot-directories, mirroring `collectSvelteFiles`). Same
|
|
33
34
|
* include scope as the seed scan — `.ts` inside `node_modules` is deliberately NOT
|
|
34
|
-
* scanned (docs §4.2).
|
|
35
|
+
* scanned (docs §4.2). `devOnly` drops modules that never ship (a `Button.test.ts`)
|
|
36
|
+
* so a colocated test does not mark the component it imports escaped (docs §8.1.1);
|
|
37
|
+
* it is the SAME predicate the seed scan uses, so both scans discount the same files.
|
|
35
38
|
*/
|
|
36
|
-
function collectNonSvelteModules(dir) {
|
|
37
|
-
const out = [];
|
|
39
|
+
function collectNonSvelteModules(dir, devOnly, out) {
|
|
38
40
|
let entries;
|
|
39
41
|
try {
|
|
40
42
|
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
41
43
|
}
|
|
42
44
|
catch {
|
|
43
|
-
return
|
|
45
|
+
return;
|
|
44
46
|
}
|
|
45
47
|
for (const entry of entries) {
|
|
46
48
|
if (entry.name === 'node_modules' || entry.name.startsWith('.'))
|
|
47
49
|
continue;
|
|
48
50
|
const full = path.join(dir, entry.name);
|
|
49
51
|
if (entry.isDirectory())
|
|
50
|
-
|
|
51
|
-
else if (entry.isFile() && isScannableModule(entry.name))
|
|
52
|
+
collectNonSvelteModules(full, devOnly, out);
|
|
53
|
+
else if (entry.isFile() && isScannableModule(entry.name) && !devOnly(full))
|
|
52
54
|
out.push(full);
|
|
53
55
|
}
|
|
54
|
-
return out;
|
|
55
56
|
}
|
|
56
57
|
/**
|
|
57
58
|
* Every module specifier a JS/TS module statically references: `import … from`,
|
|
@@ -115,7 +116,7 @@ function moduleImportSpecifiers(code, id) {
|
|
|
115
116
|
* A module that cannot be read or parsed is collected into `unscannable` (NOT
|
|
116
117
|
* silently dropped): a call site inside it is invisible, so the caller must warn.
|
|
117
118
|
*/
|
|
118
|
-
async function
|
|
119
|
+
async function collectModuleEscapes(modules, resolve, readFile) {
|
|
119
120
|
const escaped = new Set();
|
|
120
121
|
const unscannable = new Set();
|
|
121
122
|
for (const id of modules) {
|
|
@@ -181,7 +182,11 @@ export function matchPreserve(preserve, root, components) {
|
|
|
181
182
|
* `svelte-shaker/node`).
|
|
182
183
|
*/
|
|
183
184
|
export async function computeEscapedComponents(opts) {
|
|
184
|
-
const
|
|
185
|
+
const devOnly = opts.devOnly ?? compileDevOnly(opts.root);
|
|
186
|
+
const modules = [];
|
|
187
|
+
for (const dir of opts.entryDirs)
|
|
188
|
+
collectNonSvelteModules(dir, devOnly, modules);
|
|
189
|
+
const { escaped, unscannable } = await collectModuleEscapes(modules, opts.resolve, opts.readFile);
|
|
185
190
|
const { matched, unmatched } = partitionPreserve(opts.preserve, opts.root, opts.components);
|
|
186
191
|
for (const id of matched)
|
|
187
192
|
escaped.add(id);
|
package/dist/ir.d.ts
CHANGED
|
@@ -43,8 +43,9 @@ export interface AnalyzeInput {
|
|
|
43
43
|
* this set (its FS scan cannot parse `.ts` call sites); the engine unions it
|
|
44
44
|
* into the same whole-component escape bail auto-detected escapes use, so these
|
|
45
45
|
* components are never folded and never reported as never-passed — while their
|
|
46
|
-
* OWN call sites still count toward their children. Omitted/`[]` means
|
|
47
|
-
*
|
|
46
|
+
* OWN call sites still count toward their children. Omitted/`[]` means every
|
|
47
|
+
* consumer is inside the crawled `.svelte` graph, keeping the output
|
|
48
|
+
* byte-for-byte unchanged.
|
|
48
49
|
*/
|
|
49
50
|
escaped?: ComponentId[];
|
|
50
51
|
}
|
package/dist/rsvelte-parse.d.ts
CHANGED
|
@@ -6,9 +6,9 @@ import type { Parse } from './parse.js';
|
|
|
6
6
|
* rsvelte is the default parser, the caller THROWS on `null` rather than silently
|
|
7
7
|
* using svelte/compiler; `parser: 'svelte'` is the explicit opt-out.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* rsvelte's AST positions match svelte/compiler's — UTF-16 code-unit offsets — so
|
|
10
|
+
* the AST feeds the engine directly (`@rsvelte/compiler` <= 0.6 reported UTF-8
|
|
11
|
+
* *byte* offsets and needed a remap; 0.7 emits UTF-16, so the remap is gone).
|
|
12
12
|
* A genuine parse error on a specific file is a real failure and PROPAGATES; only
|
|
13
13
|
* a load/init failure returns `null`.
|
|
14
14
|
*/
|
package/dist/rsvelte-parse.js
CHANGED
|
@@ -8,6 +8,11 @@ import { createRequire } from 'node:module';
|
|
|
8
8
|
// free engine (`index.ts`/`analyze.ts`), so the browser playground build stays
|
|
9
9
|
// clean.
|
|
10
10
|
const require = createRequire(import.meta.url);
|
|
11
|
+
/** The wasm-pack `--target web` bytes shipped in `@rsvelte/compiler`, reached via
|
|
12
|
+
* the package's stable `./wasm` subpath export (added upstream in 0.8.1). Using
|
|
13
|
+
* the public subpath keeps the loader independent of the internal crate name that
|
|
14
|
+
* determines the actual artifact basename. */
|
|
15
|
+
const WASM_FILE = '@rsvelte/compiler/wasm';
|
|
11
16
|
let ready = false;
|
|
12
17
|
/** Require `@rsvelte/compiler` and initialize its wasm once. Throws if the
|
|
13
18
|
* package can't be resolved or the wasm can't be instantiated. */
|
|
@@ -17,7 +22,7 @@ function loadCompiler() {
|
|
|
17
22
|
if (!ready) {
|
|
18
23
|
// A `wasm-pack --target web` module: init once with the wasm bytes (Node has
|
|
19
24
|
// no fetch for file URLs), after which `parse_svelte` is callable.
|
|
20
|
-
const wasmPath = require.resolve(
|
|
25
|
+
const wasmPath = require.resolve(WASM_FILE);
|
|
21
26
|
compiler.initSync({ module: readFileSync(wasmPath) });
|
|
22
27
|
ready = true;
|
|
23
28
|
}
|
|
@@ -30,9 +35,9 @@ function loadCompiler() {
|
|
|
30
35
|
* rsvelte is the default parser, the caller THROWS on `null` rather than silently
|
|
31
36
|
* using svelte/compiler; `parser: 'svelte'` is the explicit opt-out.
|
|
32
37
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
38
|
+
* rsvelte's AST positions match svelte/compiler's — UTF-16 code-unit offsets — so
|
|
39
|
+
* the AST feeds the engine directly (`@rsvelte/compiler` <= 0.6 reported UTF-8
|
|
40
|
+
* *byte* offsets and needed a remap; 0.7 emits UTF-16, so the remap is gone).
|
|
36
41
|
* A genuine parse error on a specific file is a real failure and PROPAGATES; only
|
|
37
42
|
* a load/init failure returns `null`.
|
|
38
43
|
*/
|
package/dist/scan.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ComponentId } from './ir.js';
|
|
2
2
|
import type { Resolve, ReadFile } from './analyze.js';
|
|
3
|
+
import { type DevOnlyFilter } from './dev-only.js';
|
|
3
4
|
export { computeEscapedComponents, type EscapeScanResult } from './escape-scan.js';
|
|
5
|
+
export { DEFAULT_DEV_ONLY, compileDevOnly, type DevOnlyFilter } from './dev-only.js';
|
|
4
6
|
/** Default filesystem resolver: resolve `source` relative to its importer. */
|
|
5
7
|
export declare const fsResolve: Resolve;
|
|
6
8
|
/** Default filesystem reader. */
|
|
@@ -9,5 +11,14 @@ export declare const fsReadFile: ReadFile;
|
|
|
9
11
|
* Recursively collect every `.svelte` file under `dir` (skipping `node_modules`
|
|
10
12
|
* and dot-directories). A Shell helper, kept out of the env-free engine core
|
|
11
13
|
* (docs/ARCHITECTURE.md §5): plugins use it to seed the whole-program crawl.
|
|
14
|
+
*
|
|
15
|
+
* `devOnly` lists files that never ship in the production bundle (tests, stories);
|
|
16
|
+
* a match stops counting as a component consumer, so it is not seeded as an entry
|
|
17
|
+
* and cannot pessimize the shake (docs §8.1.1). A matched file the app actually
|
|
18
|
+
* imports is still crawled and shaken through the normal graph — this only removes
|
|
19
|
+
* it as a SEED. Omitted, it defaults to {@link DEFAULT_DEV_ONLY} matched relative
|
|
20
|
+
* to `dir`; the Vite plugin passes a predicate compiled against the Vite ROOT so a
|
|
21
|
+
* custom pattern is root-relative there. Pass `compileDevOnly(dir, [])` to seed
|
|
22
|
+
* every file.
|
|
12
23
|
*/
|
|
13
|
-
export declare function collectSvelteFiles(dir: string): ComponentId[];
|
|
24
|
+
export declare function collectSvelteFiles(dir: string, devOnly?: DevOnlyFilter): ComponentId[];
|
package/dist/scan.js
CHANGED
|
@@ -5,9 +5,14 @@
|
|
|
5
5
|
// ----------------------------------------------------------------------
|
|
6
6
|
import * as fs from 'node:fs';
|
|
7
7
|
import * as path from 'node:path';
|
|
8
|
+
import { compileDevOnly } from './dev-only.js';
|
|
8
9
|
// The escape-scan machinery lives in `./escape-scan.js` (internal); only the single
|
|
9
10
|
// entry helper is part of the public `svelte-shaker/node` surface.
|
|
10
11
|
export { computeEscapedComponents } from './escape-scan.js';
|
|
12
|
+
// The dev-only glob support (docs §8.1.1) is Shell-side and part of the public
|
|
13
|
+
// `svelte-shaker/node` surface, so a plain-Rollup pipeline can compile the same
|
|
14
|
+
// predicate the Vite plugin does and feed it to both scans.
|
|
15
|
+
export { DEFAULT_DEV_ONLY, compileDevOnly } from './dev-only.js';
|
|
11
16
|
/** Default filesystem resolver: resolve `source` relative to its importer. */
|
|
12
17
|
export const fsResolve = (source, importer) => {
|
|
13
18
|
if (!source.startsWith('.'))
|
|
@@ -20,24 +25,37 @@ export const fsReadFile = (id) => fs.readFileSync(id, 'utf-8');
|
|
|
20
25
|
* Recursively collect every `.svelte` file under `dir` (skipping `node_modules`
|
|
21
26
|
* and dot-directories). A Shell helper, kept out of the env-free engine core
|
|
22
27
|
* (docs/ARCHITECTURE.md §5): plugins use it to seed the whole-program crawl.
|
|
28
|
+
*
|
|
29
|
+
* `devOnly` lists files that never ship in the production bundle (tests, stories);
|
|
30
|
+
* a match stops counting as a component consumer, so it is not seeded as an entry
|
|
31
|
+
* and cannot pessimize the shake (docs §8.1.1). A matched file the app actually
|
|
32
|
+
* imports is still crawled and shaken through the normal graph — this only removes
|
|
33
|
+
* it as a SEED. Omitted, it defaults to {@link DEFAULT_DEV_ONLY} matched relative
|
|
34
|
+
* to `dir`; the Vite plugin passes a predicate compiled against the Vite ROOT so a
|
|
35
|
+
* custom pattern is root-relative there. Pass `compileDevOnly(dir, [])` to seed
|
|
36
|
+
* every file.
|
|
23
37
|
*/
|
|
24
|
-
export function collectSvelteFiles(dir) {
|
|
38
|
+
export function collectSvelteFiles(dir, devOnly = compileDevOnly(dir)) {
|
|
25
39
|
const out = [];
|
|
40
|
+
collectSvelteFilesInto(dir, devOnly, out);
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
/** Recursive worker: the compiled `devOnly` predicate is threaded, never recompiled. */
|
|
44
|
+
function collectSvelteFilesInto(dir, devOnly, out) {
|
|
26
45
|
let entries;
|
|
27
46
|
try {
|
|
28
47
|
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
29
48
|
}
|
|
30
49
|
catch {
|
|
31
|
-
return
|
|
50
|
+
return;
|
|
32
51
|
}
|
|
33
52
|
for (const entry of entries) {
|
|
34
53
|
if (entry.name === 'node_modules' || entry.name.startsWith('.'))
|
|
35
54
|
continue;
|
|
36
55
|
const full = path.join(dir, entry.name);
|
|
37
56
|
if (entry.isDirectory())
|
|
38
|
-
|
|
39
|
-
else if (entry.isFile() && entry.name.endsWith('.svelte'))
|
|
57
|
+
collectSvelteFilesInto(full, devOnly, out);
|
|
58
|
+
else if (entry.isFile() && entry.name.endsWith('.svelte') && !devOnly(full))
|
|
40
59
|
out.push(full);
|
|
41
60
|
}
|
|
42
|
-
return out;
|
|
43
61
|
}
|
package/dist/unread.js
CHANGED
|
@@ -81,7 +81,8 @@ export function collectUnread(models, plans) {
|
|
|
81
81
|
// (b): a prop is droppable when it survived every site's veto (and had the
|
|
82
82
|
// structural gates). A child with NO call sites keeps every `true` here — safe,
|
|
83
83
|
// since the child does not read the prop, so its own render is unchanged whether
|
|
84
|
-
// it is declared or not (and
|
|
84
|
+
// it is declared or not (and a consumer outside the `.svelte` graph, if any,
|
|
85
|
+
// bails the child).
|
|
85
86
|
for (const [id, perProp] of dropEligible) {
|
|
86
87
|
const set = new Set();
|
|
87
88
|
for (const [name, ok] of perProp)
|
package/dist/vite.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Plugin } from 'vite';
|
|
2
2
|
import { type DevMode } from './engine.js';
|
|
3
|
+
export { DEFAULT_DEV_ONLY } from './dev-only.js';
|
|
3
4
|
import { type MonomorphizeOptions } from './mono.js';
|
|
4
5
|
/**
|
|
5
6
|
* Options for the {@link shaker} plugin. The set below is exhaustive: any other
|
|
@@ -52,6 +53,35 @@ export interface ShakerOptions {
|
|
|
52
53
|
* without needing it is merely shaken less, never wrongly.
|
|
53
54
|
*/
|
|
54
55
|
preserve?: string[];
|
|
56
|
+
/**
|
|
57
|
+
* Glob patterns naming files that are DEV-ONLY — they never ship in the production
|
|
58
|
+
* bundle (colocated tests, mocks, Storybook stories), so their call sites must not
|
|
59
|
+
* count toward the shake. A matched file stops counting as a component consumer in
|
|
60
|
+
* BOTH directory scans (the `.svelte` seed scan and the non-`.svelte` escape scan);
|
|
61
|
+
* patterns are matched with `picomatch` against the posix-normalized path relative
|
|
62
|
+
* to the Vite root. Defaults to {@link DEFAULT_DEV_ONLY}.
|
|
63
|
+
*
|
|
64
|
+
* List ONLY files that never ship: the option declares a property of the files,
|
|
65
|
+
* which is exactly the soundness contract that makes discounting them safe. A
|
|
66
|
+
* matched file is NOT excluded from the shake — a dev-only `.svelte` file the app
|
|
67
|
+
* actually imports is still crawled and shaken through the normal import graph, so
|
|
68
|
+
* its genuine app call sites still count. This only removes it as a SEED / ESCAPE
|
|
69
|
+
* source; it cannot un-cover reachable app code.
|
|
70
|
+
*
|
|
71
|
+
* The failure mode to avoid: a matched file's call sites simply STOP COUNTING. So
|
|
72
|
+
* if a file that really ships matches a pattern — a `+page.svelte` under a route
|
|
73
|
+
* dir named `__tests__`, a `foo.test.utils.ts` that mounts a component — the
|
|
74
|
+
* distinct prop values it passes no longer block a fold, exactly as if you had left
|
|
75
|
+
* it out of {@link entries}. That is why the defaults are narrow, convention-based
|
|
76
|
+
* patterns rather than an open glob (see docs/ARCHITECTURE.md §8.1.1). It is
|
|
77
|
+
* unrelated to {@link preserve}: that keeps a shipping component's props as written;
|
|
78
|
+
* this declares that a file is not part of the production graph at all.
|
|
79
|
+
*
|
|
80
|
+
* Passing `devOnly` REPLACES the default rather than adding to it (predictable
|
|
81
|
+
* semantics); extend it with `[...DEFAULT_DEV_ONLY, '…']`, and pass `devOnly: []` to
|
|
82
|
+
* count every file (the pre-`devOnly` behavior).
|
|
83
|
+
*/
|
|
84
|
+
devOnly?: string[];
|
|
55
85
|
/**
|
|
56
86
|
* Per-call-site monomorphization tuning (docs §13.2). Monomorphization is ON
|
|
57
87
|
* by default because it is bail-safe and never bloats (the measured net-win
|
package/dist/vite.js
CHANGED
|
@@ -5,6 +5,9 @@ import { svelteShaker, svelteShakerWithMono } from './index.js';
|
|
|
5
5
|
import { DevShaker } from './engine.js';
|
|
6
6
|
import { collectSvelteFiles, fsResolve } from './scan.js';
|
|
7
7
|
import { computeEscapedComponents, isScannableModule, } from './escape-scan.js';
|
|
8
|
+
import { compileDevOnly } from './dev-only.js';
|
|
9
|
+
// Re-export so a user can extend the default dev-only list: `devOnly: [...DEFAULT_DEV_ONLY, '…']`.
|
|
10
|
+
export { DEFAULT_DEV_ONLY } from './dev-only.js';
|
|
8
11
|
import { DEFAULT_MONO_OPTIONS } from './mono.js';
|
|
9
12
|
import { tryLoadRsvelteParser } from './rsvelte-parse.js';
|
|
10
13
|
import { svelteShakerWasm, svelteShakerWasmWithMono, tryLoadWasmEngine } from './wasm-engine.js';
|
|
@@ -126,6 +129,7 @@ const RENAMED_OPTIONS = {
|
|
|
126
129
|
const KNOWN_OPTIONS = {
|
|
127
130
|
entries: true,
|
|
128
131
|
preserve: true,
|
|
132
|
+
devOnly: true,
|
|
129
133
|
monomorphize: true,
|
|
130
134
|
engine: true,
|
|
131
135
|
dev: true,
|
|
@@ -263,9 +267,13 @@ export function shaker(options = {}) {
|
|
|
263
267
|
if (!devMode)
|
|
264
268
|
return;
|
|
265
269
|
const dirs = (options.entries ?? ['.']).map((p) => path.resolve(root, p));
|
|
270
|
+
// One dev-only predicate for the whole dev session, compiled against the Vite
|
|
271
|
+
// root so custom patterns are root-relative regardless of which entry dir a
|
|
272
|
+
// file sits under. Fed to both scans and the watch guards below (docs §8.1.1).
|
|
273
|
+
const isDevOnly = compileDevOnly(root, options.devOnly);
|
|
266
274
|
// The engine's entry components, collected from the entry DIRS: `entries`
|
|
267
275
|
// names the roots to crawl from, these are the `.svelte` files under them.
|
|
268
|
-
const entryComponents = dirs.flatMap(collectSvelteFiles);
|
|
276
|
+
const entryComponents = dirs.flatMap((d) => collectSvelteFiles(d, isDevOnly));
|
|
269
277
|
const read = (id) => fs.readFileSync(id, 'utf-8');
|
|
270
278
|
const underDirs = (file) => dirs.some((d) => file === d || file.startsWith(d + path.sep));
|
|
271
279
|
// Re-scan the non-`.svelte` modules for `.svelte` call sites and re-apply
|
|
@@ -280,6 +288,7 @@ export function shaker(options = {}) {
|
|
|
280
288
|
root,
|
|
281
289
|
preserve: options.preserve,
|
|
282
290
|
components: entryComponents,
|
|
291
|
+
devOnly: isDevOnly,
|
|
283
292
|
resolve: fsResolve,
|
|
284
293
|
readFile: read,
|
|
285
294
|
});
|
|
@@ -301,7 +310,10 @@ export function shaker(options = {}) {
|
|
|
301
310
|
// un-shake a child; a removed one can re-shake it — docs §4). Re-shake and
|
|
302
311
|
// full-reload: over-invalidation is always sound, and add/remove is rare
|
|
303
312
|
// enough that fine-grained HMR for it is not worth the complexity here.
|
|
304
|
-
|
|
313
|
+
// Dev-only files are discounted by both scans, so an edit to one never moves
|
|
314
|
+
// the shake — skip them here too, or a `Foo.test.svelte` save would retrigger a
|
|
315
|
+
// full re-shake for no effect.
|
|
316
|
+
const isOurs = (file) => file.endsWith('.svelte') && underDirs(file) && !isDevOnly(file);
|
|
305
317
|
const onGraphChange = async (file, kind) => {
|
|
306
318
|
if (!devShaker || !isOurs(file))
|
|
307
319
|
return;
|
|
@@ -315,7 +327,7 @@ export function shaker(options = {}) {
|
|
|
315
327
|
// we re-shake and full-reload (over-invalidation is sound, but pointless
|
|
316
328
|
// reloads on unrelated `.ts` edits are avoided).
|
|
317
329
|
const onModuleChange = async (file) => {
|
|
318
|
-
if (!devShaker || !isScannableModule(file) || !underDirs(file))
|
|
330
|
+
if (!devShaker || !isScannableModule(file) || !underDirs(file) || isDevOnly(file))
|
|
319
331
|
return;
|
|
320
332
|
const before = escapedKey;
|
|
321
333
|
devShaker.setEscaped(await currentEscaped());
|
|
@@ -363,9 +375,12 @@ export function shaker(options = {}) {
|
|
|
363
375
|
if (devShaker)
|
|
364
376
|
return;
|
|
365
377
|
const dirs = (options.entries ?? ['.']).map((p) => path.resolve(root, p));
|
|
378
|
+
// One dev-only predicate for both scans, compiled against the Vite root so
|
|
379
|
+
// custom patterns are root-relative regardless of the entry dir (docs §8.1.1).
|
|
380
|
+
const isDevOnly = compileDevOnly(root, options.devOnly);
|
|
366
381
|
// The engine's entry components, collected from the entry DIRS: `entries`
|
|
367
382
|
// names the roots to crawl from, these are the `.svelte` files under them.
|
|
368
|
-
const entryComponents = dirs.flatMap(collectSvelteFiles);
|
|
383
|
+
const entryComponents = dirs.flatMap((d) => collectSvelteFiles(d, isDevOnly));
|
|
369
384
|
if (entryComponents.length === 0) {
|
|
370
385
|
shaken = {};
|
|
371
386
|
variantSources = new Map();
|
|
@@ -404,6 +419,7 @@ export function shaker(options = {}) {
|
|
|
404
419
|
root,
|
|
405
420
|
preserve: options.preserve,
|
|
406
421
|
components: entryComponents,
|
|
422
|
+
devOnly: isDevOnly,
|
|
407
423
|
resolve,
|
|
408
424
|
readFile: read,
|
|
409
425
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-shaker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Tree shaking for Svelte components",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dead-code-elimination",
|
|
@@ -49,12 +49,14 @@
|
|
|
49
49
|
"access": "public"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@rsvelte/compiler": "0.
|
|
52
|
+
"@rsvelte/compiler": "0.8.1",
|
|
53
53
|
"magic-string": "1.0.0",
|
|
54
|
+
"picomatch": "4.0.5",
|
|
54
55
|
"zimmerframe": "1.1.4"
|
|
55
56
|
},
|
|
56
57
|
"devDependencies": {
|
|
57
58
|
"@sveltejs/vite-plugin-svelte": "^7.2.0",
|
|
59
|
+
"@types/picomatch": "4.0.2",
|
|
58
60
|
"svelte": "^5.56.6",
|
|
59
61
|
"vite": "^8.1.5"
|
|
60
62
|
},
|