@svelte-vitals/vite 0.11.0 → 0.12.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.
Files changed (2) hide show
  1. package/dist/index.js +181 -32
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  // src/plugin.ts
8
8
  import { existsSync } from "fs";
9
9
  import { writeFile, mkdir } from "fs/promises";
10
- import { join as join4, isAbsolute, dirname } from "path";
10
+ import { join as join4, isAbsolute, dirname, relative, basename, sep } from "path";
11
11
  import { defineConfig as defineConfig2 } from "@svelte-vitals/core";
12
12
 
13
13
  // src/analyze.ts
@@ -131,6 +131,15 @@ function readPackageVersion() {
131
131
  return "0.0.0";
132
132
  }
133
133
  }
134
+ function readCoreVersion() {
135
+ try {
136
+ const entry = import.meta.resolve("@svelte-vitals/core");
137
+ const pkg = JSON.parse(readFileSync(new URL("../package.json", entry), "utf8"));
138
+ return pkg.version ?? "0.0.0";
139
+ } catch {
140
+ return "0.0.0";
141
+ }
142
+ }
134
143
 
135
144
  // src/analyze.ts
136
145
  async function analyze(prerenderPagesDir, cwd, options) {
@@ -166,28 +175,6 @@ Scanned ${components.length} component(s) under src/ for Correctness/Security/Ar
166
175
  };
167
176
  }
168
177
 
169
- // src/ui/store.ts
170
- function createStore() {
171
- const byRoute = /* @__PURE__ */ new Map();
172
- const subs = /* @__PURE__ */ new Set();
173
- return {
174
- set(route, results) {
175
- byRoute.set(
176
- route,
177
- results.map((r) => r.route ? r : { ...r, route })
178
- );
179
- for (const fn of subs) fn();
180
- },
181
- snapshot() {
182
- return [...byRoute.values()].flat();
183
- },
184
- subscribe(fn) {
185
- subs.add(fn);
186
- return () => subs.delete(fn);
187
- }
188
- };
189
- }
190
-
191
178
  // src/ui/serve.ts
192
179
  import { buildHtmlDocument, buildJsonReport } from "@svelte-vitals/core";
193
180
  var LIVE_SCRIPT = `<script data-live>
@@ -205,8 +192,12 @@ var LIVE_SCRIPT = `<script data-live>
205
192
  });
206
193
  })();
207
194
  </script>`;
208
- function renderDashboard(results, config, meta) {
209
- const html = buildHtmlDocument(buildJsonReport(results, config, meta), meta);
195
+ function renderDashboard(results, config, meta, badges) {
196
+ const html = buildHtmlDocument(
197
+ buildJsonReport(results, config, meta),
198
+ meta,
199
+ badges ? { routeBadges: badges } : void 0
200
+ );
210
201
  return html.replace("</body>", LIVE_SCRIPT + "</body>");
211
202
  }
212
203
 
@@ -223,8 +214,7 @@ function isResultLike(x) {
223
214
  const f = r.fix;
224
215
  return typeof r.id === "string" && typeof r.message === "string" && typeof r.severity === "string" && SEVERITIES.has(r.severity) && typeof d === "object" && d !== null && typeof d.presence === "string" && typeof d.value === "string" && (r.category === void 0 || typeof r.category === "string" && CATEGORIES.has(r.category)) && isOptionalString(r.location) && isOptionalString(r.recommendation) && isOptionalString(r.docsUrl) && (f === void 0 || typeof f === "object" && f !== null && typeof f.description === "string" && isOptionalString(f.snippet) && isOptionalString(f.lang)) && (r.line === void 0 || typeof r.line === "number") && isOptionalString(r.route);
225
216
  }
226
- function installUiMiddleware(server, config, version) {
227
- const store = createStore();
217
+ function installUiMiddleware(server, config, version, store, coreVersion) {
228
218
  const clients = /* @__PURE__ */ new Set();
229
219
  store.subscribe(() => {
230
220
  for (const res of clients) {
@@ -277,7 +267,7 @@ function installUiMiddleware(server, config, version) {
277
267
  return;
278
268
  }
279
269
  try {
280
- const html = renderDashboard(store.snapshot(), config, { version });
270
+ const html = renderDashboard(store.snapshot(), config, { version, coreVersion }, store.badges());
281
271
  res.setHeader("Content-Type", "text/html");
282
272
  res.end(html);
283
273
  } catch {
@@ -287,7 +277,150 @@ function installUiMiddleware(server, config, version) {
287
277
  });
288
278
  }
289
279
 
280
+ // src/ui/store.ts
281
+ function composeSnapshot(staticResults, liveByRoute) {
282
+ const staticByRoute = /* @__PURE__ */ new Map();
283
+ const routeless = [];
284
+ for (const r of staticResults) {
285
+ if (r.route) {
286
+ const bucket = staticByRoute.get(r.route);
287
+ if (bucket) bucket.push(r);
288
+ else staticByRoute.set(r.route, [r]);
289
+ } else {
290
+ routeless.push(r);
291
+ }
292
+ }
293
+ const out = [...routeless];
294
+ for (const [route, live] of liveByRoute) {
295
+ const liveIds = new Set(live.map((r) => r.id));
296
+ const staticForRoute = staticByRoute.get(route) ?? [];
297
+ out.push(...staticForRoute.filter((r) => !liveIds.has(r.id)), ...live);
298
+ staticByRoute.delete(route);
299
+ }
300
+ for (const remaining of staticByRoute.values()) out.push(...remaining);
301
+ return out;
302
+ }
303
+ function composeBadges(staticResults, liveByRoute) {
304
+ const badges = {};
305
+ for (const r of staticResults) {
306
+ if (r.route) badges[r.route] = "static";
307
+ }
308
+ for (const route of liveByRoute.keys()) badges[route] = "measured";
309
+ return badges;
310
+ }
311
+ function createStore() {
312
+ let staticResults = [];
313
+ const liveByRoute = /* @__PURE__ */ new Map();
314
+ const subs = /* @__PURE__ */ new Set();
315
+ function notify() {
316
+ for (const fn of subs) fn();
317
+ }
318
+ return {
319
+ set(route, results) {
320
+ liveByRoute.set(
321
+ route,
322
+ results.map((r) => r.route ? r : { ...r, route })
323
+ );
324
+ notify();
325
+ },
326
+ setStatic(results) {
327
+ staticResults = results;
328
+ notify();
329
+ },
330
+ snapshot() {
331
+ return composeSnapshot(staticResults, liveByRoute);
332
+ },
333
+ badges() {
334
+ return composeBadges(staticResults, liveByRoute);
335
+ },
336
+ subscribe(fn) {
337
+ subs.add(fn);
338
+ return () => subs.delete(fn);
339
+ }
340
+ };
341
+ }
342
+
343
+ // src/ui/analysis.ts
344
+ function createAnalysisRunner(opts) {
345
+ const debounceMs = opts.debounceMs ?? 500;
346
+ let cachedAnalyze = opts.analyze;
347
+ let stopped = false;
348
+ let running = false;
349
+ let pending = false;
350
+ let timer;
351
+ async function getAnalyze() {
352
+ if (cachedAnalyze) return cachedAnalyze;
353
+ const mod = await import("svelte-vitals");
354
+ const fn = (o) => mod.analyzeProject(o);
355
+ cachedAnalyze = fn;
356
+ return fn;
357
+ }
358
+ async function runOnce() {
359
+ if (stopped) return;
360
+ running = true;
361
+ try {
362
+ const analyze2 = await getAnalyze();
363
+ const { results } = await analyze2({
364
+ cwd: opts.root,
365
+ treatDynamicAs: opts.treatDynamicAs,
366
+ metaComponents: opts.metaComponents,
367
+ rules: opts.rules,
368
+ failOn: opts.failOn
369
+ });
370
+ if (!stopped) opts.onResults(results);
371
+ } catch (err) {
372
+ if (!stopped) opts.onError(err);
373
+ } finally {
374
+ running = false;
375
+ if (!stopped && pending) {
376
+ pending = false;
377
+ void runOnce();
378
+ }
379
+ }
380
+ }
381
+ return {
382
+ start() {
383
+ if (stopped) return;
384
+ void runOnce();
385
+ },
386
+ notifyChange() {
387
+ if (stopped) return;
388
+ if (timer !== void 0) clearTimeout(timer);
389
+ timer = setTimeout(() => {
390
+ timer = void 0;
391
+ if (stopped) return;
392
+ if (running) pending = true;
393
+ else void runOnce();
394
+ }, debounceMs);
395
+ },
396
+ stop() {
397
+ stopped = true;
398
+ if (timer !== void 0) {
399
+ clearTimeout(timer);
400
+ timer = void 0;
401
+ }
402
+ pending = false;
403
+ }
404
+ };
405
+ }
406
+
290
407
  // src/plugin.ts
408
+ var CONFIG_BASENAMES = /* @__PURE__ */ new Set([
409
+ "svelte.config.js",
410
+ "svelte.config.ts",
411
+ "svelte-vitals.config.mjs",
412
+ "svelte-vitals.config.js",
413
+ "svelte-vitals.config.ts"
414
+ ]);
415
+ var IGNORED_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", ".svelte-kit", "build", "dist"]);
416
+ function isRelevant(file, root) {
417
+ const rel = relative(root, file);
418
+ if (rel === "" || rel.startsWith("..")) return false;
419
+ const segments = rel.split(sep);
420
+ if (segments.some((s) => IGNORED_SEGMENTS.has(s))) return false;
421
+ if (CONFIG_BASENAMES.has(basename(file))) return true;
422
+ return segments[0] === "src" || segments[0] === "static";
423
+ }
291
424
  var DEFAULT_PRERENDER_DIR = ".svelte-kit/output/prerendered/pages";
292
425
  function svelteVitals(options = {}) {
293
426
  let root = options.cwd ?? process.cwd();
@@ -330,16 +463,32 @@ function svelteVitals(options = {}) {
330
463
  apply: "serve",
331
464
  configureServer(server) {
332
465
  process.env.SVELTE_VITALS_UI = "1";
333
- server.httpServer?.once("close", () => {
334
- delete process.env.SVELTE_VITALS_UI;
335
- });
336
466
  const config = defineConfig2({
337
467
  treatDynamicAs: options.treatDynamicAs ?? "pass",
338
468
  metaComponents: options.metaComponents ?? [],
339
469
  rules: options.rules ?? {},
340
470
  failOn: options.failOn ?? "critical"
341
471
  });
342
- installUiMiddleware(server, config, readPackageVersion());
472
+ const store = createStore();
473
+ const uiRoot = options.cwd ?? server.config.root;
474
+ const runner = createAnalysisRunner({
475
+ root: uiRoot,
476
+ treatDynamicAs: options.treatDynamicAs,
477
+ metaComponents: options.metaComponents,
478
+ rules: options.rules,
479
+ failOn: options.failOn,
480
+ onResults: (results) => store.setStatic(results),
481
+ onError: (err) => console.warn("[svelte-vitals] dev analysis failed:", err)
482
+ });
483
+ runner.start();
484
+ server.watcher?.on("all", (_event, file) => {
485
+ if (isRelevant(file, uiRoot)) runner.notifyChange(file);
486
+ });
487
+ server.httpServer?.once("close", () => {
488
+ delete process.env.SVELTE_VITALS_UI;
489
+ runner.stop();
490
+ });
491
+ installUiMiddleware(server, config, readPackageVersion(), store, readCoreVersion());
343
492
  }
344
493
  };
345
494
  return [buildPlugin, uiPlugin];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-vitals/vite",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Vite/SvelteKit plugin for svelte-vitals — analyzes prerendered HTML during vite build.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -42,7 +42,8 @@
42
42
  "esm-env": "^1.2.2",
43
43
  "node-html-parser": "^8.0.4",
44
44
  "tinyglobby": "^0.2.17",
45
- "@svelte-vitals/core": "0.20.0"
45
+ "@svelte-vitals/core": "0.22.0",
46
+ "svelte-vitals": "0.21.0"
46
47
  },
47
48
  "peerDependencies": {
48
49
  "@sveltejs/kit": "^2.69.1",