@svelte-vitals/core 0.23.0 → 0.24.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
@@ -247,6 +247,11 @@ interface ComponentFacts {
247
247
  propCount: number;
248
248
  /** Module specifiers of every `import` in the instance + module scripts (Bundle PERF009). */
249
249
  imports: string[];
250
+ /** Module specifiers of every `import`, each with its source line (Bundle PERF009). */
251
+ importSpans: {
252
+ source: string;
253
+ line: number;
254
+ }[];
250
255
  /** Value `import * as X from '<bare pkg>'` namespace imports (type-only excluded) — Bundle PERF010. */
251
256
  namespaceImports: {
252
257
  source: string;
@@ -275,6 +280,10 @@ declare function parseComponentFacts(source: string, filename: string): {
275
280
  loc: number;
276
281
  propCount: number;
277
282
  imports: string[];
283
+ importSpans: {
284
+ source: string;
285
+ line: number;
286
+ }[];
278
287
  namespaceImports: {
279
288
  source: string;
280
289
  line: number;
package/dist/index.js CHANGED
@@ -419,9 +419,11 @@ function countLines(source) {
419
419
  if (source.length === 0) return 0;
420
420
  return source.split("\n").length - (source.endsWith("\n") ? 1 : 0);
421
421
  }
422
- function collectImportSources(program, acc) {
422
+ function collectImportSources(program, source, acc) {
423
423
  walkEstree(program, (n) => {
424
- if (n.type === "ImportDeclaration" && typeof n.source?.value === "string") acc.push(n.source.value);
424
+ if (n.type === "ImportDeclaration" && typeof n.source?.value === "string") {
425
+ acc.push({ source: n.source.value, line: lineOf(source, n.start) });
426
+ }
425
427
  });
426
428
  }
427
429
  function isBareSpecifier(s) {
@@ -459,10 +461,10 @@ function parseComponentFacts(source, filename) {
459
461
  collectSecurityFacts(ast.fragment ?? ast, source, htmlTags, javascriptUrls);
460
462
  const loc = countLines(source);
461
463
  const suppressions = collectSuppressions(source);
462
- const imports = [];
464
+ const importSpans = [];
463
465
  const namespaceImports = [];
464
466
  if (ast.module?.content) {
465
- collectImportSources(ast.module.content, imports);
467
+ collectImportSources(ast.module.content, source, importSpans);
466
468
  collectNamespaceImports(ast.module.content, source, namespaceImports);
467
469
  }
468
470
  const effects = [];
@@ -471,7 +473,7 @@ function parseComponentFacts(source, filename) {
471
473
  let propCount = 0;
472
474
  const program = ast.instance?.content;
473
475
  if (program) {
474
- collectImportSources(program, imports);
476
+ collectImportSources(program, source, importSpans);
475
477
  collectNamespaceImports(program, source, namespaceImports);
476
478
  propCount = countProps(program);
477
479
  const nonBindableProps = collectNonBindableProps(program);
@@ -509,6 +511,7 @@ function parseComponentFacts(source, filename) {
509
511
  if (!writtenOrEscaped.has(d.name)) constableStates.push(d);
510
512
  }
511
513
  }
514
+ const imports = importSpans.map((s) => s.source);
512
515
  return {
513
516
  eachBlocks,
514
517
  effects,
@@ -517,6 +520,7 @@ function parseComponentFacts(source, filename) {
517
520
  loc,
518
521
  propCount,
519
522
  imports,
523
+ importSpans,
520
524
  namespaceImports,
521
525
  constableStates,
522
526
  mutatedProps,
@@ -535,6 +539,7 @@ function emptyComponentFacts(file) {
535
539
  loc: 0,
536
540
  propCount: 0,
537
541
  imports: [],
542
+ importSpans: [],
538
543
  namespaceImports: [],
539
544
  constableStates: [],
540
545
  mutatedProps: [],
@@ -2143,7 +2148,7 @@ var arch002PropCount = componentRule({
2143
2148
  bad: (c) => c.propCount > MAX_PROPS ? [{ line: 1, message: `Component takes ${c.propCount} props (over ${MAX_PROPS})` }] : []
2144
2149
  });
2145
2150
 
2146
- // src/rules/performance/perf009-heavy-import.ts
2151
+ // src/rules/perf/perf009-heavy-import.ts
2147
2152
  var HEAVY_PACKAGES = {
2148
2153
  lodash: "import a submodule (lodash/debounce) or use lodash-es for tree-shaking",
2149
2154
  moment: "use a lighter date library (date-fns or dayjs) \u2014 moment is large and not tree-shakeable"
@@ -2156,20 +2161,24 @@ var perf009HeavyImport = componentRule({
2156
2161
  label: "No heavy imports",
2157
2162
  recommendation: "Import a submodule or switch to a lighter, tree-shakeable alternative.",
2158
2163
  rationale: "Importing a large, non-tree-shakeable package pulls its whole weight into the bundle even when only a fraction is used, slowing load.",
2159
- applies: (c) => c.imports.length > 0,
2164
+ // ComponentFacts is a public @svelte-vitals/core export — an external caller compiled
2165
+ // against an older version may still construct one without importSpans. Fall back to the
2166
+ // line-less `imports` (line: 0, the pre-fix behavior) instead of crashing on `undefined`.
2167
+ applies: (c) => (c.importSpans ?? c.imports).length > 0,
2160
2168
  bad: (c) => {
2161
2169
  const seen = /* @__PURE__ */ new Set();
2162
2170
  const out = [];
2163
- for (const src of c.imports) {
2171
+ const spans = c.importSpans ?? c.imports.map((source) => ({ source, line: 0 }));
2172
+ for (const { source: src, line } of spans) {
2164
2173
  if (!Object.hasOwn(HEAVY_PACKAGES, src) || seen.has(src)) continue;
2165
2174
  seen.add(src);
2166
- out.push({ line: 0, message: `Heavy import "${src}" \u2014 ${HEAVY_PACKAGES[src]}` });
2175
+ out.push({ line, message: `Heavy import "${src}" \u2014 ${HEAVY_PACKAGES[src]}` });
2167
2176
  }
2168
2177
  return out;
2169
2178
  }
2170
2179
  });
2171
2180
 
2172
- // src/rules/performance/perf010-namespace-import.ts
2181
+ // src/rules/perf/perf010-namespace-import.ts
2173
2182
  var perf010NamespaceImport = componentRule({
2174
2183
  id: "PERF010",
2175
2184
  title: "Namespace import",
@@ -2516,7 +2525,7 @@ function formatConsoleReport(results, config, options = {}) {
2516
2525
  lines.push("");
2517
2526
  }
2518
2527
  if (options.byRoute) lines.push(...byRouteTree(p, results, config, options.verbose ?? false));
2519
- if (summary.dynamic > 0) lines.push(p.dim("\u21AF = set dynamically (verified at runtime)."));
2528
+ if (options.verbose && summary.dynamic > 0) lines.push(p.dim("\u21AF = set dynamically (verified at runtime)."));
2520
2529
  return lines.join("\n").replace(/\n+$/, "\n");
2521
2530
  }
2522
2531
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-vitals/core",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "Shared, runtime-agnostic core for svelte-vitals (types, rule engine, scorer, reporter).",
5
5
  "type": "module",
6
6
  "license": "MIT",