@svelte-vitals/vite 0.29.7 → 0.30.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 +1 -1
- package/dist/{chunk-EHCC47NO.js → chunk-VLQYXCE6.js} +103 -1
- package/dist/hooks/index.js +27 -12
- package/dist/index.js +24 -61
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Vite/SvelteKit plugin for [svelte-vitals](https://github.com/oekazuma/svelte-vitals). Two things in one package:
|
|
6
6
|
|
|
7
|
-
- **Build-time gate** — piggybacks on `vite build`, parses the **prerendered HTML
|
|
7
|
+
- **Build-time gate** — piggybacks on `vite build`, parses the **prerendered HTML** (head metadata, images, headings, landmarks and ids), and runs the same rule engine as the CLI (SEO, Performance, Correctness, Security, Architecture, Accessibility) — library-agnostic, because it inspects the real output. Fails the build when findings reach `failOn`.
|
|
8
8
|
- **Live dev dashboard** — on by default during `vite dev`: a searchable, filterable code-health dashboard at `/__svelte-vitals/`, covering the whole project from startup via static analysis, refined to real rendered values as you browse.
|
|
9
9
|
|
|
10
10
|
> **ESM-only** (Node 22.13+). Ships ES modules only; `require()` is unsupported by design.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/providers/rendered/parse-html.ts
|
|
2
2
|
import { parse } from "node-html-parser";
|
|
3
|
+
import { decodeFragmentId, splitTokens, isTopFragment, LANDMARK_ROLES, IDREF_ATTRS } from "@svelte-vitals/core";
|
|
3
4
|
function attrValue(v) {
|
|
4
5
|
return v !== void 0 && v.trim().length > 0 ? "static" : "absent";
|
|
5
6
|
}
|
|
@@ -26,6 +27,49 @@ function isClassicScriptType(type) {
|
|
|
26
27
|
const normalized = type.trim().toLowerCase();
|
|
27
28
|
return normalized === "" || JS_MIME_TYPES.has(normalized);
|
|
28
29
|
}
|
|
30
|
+
var SECTIONING_TAGS = /* @__PURE__ */ new Set(["article", "aside", "main", "nav", "section"]);
|
|
31
|
+
function collectA11y(root) {
|
|
32
|
+
const landmarks = [];
|
|
33
|
+
const nestedLandmarks = [];
|
|
34
|
+
const ids = [];
|
|
35
|
+
const idRefs = [];
|
|
36
|
+
const walk = (el, ctx) => {
|
|
37
|
+
const tag = el.rawTagName?.toLowerCase();
|
|
38
|
+
const roleAttr = el.getAttribute("role");
|
|
39
|
+
let landmark;
|
|
40
|
+
if (roleAttr !== void 0) {
|
|
41
|
+
const role = splitTokens(roleAttr)[0];
|
|
42
|
+
landmark = role && LANDMARK_ROLES.has(role) ? role : void 0;
|
|
43
|
+
} else if (tag === "main") {
|
|
44
|
+
landmark = "main";
|
|
45
|
+
} else if ((tag === "header" || tag === "footer") && ctx.sectioning === 0) {
|
|
46
|
+
landmark = tag === "header" ? "banner" : "contentinfo";
|
|
47
|
+
}
|
|
48
|
+
if (landmark) {
|
|
49
|
+
landmarks.push(landmark);
|
|
50
|
+
const within = ctx.landmarks.at(-1);
|
|
51
|
+
if (within) nestedLandmarks.push({ kind: landmark, within });
|
|
52
|
+
}
|
|
53
|
+
const id = el.getAttribute("id");
|
|
54
|
+
if (id && id.trim()) ids.push(id);
|
|
55
|
+
const href = el.getAttribute("href");
|
|
56
|
+
if (href && href.startsWith("#") && href.length > 1) {
|
|
57
|
+
const fragment = decodeFragmentId(href.slice(1));
|
|
58
|
+
if (!isTopFragment(fragment)) idRefs.push({ id: fragment, attr: "href" });
|
|
59
|
+
}
|
|
60
|
+
for (const attr of IDREF_ATTRS) {
|
|
61
|
+
for (const token of splitTokens(el.getAttribute(attr) ?? void 0)) idRefs.push({ id: token, attr });
|
|
62
|
+
}
|
|
63
|
+
if (tag === "template") return;
|
|
64
|
+
const nextCtx = {
|
|
65
|
+
sectioning: ctx.sectioning + (SECTIONING_TAGS.has(tag) ? 1 : 0),
|
|
66
|
+
landmarks: landmark ? [...ctx.landmarks, landmark] : ctx.landmarks
|
|
67
|
+
};
|
|
68
|
+
for (const child of el.children) walk(child, nextCtx);
|
|
69
|
+
};
|
|
70
|
+
for (const child of root.children) walk(child, { sectioning: 0, landmarks: [] });
|
|
71
|
+
return { landmarks, nestedLandmarks, ids, idRefs };
|
|
72
|
+
}
|
|
29
73
|
function parseHtmlHead(html) {
|
|
30
74
|
const root = parse(html);
|
|
31
75
|
const head = root.querySelector("head") ?? root;
|
|
@@ -118,7 +162,63 @@ function parseHtmlHead(html) {
|
|
|
118
162
|
hasSrcset: img.hasAttribute("srcset"),
|
|
119
163
|
line: 0
|
|
120
164
|
}));
|
|
121
|
-
|
|
165
|
+
const a11y = collectA11y(root);
|
|
166
|
+
return { tags, htmlLang, headings, images, ...a11y };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/providers/rendered/collect.ts
|
|
170
|
+
import { readFile } from "fs/promises";
|
|
171
|
+
import { join } from "path";
|
|
172
|
+
import { glob } from "tinyglobby";
|
|
173
|
+
function toOccurrenceMap(keys, file) {
|
|
174
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
175
|
+
for (const key of keys) (out[key] ??= []).push({ file, line: 0 });
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
function deriveRouteFromHtmlPath(relPath) {
|
|
179
|
+
let p = relPath.replace(/\\/g, "/").replace(/\.html$/, "");
|
|
180
|
+
if (p === "index") return "/";
|
|
181
|
+
if (p.endsWith("/index")) p = p.slice(0, -"/index".length);
|
|
182
|
+
return "/" + p;
|
|
183
|
+
}
|
|
184
|
+
async function collectRenderedHeads(prerenderPagesDir) {
|
|
185
|
+
const files = (await glob("**/*.html", { cwd: prerenderPagesDir })).sort();
|
|
186
|
+
const parsedFiles = await Promise.all(
|
|
187
|
+
files.map(async (rel) => ({ rel, parsed: parseHtmlHead(await readFile(join(prerenderPagesDir, rel), "utf8")) }))
|
|
188
|
+
);
|
|
189
|
+
const heads = [];
|
|
190
|
+
const headings = [];
|
|
191
|
+
const images = [];
|
|
192
|
+
const a11y = [];
|
|
193
|
+
let htmlLang = { presence: "none", value: "absent" };
|
|
194
|
+
for (const { rel, parsed } of parsedFiles) {
|
|
195
|
+
if (htmlLang.presence === "none" && parsed.htmlLang.presence === "own") htmlLang = parsed.htmlLang;
|
|
196
|
+
const route = deriveRouteFromHtmlPath(rel);
|
|
197
|
+
heads.push({
|
|
198
|
+
route,
|
|
199
|
+
source: "rendered",
|
|
200
|
+
tags: parsed.tags,
|
|
201
|
+
file: rel
|
|
202
|
+
});
|
|
203
|
+
headings.push({
|
|
204
|
+
route,
|
|
205
|
+
// Rendered mode does not track source lines (line 0 = unknown); file is the HTML path.
|
|
206
|
+
headings: parsed.headings.map((level) => ({ level, line: 0, file: rel }))
|
|
207
|
+
});
|
|
208
|
+
images.push({ route, images: parsed.images.map((img) => ({ ...img, file: rel })) });
|
|
209
|
+
a11y.push({
|
|
210
|
+
route,
|
|
211
|
+
landmarks: toOccurrenceMap(parsed.landmarks, rel),
|
|
212
|
+
nestedLandmarks: parsed.nestedLandmarks.map((n) => ({ ...n, file: rel, line: 0 })),
|
|
213
|
+
ids: toOccurrenceMap(parsed.ids, rel),
|
|
214
|
+
idRefs: parsed.idRefs.map((r) => ({ ...r, file: rel, line: 0 })),
|
|
215
|
+
idCandidates: [...new Set(parsed.ids)],
|
|
216
|
+
// The prerendered document IS the closed world: every id/landmark/reference it can ever
|
|
217
|
+
// have is already in it, unlike source mode which may hit an unresolved component.
|
|
218
|
+
fullyResolved: true
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return { heads, headings, images, a11y, htmlLang };
|
|
122
222
|
}
|
|
123
223
|
|
|
124
224
|
// src/loopback.ts
|
|
@@ -133,5 +233,7 @@ function isLoopbackOrigin(origin) {
|
|
|
133
233
|
|
|
134
234
|
export {
|
|
135
235
|
parseHtmlHead,
|
|
236
|
+
toOccurrenceMap,
|
|
237
|
+
collectRenderedHeads,
|
|
136
238
|
isLoopbackOrigin
|
|
137
239
|
};
|
package/dist/hooks/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isLoopbackOrigin,
|
|
3
|
-
parseHtmlHead
|
|
4
|
-
|
|
3
|
+
parseHtmlHead,
|
|
4
|
+
toOccurrenceMap
|
|
5
|
+
} from "../chunk-VLQYXCE6.js";
|
|
5
6
|
|
|
6
7
|
// src/hooks/handle.ts
|
|
7
8
|
import { DEV } from "esm-env";
|
|
@@ -42,17 +43,38 @@ async function postIngest(origin, route, results, failedRuleIds) {
|
|
|
42
43
|
}
|
|
43
44
|
async function analyzeAndIngest(html, route, origin, rules, config, lastSignature) {
|
|
44
45
|
try {
|
|
45
|
-
const {
|
|
46
|
+
const {
|
|
47
|
+
tags,
|
|
48
|
+
htmlLang,
|
|
49
|
+
headings: levels,
|
|
50
|
+
images: imgs,
|
|
51
|
+
landmarks,
|
|
52
|
+
nestedLandmarks,
|
|
53
|
+
ids,
|
|
54
|
+
idRefs
|
|
55
|
+
} = parseHtmlHead(html);
|
|
46
56
|
const head = { route, source: "rendered", tags, file: route };
|
|
47
57
|
const headings = [
|
|
48
58
|
{ route, headings: levels.map((level) => ({ level, line: 0, file: route })) }
|
|
49
59
|
];
|
|
50
60
|
const images = [{ route, images: imgs.map((img) => ({ ...img, file: route })) }];
|
|
61
|
+
const a11y = [
|
|
62
|
+
{
|
|
63
|
+
route,
|
|
64
|
+
landmarks: toOccurrenceMap(landmarks, route),
|
|
65
|
+
nestedLandmarks: nestedLandmarks.map((n) => ({ ...n, file: route, line: 0 })),
|
|
66
|
+
ids: toOccurrenceMap(ids, route),
|
|
67
|
+
idRefs: idRefs.map((r) => ({ ...r, file: route, line: 0 })),
|
|
68
|
+
idCandidates: [...new Set(ids)],
|
|
69
|
+
fullyResolved: true
|
|
70
|
+
}
|
|
71
|
+
];
|
|
51
72
|
const project = { hasRobotsTxt: true, hasSitemap: true, htmlLang };
|
|
52
73
|
const { results: ruleResults, failedRules } = await runRules(rules, {
|
|
53
74
|
heads: [head],
|
|
54
75
|
headings,
|
|
55
76
|
images,
|
|
77
|
+
a11y,
|
|
56
78
|
project,
|
|
57
79
|
config
|
|
58
80
|
});
|
|
@@ -84,15 +106,8 @@ function svelteVitalsHandle(options = {}) {
|
|
|
84
106
|
return resolve(event, {
|
|
85
107
|
transformPageChunk: ({ html, done }) => {
|
|
86
108
|
buffer += html;
|
|
87
|
-
if (done)
|
|
88
|
-
void analyzeAndIngest(
|
|
89
|
-
buffer,
|
|
90
|
-
event.route.id ?? event.url.pathname,
|
|
91
|
-
event.url.origin,
|
|
92
|
-
rules,
|
|
93
|
-
config,
|
|
94
|
-
lastSignature
|
|
95
|
-
);
|
|
109
|
+
if (done && event.route.id != null)
|
|
110
|
+
void analyzeAndIngest(buffer, event.route.id, event.url.origin, rules, config, lastSignature);
|
|
96
111
|
return html;
|
|
97
112
|
}
|
|
98
113
|
});
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from "./chunk-
|
|
2
|
+
collectRenderedHeads,
|
|
3
|
+
isLoopbackOrigin
|
|
4
|
+
} from "./chunk-VLQYXCE6.js";
|
|
5
5
|
|
|
6
6
|
// src/plugin.ts
|
|
7
7
|
import { existsSync } from "fs";
|
|
8
8
|
import { writeFile, mkdir } from "fs/promises";
|
|
9
|
-
import { join as
|
|
9
|
+
import { join as join3, isAbsolute, dirname, relative as relative3, basename, sep as sep2 } from "path";
|
|
10
10
|
import {
|
|
11
11
|
CATEGORIES as CATEGORIES2,
|
|
12
12
|
defaultConfig,
|
|
@@ -35,47 +35,9 @@ import {
|
|
|
35
35
|
} from "@svelte-vitals/core";
|
|
36
36
|
import { loadConfigFile } from "svelte-vitals";
|
|
37
37
|
|
|
38
|
-
// src/providers/rendered/collect.ts
|
|
39
|
-
import { readFile } from "fs/promises";
|
|
40
|
-
import { join } from "path";
|
|
41
|
-
import { glob } from "tinyglobby";
|
|
42
|
-
function deriveRouteFromHtmlPath(relPath) {
|
|
43
|
-
let p = relPath.replace(/\\/g, "/").replace(/\.html$/, "");
|
|
44
|
-
if (p === "index") return "/";
|
|
45
|
-
if (p.endsWith("/index")) p = p.slice(0, -"/index".length);
|
|
46
|
-
return "/" + p;
|
|
47
|
-
}
|
|
48
|
-
async function collectRenderedHeads(prerenderPagesDir) {
|
|
49
|
-
const files = (await glob("**/*.html", { cwd: prerenderPagesDir })).sort();
|
|
50
|
-
const parsedFiles = await Promise.all(
|
|
51
|
-
files.map(async (rel) => ({ rel, parsed: parseHtmlHead(await readFile(join(prerenderPagesDir, rel), "utf8")) }))
|
|
52
|
-
);
|
|
53
|
-
const heads = [];
|
|
54
|
-
const headings = [];
|
|
55
|
-
const images = [];
|
|
56
|
-
let htmlLang = { presence: "none", value: "absent" };
|
|
57
|
-
for (const { rel, parsed } of parsedFiles) {
|
|
58
|
-
if (htmlLang.presence === "none" && parsed.htmlLang.presence === "own") htmlLang = parsed.htmlLang;
|
|
59
|
-
const route = deriveRouteFromHtmlPath(rel);
|
|
60
|
-
heads.push({
|
|
61
|
-
route,
|
|
62
|
-
source: "rendered",
|
|
63
|
-
tags: parsed.tags,
|
|
64
|
-
file: rel
|
|
65
|
-
});
|
|
66
|
-
headings.push({
|
|
67
|
-
route,
|
|
68
|
-
// Rendered mode does not track source lines (line 0 = unknown); file is the HTML path.
|
|
69
|
-
headings: parsed.headings.map((level) => ({ level, line: 0, file: rel }))
|
|
70
|
-
});
|
|
71
|
-
images.push({ route, images: parsed.images.map((img) => ({ ...img, file: rel })) });
|
|
72
|
-
}
|
|
73
|
-
return { heads, headings, images, htmlLang };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
38
|
// src/providers/rendered/project.ts
|
|
77
|
-
import { access, readFile
|
|
78
|
-
import { join
|
|
39
|
+
import { access, readFile } from "fs/promises";
|
|
40
|
+
import { join } from "path";
|
|
79
41
|
import {
|
|
80
42
|
ROBOTS_SOURCE_PATHS,
|
|
81
43
|
SITEMAP_SOURCE_PATHS,
|
|
@@ -93,22 +55,22 @@ async function exists(path) {
|
|
|
93
55
|
}
|
|
94
56
|
}
|
|
95
57
|
async function existsAny(cwd, paths) {
|
|
96
|
-
const found = await Promise.all(paths.map((p) => exists(
|
|
58
|
+
const found = await Promise.all(paths.map((p) => exists(join(cwd, p))));
|
|
97
59
|
return found.some(Boolean);
|
|
98
60
|
}
|
|
99
61
|
async function robotsRefsSitemap(cwd) {
|
|
100
62
|
try {
|
|
101
|
-
return /^\s*sitemap:/im.test(await
|
|
63
|
+
return /^\s*sitemap:/im.test(await readFile(join(cwd, "static/robots.txt"), "utf8"));
|
|
102
64
|
} catch {
|
|
103
65
|
return void 0;
|
|
104
66
|
}
|
|
105
67
|
}
|
|
106
68
|
async function readFirstConfig(cwd, files) {
|
|
107
69
|
for (const file of files) {
|
|
108
|
-
const path =
|
|
70
|
+
const path = join(cwd, file);
|
|
109
71
|
if (!await exists(path)) continue;
|
|
110
72
|
try {
|
|
111
|
-
return { file, source: await
|
|
73
|
+
return { file, source: await readFile(path, "utf8") };
|
|
112
74
|
} catch {
|
|
113
75
|
return void 0;
|
|
114
76
|
}
|
|
@@ -136,16 +98,16 @@ async function collectRenderedProject(cwd, htmlLang) {
|
|
|
136
98
|
}
|
|
137
99
|
|
|
138
100
|
// src/providers/source/components.ts
|
|
139
|
-
import { readFile as
|
|
140
|
-
import { join as
|
|
141
|
-
import { glob
|
|
101
|
+
import { readFile as readFile2, access as access2 } from "fs/promises";
|
|
102
|
+
import { join as join2 } from "path";
|
|
103
|
+
import { glob } from "tinyglobby";
|
|
142
104
|
import {
|
|
143
105
|
collectComponentFacts as collect,
|
|
144
106
|
collectKitModuleFacts as collectKit,
|
|
145
107
|
collectSourceFiles as collectFiles
|
|
146
108
|
} from "@svelte-vitals/core";
|
|
147
109
|
var nodeRuntime = {
|
|
148
|
-
readFile: (path) =>
|
|
110
|
+
readFile: (path) => readFile2(path, "utf8"),
|
|
149
111
|
async exists(path) {
|
|
150
112
|
try {
|
|
151
113
|
await access2(path);
|
|
@@ -154,8 +116,8 @@ var nodeRuntime = {
|
|
|
154
116
|
return false;
|
|
155
117
|
}
|
|
156
118
|
},
|
|
157
|
-
glob: (pattern, cwd) =>
|
|
158
|
-
join: (...parts) =>
|
|
119
|
+
glob: (pattern, cwd) => glob(pattern, { cwd, dot: false }),
|
|
120
|
+
join: (...parts) => join2(...parts)
|
|
159
121
|
};
|
|
160
122
|
function collectComponentFacts(root) {
|
|
161
123
|
return collect(nodeRuntime, root);
|
|
@@ -207,7 +169,7 @@ async function resolveConfig(cwd, options) {
|
|
|
207
169
|
}
|
|
208
170
|
async function analyze(prerenderPagesDir, cwd, options, extraProjectFacts, resolved) {
|
|
209
171
|
const { config, warnings } = resolved ?? await resolveConfig(cwd, options);
|
|
210
|
-
const [{ heads, headings, images, htmlLang }, components, sourceFiles] = await Promise.all([
|
|
172
|
+
const [{ heads, headings, images, a11y, htmlLang }, components, sourceFiles] = await Promise.all([
|
|
211
173
|
collectRenderedHeads(prerenderPagesDir),
|
|
212
174
|
collectComponentFacts(cwd),
|
|
213
175
|
collectSourceFiles(cwd)
|
|
@@ -226,6 +188,7 @@ async function analyze(prerenderPagesDir, cwd, options, extraProjectFacts, resol
|
|
|
226
188
|
heads,
|
|
227
189
|
headings,
|
|
228
190
|
images,
|
|
191
|
+
a11y,
|
|
229
192
|
project,
|
|
230
193
|
components,
|
|
231
194
|
config,
|
|
@@ -242,7 +205,7 @@ async function analyze(prerenderPagesDir, cwd, options, extraProjectFacts, resol
|
|
|
242
205
|
const summary = summarize(results, scoringConfig);
|
|
243
206
|
const failed = hasFailureAtOrAbove(summary, scoringConfig.failOn);
|
|
244
207
|
const coverageNote = `Analyzed ${heads.length} prerendered route(s). SSR/dynamic routes are not covered \u2014 run \`npx svelte-vitals\` for those.
|
|
245
|
-
Scanned ${components.length} component(s) under src/ for Correctness/Security/Architecture/Bundle findings.`;
|
|
208
|
+
Scanned ${components.length} component(s) under src/ for Correctness/Security/Architecture/Accessibility/Bundle findings.`;
|
|
246
209
|
const consoleReport = formatConsoleReport(results, scoringConfig, { mode: "rendered / plugin" }) + "\n" + coverageNote + "\n";
|
|
247
210
|
const jsonReport = formatJsonReport(
|
|
248
211
|
results,
|
|
@@ -265,7 +228,7 @@ Scanned ${components.length} component(s) under src/ for Correctness/Security/Ar
|
|
|
265
228
|
}
|
|
266
229
|
|
|
267
230
|
// src/minify-flag.ts
|
|
268
|
-
import { readFile as
|
|
231
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
269
232
|
import { relative } from "path";
|
|
270
233
|
import { findMinifyDisabled } from "@svelte-vitals/core";
|
|
271
234
|
async function resolveMinifyDisabled(minify, configFile, root) {
|
|
@@ -275,7 +238,7 @@ async function resolveMinifyDisabled(minify, configFile, root) {
|
|
|
275
238
|
const file = rel === "" ? configFile.split("\\").join("/") : rel;
|
|
276
239
|
let line;
|
|
277
240
|
try {
|
|
278
|
-
line = findMinifyDisabled(await
|
|
241
|
+
line = findMinifyDisabled(await readFile3(configFile, "utf8"))?.line;
|
|
279
242
|
} catch {
|
|
280
243
|
}
|
|
281
244
|
return { file, ...line !== void 0 ? { line } : {} };
|
|
@@ -636,8 +599,8 @@ function svelteVitals(options = {}) {
|
|
|
636
599
|
if (!config.build.ssr) minifyFlag = { minify: config.build.minify, configFile: config.configFile };
|
|
637
600
|
},
|
|
638
601
|
async closeBundle() {
|
|
639
|
-
const pagesDir = options.prerenderDir ? options.prerenderDir :
|
|
640
|
-
const resolved = isAbsolute(pagesDir) ? pagesDir :
|
|
602
|
+
const pagesDir = options.prerenderDir ? options.prerenderDir : join3(root, DEFAULT_PRERENDER_DIR);
|
|
603
|
+
const resolved = isAbsolute(pagesDir) ? pagesDir : join3(root, pagesDir);
|
|
641
604
|
if (!existsSync(resolved)) return;
|
|
642
605
|
const resolvedConfig = await resolveConfig(root, options);
|
|
643
606
|
let result;
|
|
@@ -661,7 +624,7 @@ function svelteVitals(options = {}) {
|
|
|
661
624
|
console.log(out);
|
|
662
625
|
}
|
|
663
626
|
if (options.outFile) {
|
|
664
|
-
const outPath = isAbsolute(options.outFile) ? options.outFile :
|
|
627
|
+
const outPath = isAbsolute(options.outFile) ? options.outFile : join3(root, options.outFile);
|
|
665
628
|
await mkdir(dirname(outPath), { recursive: true });
|
|
666
629
|
await writeFile(outPath, result.jsonReport);
|
|
667
630
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svelte-vitals/vite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"description": "Vite/SvelteKit plugin for svelte-vitals — analyzes prerendered HTML during vite build, plus a live dev dashboard.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"esm-env": "^1.2.2",
|
|
43
43
|
"node-html-parser": "^9.0.1",
|
|
44
44
|
"tinyglobby": "^0.2.17",
|
|
45
|
-
"@svelte-vitals/core": "0.
|
|
46
|
-
"svelte-vitals": "0.
|
|
45
|
+
"@svelte-vitals/core": "0.43.0",
|
|
46
|
+
"svelte-vitals": "0.47.0"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"@sveltejs/kit": "^2.70.2",
|