@jay-framework/seo-validator 0.20.0 → 0.22.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.js +37 -0
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { walkElements } from "@jay-framework/compiler-shared";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
2
4
|
const validate = (ctx) => {
|
|
3
5
|
const findings = [];
|
|
4
6
|
let hasH1 = false;
|
|
@@ -123,6 +125,41 @@ const validate = (ctx) => {
|
|
|
123
125
|
attribute: "fetchpriority"
|
|
124
126
|
});
|
|
125
127
|
}
|
|
128
|
+
const cssSources = [];
|
|
129
|
+
const styleBlocks = ctx.body.querySelectorAll?.("style") ?? [];
|
|
130
|
+
for (const styleEl of styleBlocks) {
|
|
131
|
+
const cssText = styleEl.textContent || "";
|
|
132
|
+
if (cssText)
|
|
133
|
+
cssSources.push({ css: cssText, source: "<style>" });
|
|
134
|
+
}
|
|
135
|
+
const linkedFiles = ctx.body.querySelectorAll?.('link[rel="stylesheet"]') ?? [];
|
|
136
|
+
for (const link of linkedFiles) {
|
|
137
|
+
const href = link.getAttribute?.("href");
|
|
138
|
+
if (href && !href.startsWith("http")) {
|
|
139
|
+
try {
|
|
140
|
+
const dir = path.dirname(path.resolve(ctx.projectRoot, ctx.filePath));
|
|
141
|
+
const cssPath = path.resolve(dir, href);
|
|
142
|
+
const cssText = fs.readFileSync(cssPath, "utf-8");
|
|
143
|
+
cssSources.push({ css: cssText, source: href });
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
for (const { css, source } of cssSources) {
|
|
149
|
+
const importRegex = /@import\s+(?:url\(\s*['"]?([^'")]+)['"]?\s*\)|['"]([^'"]+)['"])/g;
|
|
150
|
+
let importMatch;
|
|
151
|
+
while ((importMatch = importRegex.exec(css)) !== null) {
|
|
152
|
+
const url = importMatch[1] || importMatch[2];
|
|
153
|
+
if (url.startsWith("https://") || url.startsWith("http://")) {
|
|
154
|
+
findings.push({
|
|
155
|
+
severity: "warning",
|
|
156
|
+
message: `CSS @import of external URL "${url}" creates a chained blocking request that delays page rendering`,
|
|
157
|
+
suggestion: 'Move this to a <link rel="stylesheet" href="..."> tag in the HTML <head> instead. This allows the browser preload scanner to discover both resources in parallel.',
|
|
158
|
+
element: source === "<style>" ? "<style>" : `<link href="${source}">`
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
126
163
|
const componentHeadTags = new Set(
|
|
127
164
|
ctx.headlessImports.flatMap((imp) => imp.providedHeadTags ?? [])
|
|
128
165
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jay-framework/seo-validator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "SEO validation plugin for Jay Framework — checks jay-html templates for SEO best practices",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
"test": "vitest run"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@jay-framework/compiler-shared": "^0.
|
|
27
|
+
"@jay-framework/compiler-shared": "^0.22.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@jay-framework/dev-environment": "^0.
|
|
31
|
-
"@jay-framework/jay-stack-cli": "^0.
|
|
30
|
+
"@jay-framework/dev-environment": "^0.22.0",
|
|
31
|
+
"@jay-framework/jay-stack-cli": "^0.22.0",
|
|
32
32
|
"@types/node": "^22.15.21",
|
|
33
33
|
"node-html-parser": "^6.1.0",
|
|
34
34
|
"rimraf": "^5.0.5",
|