@svelte-vitals/core 0.16.0 → 0.18.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 CHANGED
@@ -55,7 +55,7 @@ interface Result {
55
55
  line?: number;
56
56
  }
57
57
  type Scope = 'route' | 'project' | 'component';
58
- type Category = 'seo' | 'performance' | 'correctness' | 'security';
58
+ type Category = 'seo' | 'performance' | 'correctness' | 'security' | 'architecture';
59
59
  /** How dynamic (`{data.title}`) values are treated by scoring (design §4, §12). */
60
60
  type TreatDynamicAs = 'pass' | 'warn' | 'fail';
61
61
  /** Per-rule override: disable, or change severity. */
@@ -216,13 +216,15 @@ interface EffectFact {
216
216
  line: number;
217
217
  /** True when the effect body only assigns to `$state` variables (the "use $derived" smell). */
218
218
  assignsOnlyState: boolean;
219
+ /** True when this $effect has a NON-EMPTY body that reads no reactive value and makes no bare call — it never re-runs, so it should be onMount (CORRECT003). */
220
+ mountOnly: boolean;
219
221
  }
220
222
  /** A flagged source position in a component (e.g. an `{@html}` tag or a `javascript:` URL). */
221
223
  interface SourceSpan {
222
224
  /** 1-based source line, or 0 if unknown. */
223
225
  line: number;
224
226
  }
225
- /** Reactivity/correctness + security facts parsed from one `.svelte` component. */
227
+ /** Reactivity/correctness + security + architecture facts parsed from one `.svelte` component. */
226
228
  interface ComponentFacts {
227
229
  /** Source file the component came from. */
228
230
  file: string;
@@ -232,6 +234,22 @@ interface ComponentFacts {
232
234
  htmlTags: SourceSpan[];
233
235
  /** Element attributes with a literal `javascript:` URL (Security SEC002). */
234
236
  javascriptUrls: SourceSpan[];
237
+ /** Source line count of the component file (Architecture ARCH001). */
238
+ loc: number;
239
+ /** Named props destructured from `$props()`; 0 when unknowable (rest / non-destructured) (Architecture ARCH002). */
240
+ propCount: number;
241
+ /** Module specifiers of every `import` in the instance + module scripts (Bundle PERF009). */
242
+ imports: string[];
243
+ /** Value `import * as X from '<bare pkg>'` namespace imports (type-only excluded) — Bundle PERF010. */
244
+ namespaceImports: {
245
+ source: string;
246
+ line: number;
247
+ }[];
248
+ /** `$state` declarations never written or escaped anywhere in the component — candidates for const (CORRECT004). */
249
+ constableStates: {
250
+ name: string;
251
+ line: number;
252
+ }[];
235
253
  }
236
254
 
237
255
  /**
@@ -404,10 +422,20 @@ declare const seo030HeadingOrder: Rule;
404
422
 
405
423
  declare const correct001EachKey: Rule;
406
424
  declare const correct002EffectDerived: Rule;
425
+ declare const correct003EffectAsOnMount: Rule;
426
+
427
+ declare const correct004UnmutatedState: Rule;
407
428
 
408
429
  declare const sec001Html: Rule;
409
430
  declare const sec002JavascriptUrl: Rule;
410
431
 
432
+ declare const arch001ComponentSize: Rule;
433
+ declare const arch002PropCount: Rule;
434
+
435
+ declare const perf009HeavyImport: Rule;
436
+
437
+ declare const perf010NamespaceImport: Rule;
438
+
411
439
  declare const allRules: Rule[];
412
440
 
413
441
  interface RuleInfo {
@@ -498,10 +526,26 @@ declare function summarize(results: Result[], config: Config): Summary;
498
526
  /** Whether the run should fail the build/CI per the minimum failing severity. */
499
527
  declare function hasFailureAtOrAbove(summary: Summary, min: Severity): boolean;
500
528
 
529
+ /** String decorators for the console reporter. Injected so core stays pure/dep-free. */
530
+ interface Palette {
531
+ bold: (s: string) => string;
532
+ dim: (s: string) => string;
533
+ red: (s: string) => string;
534
+ yellow: (s: string) => string;
535
+ green: (s: string) => string;
536
+ cyan: (s: string) => string;
537
+ }
538
+ /** Default: no decoration (identity) — output is byte-identical to plain text. */
539
+ declare const noColorPalette: Palette;
540
+ /** Green ≥ 90, yellow ≥ 70, red otherwise — for a 0–100 score. */
541
+ declare function scoreColor(p: Palette, score: number): (s: string) => string;
542
+
501
543
  interface ConsoleReportOptions {
502
544
  byRoute?: boolean;
503
545
  /** Mode label shown in the header (default 'static mode'). */
504
546
  mode?: string;
547
+ /** Color decorators; defaults to no color. */
548
+ palette?: Palette;
505
549
  }
506
550
  /**
507
551
  * Render results as a console report string (design §7). Pure: returns a string,
@@ -616,4 +660,4 @@ declare function selectRules(rules: Rule[], config: Config): Rule[];
616
660
  /** Apply per-rule severity overrides to results (design §6). */
617
661
  declare function applyRuleSeverities(results: Result[], config: Config): Result[];
618
662
 
619
- export { BAND_COLOR, type Category, type Classification, type ComponentFacts, type Config, type ConsoleReportOptions, type Detection, type EachBlockFact, type EffectFact, type Fix, type HeadProvider, type HeadTag, type HeadingInfo, type HealthResult, type ImageInfo, type JsonReport, type Presence, type Project, ROBOTS_SOURCE_PATHS, type ResolvedHead, type ResolvedHeadings, type ResolvedImages, type Result, type Rule, type RuleContext, type RuleInfo, type RuleSetting, type Runtime, SITEMAP_SOURCE_PATHS, type Scope, type ScoreModel, type ScoreOptions, type ScoreResult, type Severity, type SourceSpan, type Summary, type TreatDynamicAs, type Value, allRules, applyRuleSeverities, buildHtmlDocument, buildJsonReport, classify, computeHealth, computeScore, correct001EachKey, correct002EffectDerived, defaultConfig, defaultProject, defineConfig, docsUrlFor, effectiveSeverity, escapeHtml, explainRule, formatAgentReport, formatConsoleReport, formatGithubReport, formatHtmlReport, formatJsonReport, formatSarifReport, hasFailureAtOrAbove, headTagRule, imageRule, isPenalized, linkRule, perf001ImageDimensions, perf002ImageLoading, perf003PreloadAs, perf004FontPreloadCrossorigin, perf005LcpImage, perf006ResponsiveImage, perf007RenderBlockingScript, perf008Preconnect, runRules, safeHref, scoreBand, scoresByCategory, sec001Html, sec002JavascriptUrl, selectRules, seo001Title, seo002Description, seo003Canonical, seo004OgImage, seo005OgTitle, seo006Robots, seo007Sitemap, seo008JsonLd, seo009HtmlLang, seo010Indexability, seo011TwitterCard, seo012OgDescription, seo013OgUrl, seo014Viewport, seo015SitemapInRobots, seo016JsonLdValidity, seo017DeprecatedType, seo018RelativeUrl, seo019DateFormat, seo020Placeholder, seo021RequiredProps, seo022TitleLength, seo023DescriptionLength, seo024Charset, seo025ImageAlt, seo026Hreflang, seo027Heading, seo028TitleUnique, seo029DescriptionUnique, seo030HeadingOrder, summarize };
663
+ export { BAND_COLOR, type Category, type Classification, type ComponentFacts, type Config, type ConsoleReportOptions, type Detection, type EachBlockFact, type EffectFact, type Fix, type HeadProvider, type HeadTag, type HeadingInfo, type HealthResult, type ImageInfo, type JsonReport, type Palette, type Presence, type Project, ROBOTS_SOURCE_PATHS, type ResolvedHead, type ResolvedHeadings, type ResolvedImages, type Result, type Rule, type RuleContext, type RuleInfo, type RuleSetting, type Runtime, SITEMAP_SOURCE_PATHS, type Scope, type ScoreModel, type ScoreOptions, type ScoreResult, type Severity, type SourceSpan, type Summary, type TreatDynamicAs, type Value, allRules, applyRuleSeverities, arch001ComponentSize, arch002PropCount, buildHtmlDocument, buildJsonReport, classify, computeHealth, computeScore, correct001EachKey, correct002EffectDerived, correct003EffectAsOnMount, correct004UnmutatedState, defaultConfig, defaultProject, defineConfig, docsUrlFor, effectiveSeverity, escapeHtml, explainRule, formatAgentReport, formatConsoleReport, formatGithubReport, formatHtmlReport, formatJsonReport, formatSarifReport, hasFailureAtOrAbove, headTagRule, imageRule, isPenalized, linkRule, noColorPalette, perf001ImageDimensions, perf002ImageLoading, perf003PreloadAs, perf004FontPreloadCrossorigin, perf005LcpImage, perf006ResponsiveImage, perf007RenderBlockingScript, perf008Preconnect, perf009HeavyImport, perf010NamespaceImport, runRules, safeHref, scoreBand, scoreColor, scoresByCategory, sec001Html, sec002JavascriptUrl, selectRules, seo001Title, seo002Description, seo003Canonical, seo004OgImage, seo005OgTitle, seo006Robots, seo007Sitemap, seo008JsonLd, seo009HtmlLang, seo010Indexability, seo011TwitterCard, seo012OgDescription, seo013OgUrl, seo014Viewport, seo015SitemapInRobots, seo016JsonLdValidity, seo017DeprecatedType, seo018RelativeUrl, seo019DateFormat, seo020Placeholder, seo021RequiredProps, seo022TitleLength, seo023DescriptionLength, seo024Charset, seo025ImageAlt, seo026Hreflang, seo027Heading, seo028TitleUnique, seo029DescriptionUnique, seo030HeadingOrder, summarize };
package/dist/index.js CHANGED
@@ -1507,6 +1507,32 @@ var correct002EffectDerived = componentRule({
1507
1507
  applies: (c) => c.effects.length > 0,
1508
1508
  bad: (c) => c.effects.filter((e) => e.assignsOnlyState).map((e) => ({ line: e.line, message: "$effect only assigns state \u2014 use $derived instead" }))
1509
1509
  });
1510
+ var correct003EffectAsOnMount = componentRule({
1511
+ id: "CORRECT003",
1512
+ title: "Effect used as onMount",
1513
+ category: "correctness",
1514
+ label: "$effect usage",
1515
+ recommendation: "Move mount-time side effects to onMount (import { onMount } from 'svelte'); reserve $effect for logic that reacts to $state/$derived/$props.",
1516
+ rationale: "An $effect that reads no reactive value runs once after mount and never re-runs \u2014 it is an onMount in disguise, which obscures intent and misuses the reactivity system.",
1517
+ applies: (c) => c.effects.length > 0,
1518
+ bad: (c) => c.effects.filter((e) => e.mountOnly).map((e) => ({ line: e.line, message: "$effect reads no reactive value \u2014 use onMount instead" }))
1519
+ });
1520
+
1521
+ // src/rules/correctness/correct004-unmutated-state.ts
1522
+ var correct004UnmutatedState = componentRule({
1523
+ id: "CORRECT004",
1524
+ title: "Unmutated $state",
1525
+ category: "correctness",
1526
+ severity: "info",
1527
+ label: "$state usage",
1528
+ recommendation: "If a value never changes, use const; if you only ever reassign it wholesale (never mutate its properties), use $state.raw to skip deep proxying.",
1529
+ rationale: "A $state that is never mutated pays for reactivity (deep proxying, tracking) it never uses; const (or $state.raw) is clearer and cheaper.",
1530
+ applies: (c) => c.constableStates.length > 0,
1531
+ bad: (c) => c.constableStates.map((s) => ({
1532
+ line: s.line,
1533
+ message: `$state "${s.name}" is never mutated \u2014 use const (or $state.raw if you only reassign it)`
1534
+ }))
1535
+ });
1510
1536
 
1511
1537
  // src/rules/security/sec001-002.ts
1512
1538
  var sec001Html = componentRule({
@@ -1530,6 +1556,83 @@ var sec002JavascriptUrl = componentRule({
1530
1556
  bad: (c) => c.javascriptUrls.map((u) => ({ line: u.line, message: "javascript: URL in an attribute" }))
1531
1557
  });
1532
1558
 
1559
+ // src/rules/architecture/arch001-002.ts
1560
+ var MAX_LOC = 400;
1561
+ var MAX_PROPS = 10;
1562
+ var arch001ComponentSize = componentRule({
1563
+ id: "ARCH001",
1564
+ title: "Component size",
1565
+ category: "architecture",
1566
+ severity: "info",
1567
+ label: "Component size",
1568
+ recommendation: `Split components over ${MAX_LOC} lines into smaller, focused pieces.`,
1569
+ rationale: "A very large component is hard to read, test, and reuse, and is a common sign that several responsibilities should be split out.",
1570
+ applies: (c) => c.loc > 0,
1571
+ // skip unanalyzable files (loc 0 = read/parse failure), don't PASS them
1572
+ bad: (c) => c.loc > MAX_LOC ? [{ line: 1, message: `Component is ${c.loc} lines (over ${MAX_LOC})` }] : []
1573
+ });
1574
+ var arch002PropCount = componentRule({
1575
+ id: "ARCH002",
1576
+ title: "Prop count",
1577
+ category: "architecture",
1578
+ severity: "info",
1579
+ label: "Prop count",
1580
+ recommendation: `Group related props into an object, or split the component, when it takes more than ${MAX_PROPS} props.`,
1581
+ rationale: "A component taking many props is usually doing too much; grouping or splitting keeps its API understandable.",
1582
+ applies: (c) => c.propCount > 0,
1583
+ // only components whose props we could count
1584
+ bad: (c) => c.propCount > MAX_PROPS ? [{ line: 1, message: `Component takes ${c.propCount} props (over ${MAX_PROPS})` }] : []
1585
+ });
1586
+
1587
+ // src/rules/performance/perf009-heavy-import.ts
1588
+ var HEAVY_PACKAGES = {
1589
+ lodash: "import a submodule (lodash/debounce) or use lodash-es for tree-shaking",
1590
+ moment: "use a lighter date library (date-fns or dayjs) \u2014 moment is large and not tree-shakeable"
1591
+ };
1592
+ var perf009HeavyImport = componentRule({
1593
+ id: "PERF009",
1594
+ title: "Heavy dependency import",
1595
+ category: "performance",
1596
+ severity: "info",
1597
+ label: "No heavy imports",
1598
+ recommendation: "Import a submodule or switch to a lighter, tree-shakeable alternative.",
1599
+ rationale: "Importing a large, non-tree-shakeable package pulls its whole weight into the bundle even when only a fraction is used, slowing load.",
1600
+ applies: (c) => c.imports.length > 0,
1601
+ bad: (c) => {
1602
+ const seen = /* @__PURE__ */ new Set();
1603
+ const out = [];
1604
+ for (const src of c.imports) {
1605
+ if (!Object.hasOwn(HEAVY_PACKAGES, src) || seen.has(src)) continue;
1606
+ seen.add(src);
1607
+ out.push({ line: 0, message: `Heavy import "${src}" \u2014 ${HEAVY_PACKAGES[src]}` });
1608
+ }
1609
+ return out;
1610
+ }
1611
+ });
1612
+
1613
+ // src/rules/performance/perf010-namespace-import.ts
1614
+ var perf010NamespaceImport = componentRule({
1615
+ id: "PERF010",
1616
+ title: "Namespace import",
1617
+ category: "performance",
1618
+ severity: "info",
1619
+ label: "No namespace imports",
1620
+ recommendation: "Use named imports (import { x } from 'pkg') instead of import * as \u2014 a namespace import keeps the whole module in the bundle.",
1621
+ rationale: "A namespace import (import * as X) forces the bundler to retain the entire module, so unused exports cannot be tree-shaken out.",
1622
+ applies: (c) => c.namespaceImports.length > 0,
1623
+ bad: (c) => {
1624
+ const minLine = /* @__PURE__ */ new Map();
1625
+ for (const ns of c.namespaceImports) {
1626
+ const prev = minLine.get(ns.source);
1627
+ if (prev === void 0 || ns.line < prev) minLine.set(ns.source, ns.line);
1628
+ }
1629
+ return [...minLine.entries()].sort((a, b) => a[1] - b[1]).map(([source, line]) => ({
1630
+ line,
1631
+ message: `Namespace import "* as \u2026 from '${source}'" \u2014 prefer named imports so the bundler can tree-shake`
1632
+ }));
1633
+ }
1634
+ });
1635
+
1533
1636
  // src/rules/index.ts
1534
1637
  var allRules = [
1535
1638
  seo001Title,
@@ -1572,8 +1675,14 @@ var allRules = [
1572
1675
  seo030HeadingOrder,
1573
1676
  correct001EachKey,
1574
1677
  correct002EffectDerived,
1678
+ correct003EffectAsOnMount,
1679
+ correct004UnmutatedState,
1575
1680
  sec001Html,
1576
- sec002JavascriptUrl
1681
+ sec002JavascriptUrl,
1682
+ arch001ComponentSize,
1683
+ arch002PropCount,
1684
+ perf009HeavyImport,
1685
+ perf010NamespaceImport
1577
1686
  ];
1578
1687
  function explainRule(id) {
1579
1688
  const target = id.toUpperCase();
@@ -1700,6 +1809,21 @@ function computeHealth(results, config) {
1700
1809
  return { health, categories, weights };
1701
1810
  }
1702
1811
 
1812
+ // src/reporter/palette.ts
1813
+ var noColorPalette = {
1814
+ bold: (s) => s,
1815
+ dim: (s) => s,
1816
+ red: (s) => s,
1817
+ yellow: (s) => s,
1818
+ green: (s) => s,
1819
+ cyan: (s) => s
1820
+ };
1821
+ function scoreColor(p, score) {
1822
+ if (score >= 90) return p.green;
1823
+ if (score >= 70) return p.yellow;
1824
+ return p.red;
1825
+ }
1826
+
1703
1827
  // src/reporter/console.ts
1704
1828
  var RULE = "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500";
1705
1829
  var SEVERITY_TITLE = {
@@ -1711,66 +1835,77 @@ var CATEGORY_LABEL = {
1711
1835
  seo: "SEO",
1712
1836
  performance: "Performance",
1713
1837
  correctness: "Correctness",
1714
- security: "Security"
1838
+ security: "Security",
1839
+ architecture: "Architecture"
1715
1840
  };
1716
- var CATEGORY_ORDER = ["seo", "performance", "correctness", "security"];
1717
- function scoreLine(label, { score, scoreModel }) {
1841
+ var CATEGORY_ORDER = ["seo", "performance", "correctness", "security", "architecture"];
1842
+ function scoreLine(p, label, { score, scoreModel }) {
1718
1843
  const parts = [`route avg ${scoreModel.routeAverage}`];
1719
1844
  if (scoreModel.sitePenalty > 0) parts.push(`site \u2212${scoreModel.sitePenalty}`);
1720
1845
  if (scoreModel.criticalCap !== null) parts.push(`capped at ${scoreModel.criticalCap}: critical present`);
1721
- return `${label} Score: ${score}/100 (${parts.join(" \xB7 ")})`;
1846
+ return `${label} Score: ${scoreColor(p, score)(`${score}/100`)} ${p.dim(`(${parts.join(" \xB7 ")})`)}`;
1722
1847
  }
1723
- function byRouteTree(results, config) {
1848
+ function byRouteTree(p, results, config) {
1724
1849
  const routes = /* @__PURE__ */ new Map();
1725
1850
  for (const r of results) {
1726
1851
  if (r.route === void 0) continue;
1727
1852
  if (!routes.has(r.route)) routes.set(r.route, []);
1728
1853
  routes.get(r.route).push(r);
1729
1854
  }
1730
- const lines = ["By route", RULE];
1855
+ const lines = [p.bold("By route"), p.dim(RULE)];
1731
1856
  for (const [route, rs] of [...routes.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
1732
1857
  const { score } = computeScore(rs, config, { applyCriticalCap: false });
1733
- lines.push(`${route.padEnd(28)} ${score}`);
1858
+ lines.push(`${route.padEnd(28)} ${scoreColor(p, score)(`${score}`)}`);
1734
1859
  for (const r of rs.filter((x) => classify(x, config) === "fail")) {
1735
- lines.push(` \u2717 ${r.id} ${r.message}`);
1860
+ lines.push(` ${p.red("\u2717")} ${r.id} ${r.message}`);
1736
1861
  }
1737
1862
  }
1738
1863
  lines.push("");
1739
1864
  return lines;
1740
1865
  }
1741
1866
  function formatConsoleReport(results, config, options = {}) {
1867
+ const p = options.palette ?? noColorPalette;
1742
1868
  const summary = summarize(results, config);
1743
1869
  const { health, categories: byCat } = computeHealth(results, config);
1744
1870
  const present2 = CATEGORY_ORDER.filter((c) => byCat[c] !== void 0);
1745
- const header = [`Svelte Vitals \xB7 ${options.mode ?? "static mode"}`, "", `Health: ${health}/100`];
1871
+ const header = [
1872
+ p.bold(`Svelte Vitals \xB7 ${options.mode ?? "static mode"}`),
1873
+ "",
1874
+ `${p.bold("Health:")} ${scoreColor(p, health)(`${health}/100`)}`
1875
+ ];
1746
1876
  for (const c of present2) {
1747
- header.push(scoreLine(CATEGORY_LABEL[c] ?? c, byCat[c]));
1877
+ header.push(scoreLine(p, CATEGORY_LABEL[c] ?? c, byCat[c]));
1748
1878
  }
1749
1879
  const lines = [...header, ""];
1880
+ const SEVERITY_COLOR = {
1881
+ critical: (s) => p.red(p.bold(s)),
1882
+ warning: (s) => p.yellow(p.bold(s)),
1883
+ info: (s) => p.dim(s)
1884
+ };
1750
1885
  const failures = results.filter((r) => classify(r, config) === "fail");
1751
1886
  for (const severity of ["critical", "warning", "info"]) {
1752
1887
  const bucket = failures.filter((r) => effectiveSeverity(r, config) === severity);
1753
1888
  if (bucket.length === 0) continue;
1754
- lines.push(`${SEVERITY_TITLE[severity]} (${bucket.length})`, RULE);
1889
+ lines.push(SEVERITY_COLOR[severity](`${SEVERITY_TITLE[severity]} (${bucket.length})`), p.dim(RULE));
1755
1890
  for (const r of bucket) {
1756
- lines.push(`\u2717 ${r.id} ${r.message}`);
1757
- if (r.route) lines.push(` ${r.route}`);
1758
- if (r.location) lines.push(` ${r.location}${r.line ? `:${r.line}` : ""}`);
1891
+ lines.push(`${p.red("\u2717")} ${r.id} ${r.message}`);
1892
+ if (r.route) lines.push(p.dim(` ${r.route}`));
1893
+ if (r.location) lines.push(p.dim(` ${r.location}${r.line ? `:${r.line}` : ""}`));
1759
1894
  }
1760
1895
  lines.push("");
1761
1896
  }
1762
1897
  const passed = results.filter((r) => classify(r, config) !== "fail");
1763
1898
  if (passed.length > 0) {
1764
- lines.push(`Passed (${passed.length})`, RULE);
1899
+ lines.push(p.bold(`Passed (${passed.length})`), p.dim(RULE));
1765
1900
  for (const r of passed) {
1766
- const marker = classify(r, config) === "dynamic" ? " \u21AF dynamic" : "";
1901
+ const marker = classify(r, config) === "dynamic" ? p.cyan(" \u21AF dynamic") : "";
1767
1902
  const route = r.route ? ` ${r.route}` : "";
1768
- lines.push(`\u2713 ${r.id} ${r.message}${marker}${route}`);
1903
+ lines.push(`${p.green("\u2713")} ${r.id} ${r.message}${marker}${route}`);
1769
1904
  }
1770
1905
  lines.push("");
1771
1906
  }
1772
- if (options.byRoute) lines.push(...byRouteTree(results, config));
1773
- if (summary.dynamic > 0) lines.push("\u21AF = set dynamically (verified at runtime).");
1907
+ if (options.byRoute) lines.push(...byRouteTree(p, results, config));
1908
+ if (summary.dynamic > 0) lines.push(p.dim("\u21AF = set dynamically (verified at runtime)."));
1774
1909
  return lines.join("\n").replace(/\n+$/, "\n");
1775
1910
  }
1776
1911
 
@@ -2161,6 +2296,8 @@ export {
2161
2296
  SITEMAP_SOURCE_PATHS,
2162
2297
  allRules,
2163
2298
  applyRuleSeverities,
2299
+ arch001ComponentSize,
2300
+ arch002PropCount,
2164
2301
  buildHtmlDocument,
2165
2302
  buildJsonReport,
2166
2303
  classify,
@@ -2168,6 +2305,8 @@ export {
2168
2305
  computeScore,
2169
2306
  correct001EachKey,
2170
2307
  correct002EffectDerived,
2308
+ correct003EffectAsOnMount,
2309
+ correct004UnmutatedState,
2171
2310
  defaultConfig,
2172
2311
  defaultProject,
2173
2312
  defineConfig,
@@ -2186,6 +2325,7 @@ export {
2186
2325
  imageRule,
2187
2326
  isPenalized,
2188
2327
  linkRule,
2328
+ noColorPalette,
2189
2329
  perf001ImageDimensions,
2190
2330
  perf002ImageLoading,
2191
2331
  perf003PreloadAs,
@@ -2194,9 +2334,12 @@ export {
2194
2334
  perf006ResponsiveImage,
2195
2335
  perf007RenderBlockingScript,
2196
2336
  perf008Preconnect,
2337
+ perf009HeavyImport,
2338
+ perf010NamespaceImport,
2197
2339
  runRules,
2198
2340
  safeHref,
2199
2341
  scoreBand,
2342
+ scoreColor,
2200
2343
  scoresByCategory,
2201
2344
  sec001Html,
2202
2345
  sec002JavascriptUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-vitals/core",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "Shared, runtime-agnostic core for svelte-vitals (types, rule engine, scorer, reporter).",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "homepage": "https://github.com/oekazuma/svelte-vitals#readme",
23
23
  "engines": {
24
- "node": ">=18"
24
+ "node": ">=18.20.8"
25
25
  },
26
26
  "sideEffects": false,
27
27
  "exports": {