recker 1.0.43 → 1.0.44
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 +47 -0
- package/dist/bin/recker-linux-x64 +0 -0
- package/dist/bin/recker-macos-x64 +0 -0
- package/dist/bin/recker-win-x64.exe +0 -0
- package/dist/bin/rek.cjs +85152 -100207
- package/dist/browser/ai/adaptive-timeout.d.ts +50 -0
- package/dist/browser/ai/adaptive-timeout.js +208 -0
- package/dist/browser/ai/client.d.ts +22 -0
- package/dist/browser/ai/client.js +294 -0
- package/dist/browser/ai/index.d.ts +14 -0
- package/dist/browser/ai/index.js +11 -0
- package/dist/browser/ai/providers/anthropic.d.ts +63 -0
- package/dist/browser/ai/providers/anthropic.js +370 -0
- package/dist/browser/ai/providers/base.d.ts +48 -0
- package/dist/browser/ai/providers/base.js +150 -0
- package/dist/browser/ai/providers/google.d.ts +59 -0
- package/dist/browser/ai/providers/google.js +305 -0
- package/dist/browser/ai/providers/ollama.d.ts +44 -0
- package/dist/browser/ai/providers/ollama.js +240 -0
- package/dist/browser/ai/providers/openai.d.ts +64 -0
- package/dist/browser/ai/providers/openai.js +298 -0
- package/dist/browser/ai/rate-limiter.d.ts +43 -0
- package/dist/browser/ai/rate-limiter.js +215 -0
- package/dist/browser/ai/vector/index.d.ts +2 -0
- package/dist/browser/ai/vector/index.js +2 -0
- package/dist/browser/ai/vector/similarity.d.ts +2 -0
- package/dist/browser/ai/vector/similarity.js +27 -0
- package/dist/browser/ai/vector/store.d.ts +27 -0
- package/dist/browser/ai/vector/store.js +82 -0
- package/dist/browser/browser/cache.d.ts +2 -40
- package/dist/browser/browser/cache.js +2 -199
- package/dist/browser/browser/index.d.ts +8 -0
- package/dist/browser/browser/index.js +8 -0
- package/dist/browser/browser/recker.d.ts +8 -1
- package/dist/browser/browser/recker.js +8 -2
- package/dist/browser/cache/indexed-db.d.ts +10 -0
- package/dist/browser/cache/indexed-db.js +88 -0
- package/dist/browser/cache/service-worker-cache.d.ts +18 -0
- package/dist/browser/cache/service-worker-cache.js +103 -0
- package/dist/browser/cache.d.ts +2 -40
- package/dist/browser/cache.js +2 -199
- package/dist/browser/constants/user-agents.d.ts +7 -0
- package/dist/browser/constants/user-agents.js +7 -0
- package/dist/browser/core/client.d.ts +2 -0
- package/dist/browser/core/client.js +19 -1
- package/dist/browser/index.d.ts +8 -0
- package/dist/browser/index.js +8 -0
- package/dist/browser/plugins/har-recorder.d.ts +40 -0
- package/dist/browser/plugins/har-recorder.js +120 -0
- package/dist/browser/plugins/network-simulation.d.ts +7 -0
- package/dist/browser/plugins/network-simulation.js +13 -0
- package/dist/browser/presets/android.d.ts +2 -0
- package/dist/browser/presets/android.js +16 -0
- package/dist/browser/presets/anthropic.d.ts +8 -0
- package/dist/browser/presets/anthropic.js +27 -0
- package/dist/browser/presets/aws.d.ts +19 -0
- package/dist/browser/presets/aws.js +68 -0
- package/dist/browser/presets/azure-openai.d.ts +10 -0
- package/dist/browser/presets/azure-openai.js +35 -0
- package/dist/browser/presets/azure.d.ts +41 -0
- package/dist/browser/presets/azure.js +104 -0
- package/dist/browser/presets/chaturbate.d.ts +2 -0
- package/dist/browser/presets/chaturbate.js +17 -0
- package/dist/browser/presets/cloudflare.d.ts +12 -0
- package/dist/browser/presets/cloudflare.js +39 -0
- package/dist/browser/presets/cohere.d.ts +7 -0
- package/dist/browser/presets/cohere.js +22 -0
- package/dist/browser/presets/deepseek.d.ts +7 -0
- package/dist/browser/presets/deepseek.js +22 -0
- package/dist/browser/presets/digitalocean.d.ts +5 -0
- package/dist/browser/presets/digitalocean.js +16 -0
- package/dist/browser/presets/discord.d.ts +6 -0
- package/dist/browser/presets/discord.js +17 -0
- package/dist/browser/presets/elevenlabs.d.ts +6 -0
- package/dist/browser/presets/elevenlabs.js +20 -0
- package/dist/browser/presets/enhancers.d.ts +20 -0
- package/dist/browser/presets/enhancers.js +85 -0
- package/dist/browser/presets/fireworks.d.ts +7 -0
- package/dist/browser/presets/fireworks.js +22 -0
- package/dist/browser/presets/gcp.d.ts +34 -0
- package/dist/browser/presets/gcp.js +91 -0
- package/dist/browser/presets/gemini.d.ts +7 -0
- package/dist/browser/presets/gemini.js +23 -0
- package/dist/browser/presets/github.d.ts +6 -0
- package/dist/browser/presets/github.js +17 -0
- package/dist/browser/presets/gitlab.d.ts +6 -0
- package/dist/browser/presets/gitlab.js +16 -0
- package/dist/browser/presets/groq.d.ts +7 -0
- package/dist/browser/presets/groq.js +22 -0
- package/dist/browser/presets/hubspot.d.ts +9 -0
- package/dist/browser/presets/hubspot.js +28 -0
- package/dist/browser/presets/huggingface.d.ts +7 -0
- package/dist/browser/presets/huggingface.js +23 -0
- package/dist/browser/presets/index.d.ts +47 -0
- package/dist/browser/presets/index.js +47 -0
- package/dist/browser/presets/ios.d.ts +2 -0
- package/dist/browser/presets/ios.js +13 -0
- package/dist/browser/presets/linear.d.ts +5 -0
- package/dist/browser/presets/linear.js +16 -0
- package/dist/browser/presets/mailgun.d.ts +7 -0
- package/dist/browser/presets/mailgun.js +20 -0
- package/dist/browser/presets/meta.d.ts +10 -0
- package/dist/browser/presets/meta.js +33 -0
- package/dist/browser/presets/mistral.d.ts +7 -0
- package/dist/browser/presets/mistral.js +22 -0
- package/dist/browser/presets/notion.d.ts +6 -0
- package/dist/browser/presets/notion.js +17 -0
- package/dist/browser/presets/openai.d.ts +9 -0
- package/dist/browser/presets/openai.js +30 -0
- package/dist/browser/presets/oracle.d.ts +19 -0
- package/dist/browser/presets/oracle.js +117 -0
- package/dist/browser/presets/perplexity.d.ts +7 -0
- package/dist/browser/presets/perplexity.js +22 -0
- package/dist/browser/presets/pinecone.d.ts +8 -0
- package/dist/browser/presets/pinecone.js +42 -0
- package/dist/browser/presets/registry.d.ts +23 -0
- package/dist/browser/presets/registry.js +519 -0
- package/dist/browser/presets/replicate.d.ts +7 -0
- package/dist/browser/presets/replicate.js +23 -0
- package/dist/browser/presets/sendgrid.d.ts +6 -0
- package/dist/browser/presets/sendgrid.js +20 -0
- package/dist/browser/presets/sentry.d.ts +11 -0
- package/dist/browser/presets/sentry.js +48 -0
- package/dist/browser/presets/sinch.d.ts +9 -0
- package/dist/browser/presets/sinch.js +39 -0
- package/dist/browser/presets/slack.d.ts +5 -0
- package/dist/browser/presets/slack.js +16 -0
- package/dist/browser/presets/square.d.ts +10 -0
- package/dist/browser/presets/square.js +33 -0
- package/dist/browser/presets/stripe.d.ts +7 -0
- package/dist/browser/presets/stripe.js +23 -0
- package/dist/browser/presets/supabase.d.ts +6 -0
- package/dist/browser/presets/supabase.js +18 -0
- package/dist/browser/presets/tiktok.d.ts +10 -0
- package/dist/browser/presets/tiktok.js +38 -0
- package/dist/browser/presets/together.d.ts +7 -0
- package/dist/browser/presets/together.js +22 -0
- package/dist/browser/presets/twilio.d.ts +6 -0
- package/dist/browser/presets/twilio.js +17 -0
- package/dist/browser/presets/vercel.d.ts +6 -0
- package/dist/browser/presets/vercel.js +23 -0
- package/dist/browser/presets/vultr.d.ts +5 -0
- package/dist/browser/presets/vultr.js +16 -0
- package/dist/browser/presets/xai.d.ts +8 -0
- package/dist/browser/presets/xai.js +23 -0
- package/dist/browser/presets/youtube.d.ts +5 -0
- package/dist/browser/presets/youtube.js +20 -0
- package/dist/browser/recker.d.ts +8 -1
- package/dist/browser/recker.js +8 -2
- package/dist/browser/scrape/document.d.ts +5 -4
- package/dist/browser/scrape/document.js +89 -76
- package/dist/browser/scrape/element.d.ts +10 -8
- package/dist/browser/scrape/element.js +295 -81
- package/dist/browser/scrape/extractors.d.ts +11 -11
- package/dist/browser/scrape/extractors.js +145 -113
- package/dist/browser/scrape/parser/back.d.ts +1 -0
- package/dist/browser/scrape/parser/back.js +3 -0
- package/dist/browser/scrape/parser/index.d.ts +20 -0
- package/dist/browser/scrape/parser/index.js +19 -0
- package/dist/browser/scrape/parser/matcher.d.ts +30 -0
- package/dist/browser/scrape/parser/matcher.js +99 -0
- package/dist/browser/scrape/parser/nodes/comment.d.ts +12 -0
- package/dist/browser/scrape/parser/nodes/comment.js +21 -0
- package/dist/browser/scrape/parser/nodes/html.d.ts +110 -0
- package/dist/browser/scrape/parser/nodes/html.js +978 -0
- package/dist/browser/scrape/parser/nodes/node.d.ts +18 -0
- package/dist/browser/scrape/parser/nodes/node.js +31 -0
- package/dist/browser/scrape/parser/nodes/text.d.ts +14 -0
- package/dist/browser/scrape/parser/nodes/text.js +30 -0
- package/dist/browser/scrape/parser/nodes/type.d.ts +6 -0
- package/dist/browser/scrape/parser/nodes/type.js +7 -0
- package/dist/browser/scrape/parser/parse.d.ts +1 -0
- package/dist/browser/scrape/parser/parse.js +1 -0
- package/dist/browser/scrape/parser/valid.d.ts +2 -0
- package/dist/browser/scrape/parser/valid.js +5 -0
- package/dist/browser/scrape/parser/void-tag.d.ts +7 -0
- package/dist/browser/scrape/parser/void-tag.js +43 -0
- package/dist/browser/scrape/types.d.ts +7 -0
- package/dist/browser/seo/analyzer.d.ts +59 -0
- package/dist/browser/seo/analyzer.js +1399 -0
- package/dist/browser/seo/keywords.d.ts +16 -0
- package/dist/browser/seo/keywords.js +55 -0
- package/dist/browser/seo/rules/accessibility.d.ts +2 -0
- package/dist/browser/seo/rules/accessibility.js +733 -0
- package/dist/browser/seo/rules/ai-search.d.ts +2 -0
- package/dist/browser/seo/rules/ai-search.js +436 -0
- package/dist/browser/seo/rules/analytics.d.ts +2 -0
- package/dist/browser/seo/rules/analytics.js +306 -0
- package/dist/browser/seo/rules/best-practices.d.ts +2 -0
- package/dist/browser/seo/rules/best-practices.js +195 -0
- package/dist/browser/seo/rules/canonical.d.ts +12 -0
- package/dist/browser/seo/rules/canonical.js +270 -0
- package/dist/browser/seo/rules/content.d.ts +2 -0
- package/dist/browser/seo/rules/content.js +522 -0
- package/dist/browser/seo/rules/crawl.d.ts +2 -0
- package/dist/browser/seo/rules/crawl.js +435 -0
- package/dist/browser/seo/rules/cwv.d.ts +2 -0
- package/dist/browser/seo/rules/cwv.js +248 -0
- package/dist/browser/seo/rules/ecommerce.d.ts +2 -0
- package/dist/browser/seo/rules/ecommerce.js +312 -0
- package/dist/browser/seo/rules/i18n.d.ts +2 -0
- package/dist/browser/seo/rules/i18n.js +288 -0
- package/dist/browser/seo/rules/images.d.ts +2 -0
- package/dist/browser/seo/rules/images.js +255 -0
- package/dist/browser/seo/rules/index.d.ts +52 -0
- package/dist/browser/seo/rules/index.js +159 -0
- package/dist/browser/seo/rules/internal-linking.d.ts +2 -0
- package/dist/browser/seo/rules/internal-linking.js +394 -0
- package/dist/browser/seo/rules/links.d.ts +2 -0
- package/dist/browser/seo/rules/links.js +498 -0
- package/dist/browser/seo/rules/local.d.ts +2 -0
- package/dist/browser/seo/rules/local.js +289 -0
- package/dist/browser/seo/rules/meta.d.ts +2 -0
- package/dist/browser/seo/rules/meta.js +805 -0
- package/dist/browser/seo/rules/mobile.d.ts +2 -0
- package/dist/browser/seo/rules/mobile.js +161 -0
- package/dist/browser/seo/rules/performance.d.ts +2 -0
- package/dist/browser/seo/rules/performance.js +738 -0
- package/dist/browser/seo/rules/pwa.d.ts +2 -0
- package/dist/browser/seo/rules/pwa.js +299 -0
- package/dist/browser/seo/rules/readability.d.ts +2 -0
- package/dist/browser/seo/rules/readability.js +264 -0
- package/dist/browser/seo/rules/redirects.d.ts +16 -0
- package/dist/browser/seo/rules/redirects.js +199 -0
- package/dist/browser/seo/rules/resources.d.ts +2 -0
- package/dist/browser/seo/rules/resources.js +390 -0
- package/dist/browser/seo/rules/schema.d.ts +2 -0
- package/dist/browser/seo/rules/schema.js +379 -0
- package/dist/browser/seo/rules/security.d.ts +2 -0
- package/dist/browser/seo/rules/security.js +877 -0
- package/dist/browser/seo/rules/social.d.ts +2 -0
- package/dist/browser/seo/rules/social.js +603 -0
- package/dist/browser/seo/rules/structural.d.ts +2 -0
- package/dist/browser/seo/rules/structural.js +223 -0
- package/dist/browser/seo/rules/technical-advanced.d.ts +10 -0
- package/dist/browser/seo/rules/technical-advanced.js +289 -0
- package/dist/browser/seo/rules/technical.d.ts +2 -0
- package/dist/browser/seo/rules/technical.js +480 -0
- package/dist/browser/seo/rules/thresholds.d.ts +196 -0
- package/dist/browser/seo/rules/thresholds.js +118 -0
- package/dist/browser/seo/rules/types.d.ts +498 -0
- package/dist/browser/seo/rules/types.js +11 -0
- package/dist/browser/seo/types.d.ts +211 -0
- package/dist/browser/seo/types.js +1 -0
- package/dist/browser/transport/curl.d.ts +4 -0
- package/dist/browser/transport/curl.js +101 -0
- package/dist/browser/transport/undici.js +1 -2
- package/dist/browser/transport/worker.d.ts +18 -0
- package/dist/browser/transport/worker.js +278 -0
- package/dist/browser/types/index.d.ts +4 -1
- package/dist/browser/utils/binary-manager.d.ts +4 -0
- package/dist/browser/utils/binary-manager.js +72 -0
- package/dist/browser/utils/user-agent.js +2 -13
- package/dist/cache/indexed-db.d.ts +10 -0
- package/dist/cache/indexed-db.js +88 -0
- package/dist/cache/service-worker-cache.d.ts +18 -0
- package/dist/cache/service-worker-cache.js +103 -0
- package/dist/cli/commands/ai.d.ts +2 -0
- package/dist/cli/commands/ai.js +162 -0
- package/dist/cli/commands/bench.d.ts +2 -0
- package/dist/cli/commands/bench.js +51 -0
- package/dist/cli/commands/dns.d.ts +2 -0
- package/dist/cli/commands/dns.js +295 -0
- package/dist/cli/commands/har.d.ts +2 -0
- package/dist/cli/commands/har.js +171 -0
- package/dist/cli/commands/hls.d.ts +2 -0
- package/dist/cli/commands/hls.js +192 -0
- package/dist/cli/commands/network.d.ts +2 -0
- package/dist/cli/commands/network.js +288 -0
- package/dist/cli/commands/protocols.d.ts +2 -0
- package/dist/cli/commands/protocols.js +344 -0
- package/dist/cli/commands/scrape.d.ts +2 -0
- package/dist/cli/commands/scrape.js +176 -0
- package/dist/cli/commands/security.d.ts +2 -0
- package/dist/cli/commands/security.js +57 -0
- package/dist/cli/commands/seo.d.ts +2 -0
- package/dist/cli/commands/seo.js +125 -0
- package/dist/cli/commands/serve.d.ts +2 -0
- package/dist/cli/commands/serve.js +531 -0
- package/dist/cli/commands/spider.d.ts +3 -0
- package/dist/cli/commands/spider.js +456 -0
- package/dist/cli/commands/utils.d.ts +2 -0
- package/dist/cli/commands/utils.js +176 -0
- package/dist/cli/commands/vector.d.ts +2 -0
- package/dist/cli/commands/vector.js +158 -0
- package/dist/cli/handler.d.ts +2 -2
- package/dist/cli/handler.js +6 -6
- package/dist/cli/helpers.d.ts +7 -0
- package/dist/cli/helpers.js +128 -0
- package/dist/cli/index.js +96 -5228
- package/dist/cli/parser/help.d.ts +2 -0
- package/dist/cli/parser/help.js +52 -0
- package/dist/cli/parser/index.d.ts +3 -0
- package/dist/cli/parser/index.js +3 -0
- package/dist/cli/parser/parser.d.ts +4 -0
- package/dist/cli/parser/parser.js +146 -0
- package/dist/cli/parser/types.d.ts +41 -0
- package/dist/cli/parser/types.js +1 -0
- package/dist/cli/presets.d.ts +1 -1
- package/dist/cli/presets.js +1 -1
- package/dist/cli/router.d.ts +36 -0
- package/dist/cli/router.js +195 -0
- package/dist/cli/tui/ai-chat.js +1 -1
- package/dist/cli/tui/commands/context.d.ts +9 -0
- package/dist/cli/tui/commands/context.js +1 -0
- package/dist/cli/tui/commands/dns.d.ts +10 -0
- package/dist/cli/tui/commands/dns.js +461 -0
- package/dist/cli/tui/commands/hls.d.ts +2 -0
- package/dist/cli/tui/commands/hls.js +162 -0
- package/dist/cli/tui/commands/ip.d.ts +2 -0
- package/dist/cli/tui/commands/ip.js +45 -0
- package/dist/cli/tui/commands/network.d.ts +3 -0
- package/dist/cli/tui/commands/network.js +81 -0
- package/dist/cli/tui/commands/protocols.d.ts +6 -0
- package/dist/cli/tui/commands/protocols.js +531 -0
- package/dist/cli/tui/commands/security.d.ts +2 -0
- package/dist/cli/tui/commands/security.js +48 -0
- package/dist/cli/tui/commands/seo.d.ts +2 -0
- package/dist/cli/tui/commands/seo.js +74 -0
- package/dist/cli/tui/context.d.ts +12 -0
- package/dist/cli/tui/context.js +1 -0
- package/dist/cli/tui/shell.d.ts +11 -20
- package/dist/cli/tui/shell.js +216 -1873
- package/dist/constants/user-agents.d.ts +7 -0
- package/dist/constants/user-agents.js +7 -0
- package/dist/core/client.d.ts +2 -0
- package/dist/core/client.js +19 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp/cli.js +2 -3
- package/dist/mcp/data/embeddings.json +1 -1
- package/dist/mcp/tools/network.js +298 -158
- package/dist/plugins/har-player.d.ts +23 -0
- package/dist/plugins/har-player.js +49 -0
- package/dist/plugins/har-recorder.d.ts +37 -3
- package/dist/plugins/har-recorder.js +116 -63
- package/dist/plugins/network-simulation.d.ts +7 -0
- package/dist/plugins/network-simulation.js +13 -0
- package/dist/presets/android.d.ts +2 -0
- package/dist/presets/android.js +16 -0
- package/dist/presets/chaturbate.d.ts +2 -0
- package/dist/presets/chaturbate.js +17 -0
- package/dist/presets/elevenlabs.d.ts +6 -0
- package/dist/presets/elevenlabs.js +20 -0
- package/dist/presets/enhancers.d.ts +20 -0
- package/dist/presets/enhancers.js +85 -0
- package/dist/presets/hubspot.d.ts +9 -0
- package/dist/presets/hubspot.js +28 -0
- package/dist/presets/index.d.ts +10 -0
- package/dist/presets/index.js +10 -0
- package/dist/presets/ios.d.ts +2 -0
- package/dist/presets/ios.js +13 -0
- package/dist/presets/pinecone.d.ts +8 -0
- package/dist/presets/pinecone.js +42 -0
- package/dist/presets/registry.js +60 -0
- package/dist/presets/sendgrid.d.ts +6 -0
- package/dist/presets/sendgrid.js +20 -0
- package/dist/presets/sentry.d.ts +11 -0
- package/dist/presets/sentry.js +48 -0
- package/dist/presets/square.d.ts +10 -0
- package/dist/presets/square.js +33 -0
- package/dist/recker.d.ts +3 -0
- package/dist/recker.js +4 -0
- package/dist/scrape/document.d.ts +5 -4
- package/dist/scrape/document.js +89 -76
- package/dist/scrape/element.d.ts +10 -8
- package/dist/scrape/element.js +295 -81
- package/dist/scrape/extractors.d.ts +11 -11
- package/dist/scrape/extractors.js +145 -113
- package/dist/scrape/index.d.ts +2 -0
- package/dist/scrape/index.js +1 -0
- package/dist/scrape/parser/back.d.ts +1 -0
- package/dist/scrape/parser/back.js +3 -0
- package/dist/scrape/parser/index.d.ts +20 -0
- package/dist/scrape/parser/index.js +19 -0
- package/dist/scrape/parser/matcher.d.ts +30 -0
- package/dist/scrape/parser/matcher.js +99 -0
- package/dist/scrape/parser/nodes/comment.d.ts +12 -0
- package/dist/scrape/parser/nodes/comment.js +21 -0
- package/dist/scrape/parser/nodes/html.d.ts +110 -0
- package/dist/scrape/parser/nodes/html.js +978 -0
- package/dist/scrape/parser/nodes/node.d.ts +18 -0
- package/dist/scrape/parser/nodes/node.js +31 -0
- package/dist/scrape/parser/nodes/text.d.ts +14 -0
- package/dist/scrape/parser/nodes/text.js +30 -0
- package/dist/scrape/parser/nodes/type.d.ts +6 -0
- package/dist/scrape/parser/nodes/type.js +7 -0
- package/dist/scrape/parser/parse.d.ts +1 -0
- package/dist/scrape/parser/parse.js +1 -0
- package/dist/scrape/parser/valid.d.ts +2 -0
- package/dist/scrape/parser/valid.js +5 -0
- package/dist/scrape/parser/void-tag.d.ts +7 -0
- package/dist/scrape/parser/void-tag.js +43 -0
- package/dist/scrape/spider.d.ts +19 -0
- package/dist/scrape/spider.js +28 -3
- package/dist/scrape/types.d.ts +7 -0
- package/dist/seo/analyzer.d.ts +15 -5
- package/dist/seo/analyzer.js +636 -175
- package/dist/seo/formatter.d.ts +16 -0
- package/dist/seo/formatter.js +228 -0
- package/dist/seo/index.d.ts +2 -0
- package/dist/seo/index.js +1 -0
- package/dist/seo/keywords.d.ts +16 -0
- package/dist/seo/keywords.js +55 -0
- package/dist/seo/rules/accessibility.js +96 -57
- package/dist/seo/rules/ai-search.js +44 -31
- package/dist/seo/rules/analytics.d.ts +2 -0
- package/dist/seo/rules/analytics.js +306 -0
- package/dist/seo/rules/best-practices.js +21 -14
- package/dist/seo/rules/canonical.js +53 -32
- package/dist/seo/rules/content.js +317 -31
- package/dist/seo/rules/crawl.js +55 -40
- package/dist/seo/rules/cwv.js +21 -15
- package/dist/seo/rules/ecommerce.js +82 -22
- package/dist/seo/rules/i18n.js +75 -36
- package/dist/seo/rules/images.js +109 -30
- package/dist/seo/rules/index.js +2 -0
- package/dist/seo/rules/internal-linking.js +58 -39
- package/dist/seo/rules/links.js +79 -52
- package/dist/seo/rules/local.js +49 -25
- package/dist/seo/rules/meta.js +339 -81
- package/dist/seo/rules/mobile.js +112 -2
- package/dist/seo/rules/performance.js +434 -66
- package/dist/seo/rules/pwa.js +36 -39
- package/dist/seo/rules/readability.js +31 -22
- package/dist/seo/rules/redirects.js +21 -15
- package/dist/seo/rules/resources.js +59 -42
- package/dist/seo/rules/schema.js +333 -8
- package/dist/seo/rules/security.js +142 -80
- package/dist/seo/rules/social.js +277 -47
- package/dist/seo/rules/structural.js +87 -19
- package/dist/seo/rules/technical-advanced.js +30 -24
- package/dist/seo/rules/technical.js +243 -42
- package/dist/seo/rules/types.d.ts +53 -1
- package/dist/seo/seo-spider.d.ts +22 -0
- package/dist/seo/seo-spider.js +77 -13
- package/dist/seo/types.d.ts +8 -1
- package/dist/seo/validators/llms-txt.js +19 -0
- package/dist/seo/validators/rss.d.ts +11 -0
- package/dist/seo/validators/rss.js +93 -0
- package/dist/seo/validators/sitemap.js +36 -26
- package/dist/transport/curl.d.ts +4 -0
- package/dist/transport/curl.js +101 -0
- package/dist/transport/udp.js +0 -1
- package/dist/transport/undici.js +1 -2
- package/dist/transport/worker.d.ts +18 -0
- package/dist/transport/worker.js +278 -0
- package/dist/types/index.d.ts +4 -1
- package/dist/utils/binary-manager.d.ts +4 -0
- package/dist/utils/binary-manager.js +72 -0
- package/dist/utils/optional-require.d.ts +7 -8
- package/dist/utils/optional-require.js +2 -21
- package/dist/utils/upload.d.ts +6 -0
- package/dist/utils/upload.js +11 -0
- package/dist/utils/user-agent.js +2 -13
- package/dist/version.js +1 -1
- package/package.json +12 -6
- package/dist/browser/utils/optional-require.d.ts +0 -19
- package/dist/browser/utils/optional-require.js +0 -105
|
@@ -14,7 +14,7 @@ export const performanceRules = [
|
|
|
14
14
|
if (ctx.preconnectCount && ctx.preconnectCount > 0) {
|
|
15
15
|
return createResult({ id: 'perf-preconnect', name: 'Preconnect Hints', category: 'performance', severity: 'info' }, 'pass', `${ctx.preconnectCount} preconnect hint(s) found`, { value: ctx.preconnectCount });
|
|
16
16
|
}
|
|
17
|
-
return
|
|
17
|
+
return createResult({ id: 'perf-preconnect', name: 'Preconnect Hints', category: 'performance', severity: 'info' }, 'info', 'Not applicable (no external links or preconnect data unavailable)', { recommendation: 'This rule checks for preconnect hints when external resources are detected' });
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
20
|
{
|
|
@@ -27,7 +27,7 @@ export const performanceRules = [
|
|
|
27
27
|
if (ctx.dnsPrefetchCount && ctx.dnsPrefetchCount > 0) {
|
|
28
28
|
return createResult({ id: 'perf-dns-prefetch', name: 'DNS Prefetch', category: 'performance', severity: 'info' }, 'pass', `${ctx.dnsPrefetchCount} dns-prefetch hint(s) found`, { value: ctx.dnsPrefetchCount });
|
|
29
29
|
}
|
|
30
|
-
return
|
|
30
|
+
return createResult({ id: 'perf-dns-prefetch', name: 'DNS Prefetch', category: 'performance', severity: 'info' }, 'pass', 'No dns-prefetch hints (not required if no external resources)');
|
|
31
31
|
},
|
|
32
32
|
},
|
|
33
33
|
{
|
|
@@ -40,7 +40,7 @@ export const performanceRules = [
|
|
|
40
40
|
if (ctx.preloadCount && ctx.preloadCount > 0) {
|
|
41
41
|
return createResult({ id: 'perf-preload', name: 'Preload Hints', category: 'performance', severity: 'info' }, 'pass', `${ctx.preloadCount} preload hint(s) found`, { value: ctx.preloadCount });
|
|
42
42
|
}
|
|
43
|
-
return
|
|
43
|
+
return createResult({ id: 'perf-preload', name: 'Preload Hints', category: 'performance', severity: 'info' }, 'pass', 'No preload hints (consider adding for critical resources)');
|
|
44
44
|
},
|
|
45
45
|
},
|
|
46
46
|
{
|
|
@@ -52,12 +52,22 @@ export const performanceRules = [
|
|
|
52
52
|
check: (ctx) => {
|
|
53
53
|
const blocking = ctx.renderBlockingResources ?? 0;
|
|
54
54
|
if (blocking > 5) {
|
|
55
|
-
return createResult({ id: 'perf-render-blocking', name: 'Render Blocking', category: 'performance', severity: 'warning' }, 'warn', `${blocking} render-blocking resources in <head>`, {
|
|
55
|
+
return createResult({ id: 'perf-render-blocking', name: 'Render Blocking', category: 'performance', severity: 'warning' }, 'warn', `${blocking} render-blocking resources in <head>`, {
|
|
56
|
+
value: blocking,
|
|
57
|
+
recommendation: 'Use async/defer for scripts, preload for critical CSS',
|
|
58
|
+
evidence: {
|
|
59
|
+
found: `${blocking} render-blocking resources`,
|
|
60
|
+
expected: '≤ 5 render-blocking resources',
|
|
61
|
+
impact: 'Render-blocking resources delay First Contentful Paint (FCP) and Largest Contentful Paint (LCP)',
|
|
62
|
+
example: '<script src="app.js" defer></script> or <link rel="preload" as="style" href="critical.css">',
|
|
63
|
+
learnMore: 'https://web.dev/render-blocking-resources/'
|
|
64
|
+
}
|
|
65
|
+
});
|
|
56
66
|
}
|
|
57
67
|
if (blocking > 0) {
|
|
58
68
|
return createResult({ id: 'perf-render-blocking', name: 'Render Blocking', category: 'performance', severity: 'warning' }, 'info', `${blocking} render-blocking resource(s) in <head>`, { value: blocking });
|
|
59
69
|
}
|
|
60
|
-
return
|
|
70
|
+
return createResult({ id: 'perf-render-blocking', name: 'Render Blocking', category: 'performance', severity: 'warning' }, 'pass', 'No render-blocking resources detected in <head>');
|
|
61
71
|
},
|
|
62
72
|
},
|
|
63
73
|
{
|
|
@@ -71,7 +81,7 @@ export const performanceRules = [
|
|
|
71
81
|
if (inline > 10) {
|
|
72
82
|
return createResult({ id: 'perf-inline-styles', name: 'Inline Styles', category: 'performance', severity: 'info' }, 'info', `${inline} inline style blocks found`, { value: inline, recommendation: 'Consider consolidating inline styles into external CSS' });
|
|
73
83
|
}
|
|
74
|
-
return
|
|
84
|
+
return createResult({ id: 'perf-inline-styles', name: 'Inline Styles', category: 'performance', severity: 'info' }, 'pass', `Inline styles count is acceptable (${inline} blocks)`);
|
|
75
85
|
},
|
|
76
86
|
},
|
|
77
87
|
{
|
|
@@ -82,9 +92,18 @@ export const performanceRules = [
|
|
|
82
92
|
description: 'Above-the-fold images should not use lazy loading',
|
|
83
93
|
check: (ctx) => {
|
|
84
94
|
if (ctx.lcpHints?.hasLazyLcp) {
|
|
85
|
-
return createResult({ id: 'cwv-lcp-lazy', name: 'LCP Image Lazy', category: 'performance', severity: 'warning' }, 'warn', 'First large image uses lazy loading (may hurt LCP)', {
|
|
95
|
+
return createResult({ id: 'cwv-lcp-lazy', name: 'LCP Image Lazy', category: 'performance', severity: 'warning' }, 'warn', 'First large image uses lazy loading (may hurt LCP)', {
|
|
96
|
+
recommendation: 'Remove loading="lazy" from above-the-fold images',
|
|
97
|
+
evidence: {
|
|
98
|
+
found: 'Above-the-fold image with loading="lazy"',
|
|
99
|
+
expected: 'LCP images should not use lazy loading',
|
|
100
|
+
impact: 'Lazy loading delays the Largest Contentful Paint (LCP), hurting Core Web Vitals',
|
|
101
|
+
example: '<img src="hero.jpg" alt="Hero"> <!-- Remove loading="lazy" from hero images -->',
|
|
102
|
+
learnMore: 'https://web.dev/lcp-lazy-loading/'
|
|
103
|
+
}
|
|
104
|
+
});
|
|
86
105
|
}
|
|
87
|
-
return
|
|
106
|
+
return createResult({ id: 'cwv-lcp-lazy', name: 'LCP Image Lazy', category: 'performance', severity: 'warning' }, 'pass', 'No lazy loading detected on LCP images');
|
|
88
107
|
},
|
|
89
108
|
},
|
|
90
109
|
{
|
|
@@ -97,7 +116,7 @@ export const performanceRules = [
|
|
|
97
116
|
if (ctx.lcpHints?.hasLargeImages && !ctx.lcpHints?.hasPriorityHints) {
|
|
98
117
|
return createResult({ id: 'cwv-lcp-priority', name: 'LCP Priority Hints', category: 'performance', severity: 'info' }, 'info', 'No fetchpriority="high" found on large images', { recommendation: 'Add fetchpriority="high" to LCP candidate images' });
|
|
99
118
|
}
|
|
100
|
-
return
|
|
119
|
+
return createResult({ id: 'cwv-lcp-priority', name: 'LCP Priority Hints', category: 'performance', severity: 'info' }, 'pass', 'LCP images have proper priority hints or no large images detected');
|
|
101
120
|
},
|
|
102
121
|
},
|
|
103
122
|
{
|
|
@@ -109,9 +128,19 @@ export const performanceRules = [
|
|
|
109
128
|
check: (ctx) => {
|
|
110
129
|
const missing = ctx.clsHints?.imagesWithoutDimensions ?? ctx.imagesMissingDimensions ?? 0;
|
|
111
130
|
if (missing > 0) {
|
|
112
|
-
return createResult({ id: 'cwv-cls-images', name: 'CLS Image Dimensions', category: 'performance', severity: 'warning' }, 'warn', `${missing} image(s) without width/height (causes CLS)`, {
|
|
131
|
+
return createResult({ id: 'cwv-cls-images', name: 'CLS Image Dimensions', category: 'performance', severity: 'warning' }, 'warn', `${missing} image(s) without width/height (causes CLS)`, {
|
|
132
|
+
value: missing,
|
|
133
|
+
recommendation: 'Add width and height attributes to all images',
|
|
134
|
+
evidence: {
|
|
135
|
+
found: `${missing} images without dimensions`,
|
|
136
|
+
expected: 'All images should have width and height attributes',
|
|
137
|
+
impact: 'Images without dimensions cause layout shifts (CLS) when they load, hurting Core Web Vitals',
|
|
138
|
+
example: '<img src="photo.jpg" alt="Photo" width="800" height="600">',
|
|
139
|
+
learnMore: 'https://web.dev/optimize-cls/'
|
|
140
|
+
}
|
|
141
|
+
});
|
|
113
142
|
}
|
|
114
|
-
return
|
|
143
|
+
return createResult({ id: 'cwv-cls-images', name: 'CLS Image Dimensions', category: 'performance', severity: 'warning' }, 'pass', 'All images have proper dimensions');
|
|
115
144
|
},
|
|
116
145
|
},
|
|
117
146
|
{
|
|
@@ -122,8 +151,9 @@ export const performanceRules = [
|
|
|
122
151
|
description: 'TTFB should be under 600ms (ideally under 200ms)',
|
|
123
152
|
check: (ctx) => {
|
|
124
153
|
const ttfb = ctx.timings?.ttfb;
|
|
125
|
-
if (ttfb === undefined)
|
|
126
|
-
return
|
|
154
|
+
if (ttfb === undefined) {
|
|
155
|
+
return createResult({ id: 'timing-ttfb', name: 'TTFB', category: 'performance', severity: 'error' }, 'info', 'Not applicable (TTFB timing data unavailable)', { recommendation: 'This rule checks Time to First Byte when timing data is available' });
|
|
156
|
+
}
|
|
127
157
|
const { good, needsImprovement, poor } = SEO_THRESHOLDS.timing.ttfb;
|
|
128
158
|
if (ttfb <= good) {
|
|
129
159
|
return createResult({ id: 'timing-ttfb', name: 'TTFB', category: 'performance', severity: 'error' }, 'pass', `Excellent TTFB (${ttfb}ms)`, { value: ttfb });
|
|
@@ -132,9 +162,27 @@ export const performanceRules = [
|
|
|
132
162
|
return createResult({ id: 'timing-ttfb', name: 'TTFB', category: 'performance', severity: 'error' }, 'info', `TTFB needs improvement (${ttfb}ms)`, { value: ttfb, recommendation: `Optimize server response time to under ${good}ms` });
|
|
133
163
|
}
|
|
134
164
|
if (ttfb <= poor) {
|
|
135
|
-
return createResult({ id: 'timing-ttfb', name: 'TTFB', category: 'performance', severity: 'error' }, 'warn', `Slow TTFB (${ttfb}ms)`, {
|
|
165
|
+
return createResult({ id: 'timing-ttfb', name: 'TTFB', category: 'performance', severity: 'error' }, 'warn', `Slow TTFB (${ttfb}ms)`, {
|
|
166
|
+
value: ttfb,
|
|
167
|
+
recommendation: 'Optimize server, use CDN, enable caching',
|
|
168
|
+
evidence: {
|
|
169
|
+
found: `${ttfb}ms TTFB`,
|
|
170
|
+
expected: `≤ ${needsImprovement}ms`,
|
|
171
|
+
impact: 'Slow server response delays page rendering and all subsequent resources',
|
|
172
|
+
learnMore: 'https://web.dev/ttfb/'
|
|
173
|
+
}
|
|
174
|
+
});
|
|
136
175
|
}
|
|
137
|
-
return createResult({ id: 'timing-ttfb', name: 'TTFB', category: 'performance', severity: 'error' }, 'fail', `Very slow TTFB (${ttfb}ms)`, {
|
|
176
|
+
return createResult({ id: 'timing-ttfb', name: 'TTFB', category: 'performance', severity: 'error' }, 'fail', `Very slow TTFB (${ttfb}ms)`, {
|
|
177
|
+
value: ttfb,
|
|
178
|
+
recommendation: 'Critical: Server is too slow. Check server, database, and network',
|
|
179
|
+
evidence: {
|
|
180
|
+
found: `${ttfb}ms TTFB`,
|
|
181
|
+
expected: `≤ ${poor}ms (ideally ≤ ${good}ms)`,
|
|
182
|
+
impact: 'Critical server performance issue. Users will experience very slow page loads',
|
|
183
|
+
learnMore: 'https://web.dev/ttfb/'
|
|
184
|
+
}
|
|
185
|
+
});
|
|
138
186
|
},
|
|
139
187
|
},
|
|
140
188
|
{
|
|
@@ -145,8 +193,9 @@ export const performanceRules = [
|
|
|
145
193
|
description: 'Total page load should be under 2.5s',
|
|
146
194
|
check: (ctx) => {
|
|
147
195
|
const total = ctx.timings?.total;
|
|
148
|
-
if (total === undefined)
|
|
149
|
-
return
|
|
196
|
+
if (total === undefined) {
|
|
197
|
+
return createResult({ id: 'timing-total', name: 'Load Time', category: 'performance', severity: 'warning' }, 'info', 'Not applicable (total load time data unavailable)', { recommendation: 'This rule checks total page load time when timing data is available' });
|
|
198
|
+
}
|
|
150
199
|
const { good, needsImprovement, poor } = SEO_THRESHOLDS.timing.total;
|
|
151
200
|
if (total <= good) {
|
|
152
201
|
return createResult({ id: 'timing-total', name: 'Load Time', category: 'performance', severity: 'warning' }, 'pass', `Fast page load (${total}ms)`, { value: total });
|
|
@@ -155,9 +204,27 @@ export const performanceRules = [
|
|
|
155
204
|
return createResult({ id: 'timing-total', name: 'Load Time', category: 'performance', severity: 'warning' }, 'info', `Page load time acceptable (${total}ms)`, { value: total, recommendation: `Aim for under ${good}ms for better user experience` });
|
|
156
205
|
}
|
|
157
206
|
if (total <= poor) {
|
|
158
|
-
return createResult({ id: 'timing-total', name: 'Load Time', category: 'performance', severity: 'warning' }, 'warn', `Slow page load (${total}ms)`, {
|
|
207
|
+
return createResult({ id: 'timing-total', name: 'Load Time', category: 'performance', severity: 'warning' }, 'warn', `Slow page load (${total}ms)`, {
|
|
208
|
+
value: total,
|
|
209
|
+
recommendation: 'Optimize assets, enable compression, use CDN',
|
|
210
|
+
evidence: {
|
|
211
|
+
found: `${total}ms total load time`,
|
|
212
|
+
expected: `≤ ${needsImprovement}ms`,
|
|
213
|
+
impact: 'Slow page loads increase bounce rate and reduce user engagement',
|
|
214
|
+
learnMore: 'https://web.dev/performance-scoring/'
|
|
215
|
+
}
|
|
216
|
+
});
|
|
159
217
|
}
|
|
160
|
-
return createResult({ id: 'timing-total', name: 'Load Time', category: 'performance', severity: 'warning' }, 'fail', `Very slow page load (${total}ms)`, {
|
|
218
|
+
return createResult({ id: 'timing-total', name: 'Load Time', category: 'performance', severity: 'warning' }, 'fail', `Very slow page load (${total}ms)`, {
|
|
219
|
+
value: total,
|
|
220
|
+
recommendation: 'Critical performance issue. Full optimization needed.',
|
|
221
|
+
evidence: {
|
|
222
|
+
found: `${total}ms total load time`,
|
|
223
|
+
expected: `≤ ${poor}ms (ideally ≤ ${good}ms)`,
|
|
224
|
+
impact: 'Critical performance issue. Very slow load times severely hurt SEO rankings and user experience',
|
|
225
|
+
learnMore: 'https://web.dev/performance-scoring/'
|
|
226
|
+
}
|
|
227
|
+
});
|
|
161
228
|
},
|
|
162
229
|
},
|
|
163
230
|
{
|
|
@@ -168,8 +235,9 @@ export const performanceRules = [
|
|
|
168
235
|
description: 'DNS lookup should be under 50ms',
|
|
169
236
|
check: (ctx) => {
|
|
170
237
|
const dns = ctx.timings?.dnsLookup;
|
|
171
|
-
if (dns === undefined)
|
|
172
|
-
return
|
|
238
|
+
if (dns === undefined) {
|
|
239
|
+
return createResult({ id: 'timing-dns', name: 'DNS Lookup', category: 'performance', severity: 'info' }, 'info', 'Not applicable (DNS lookup timing data unavailable)', { recommendation: 'This rule checks DNS lookup time when timing data is available' });
|
|
240
|
+
}
|
|
173
241
|
const { good, poor } = SEO_THRESHOLDS.timing.dnsLookup;
|
|
174
242
|
if (dns <= good) {
|
|
175
243
|
return createResult({ id: 'timing-dns', name: 'DNS Lookup', category: 'performance', severity: 'info' }, 'pass', `Fast DNS lookup (${dns}ms)`, { value: dns });
|
|
@@ -177,7 +245,17 @@ export const performanceRules = [
|
|
|
177
245
|
if (dns <= poor) {
|
|
178
246
|
return createResult({ id: 'timing-dns', name: 'DNS Lookup', category: 'performance', severity: 'info' }, 'info', `DNS lookup could be faster (${dns}ms)`, { value: dns, recommendation: 'Consider using faster DNS provider or dns-prefetch' });
|
|
179
247
|
}
|
|
180
|
-
return createResult({ id: 'timing-dns', name: 'DNS Lookup', category: 'performance', severity: 'info' }, 'warn', `Slow DNS lookup (${dns}ms)`, {
|
|
248
|
+
return createResult({ id: 'timing-dns', name: 'DNS Lookup', category: 'performance', severity: 'info' }, 'warn', `Slow DNS lookup (${dns}ms)`, {
|
|
249
|
+
value: dns,
|
|
250
|
+
recommendation: 'DNS is slow. Consider Cloudflare, Google DNS, or dns-prefetch',
|
|
251
|
+
evidence: {
|
|
252
|
+
found: `${dns}ms DNS lookup`,
|
|
253
|
+
expected: `≤ ${poor}ms (ideally ≤ ${good}ms)`,
|
|
254
|
+
impact: 'Slow DNS resolution delays connection establishment and increases page load time',
|
|
255
|
+
example: '<link rel="dns-prefetch" href="//api.example.com">',
|
|
256
|
+
learnMore: 'https://web.dev/dns-prefetch/'
|
|
257
|
+
}
|
|
258
|
+
});
|
|
181
259
|
},
|
|
182
260
|
},
|
|
183
261
|
{
|
|
@@ -188,8 +266,9 @@ export const performanceRules = [
|
|
|
188
266
|
description: 'TLS handshake should be under 100ms',
|
|
189
267
|
check: (ctx) => {
|
|
190
268
|
const tls = ctx.timings?.tlsHandshake;
|
|
191
|
-
if (tls === undefined)
|
|
192
|
-
return
|
|
269
|
+
if (tls === undefined) {
|
|
270
|
+
return createResult({ id: 'timing-tls', name: 'TLS Handshake', category: 'performance', severity: 'info' }, 'info', 'Not applicable (TLS handshake timing data unavailable)', { recommendation: 'This rule checks TLS handshake time when timing data is available' });
|
|
271
|
+
}
|
|
193
272
|
const { good, poor } = SEO_THRESHOLDS.timing.tlsHandshake;
|
|
194
273
|
if (tls <= good) {
|
|
195
274
|
return createResult({ id: 'timing-tls', name: 'TLS Handshake', category: 'performance', severity: 'info' }, 'pass', `Fast TLS handshake (${tls}ms)`, { value: tls });
|
|
@@ -197,7 +276,16 @@ export const performanceRules = [
|
|
|
197
276
|
if (tls <= poor) {
|
|
198
277
|
return createResult({ id: 'timing-tls', name: 'TLS Handshake', category: 'performance', severity: 'info' }, 'info', `TLS handshake could be faster (${tls}ms)`, { value: tls, recommendation: 'Consider TLS 1.3, HTTP/2, or preconnect hints' });
|
|
199
278
|
}
|
|
200
|
-
return createResult({ id: 'timing-tls', name: 'TLS Handshake', category: 'performance', severity: 'info' }, 'warn', `Slow TLS handshake (${tls}ms)`, {
|
|
279
|
+
return createResult({ id: 'timing-tls', name: 'TLS Handshake', category: 'performance', severity: 'info' }, 'warn', `Slow TLS handshake (${tls}ms)`, {
|
|
280
|
+
value: tls,
|
|
281
|
+
recommendation: 'TLS is slow. Check server configuration and certificate chain',
|
|
282
|
+
evidence: {
|
|
283
|
+
found: `${tls}ms TLS handshake`,
|
|
284
|
+
expected: `≤ ${poor}ms (ideally ≤ ${good}ms)`,
|
|
285
|
+
impact: 'Slow TLS handshake delays secure connection establishment, especially on mobile networks',
|
|
286
|
+
learnMore: 'https://web.dev/uses-http2/'
|
|
287
|
+
}
|
|
288
|
+
});
|
|
201
289
|
},
|
|
202
290
|
},
|
|
203
291
|
{
|
|
@@ -208,8 +296,9 @@ export const performanceRules = [
|
|
|
208
296
|
description: 'HTML should be under 500KB (ideally under 100KB)',
|
|
209
297
|
check: (ctx) => {
|
|
210
298
|
const size = ctx.htmlSize;
|
|
211
|
-
if (size === undefined)
|
|
212
|
-
return
|
|
299
|
+
if (size === undefined) {
|
|
300
|
+
return createResult({ id: 'response-html-size', name: 'HTML Size', category: 'performance', severity: 'warning' }, 'info', 'Not applicable (HTML size data unavailable)', { recommendation: 'This rule checks HTML size when response size data is available' });
|
|
301
|
+
}
|
|
213
302
|
const { good, warning, poor } = SEO_THRESHOLDS.responseSize.html;
|
|
214
303
|
const sizeKb = Math.round(size / 1024);
|
|
215
304
|
if (size <= good) {
|
|
@@ -219,9 +308,27 @@ export const performanceRules = [
|
|
|
219
308
|
return createResult({ id: 'response-html-size', name: 'HTML Size', category: 'performance', severity: 'warning' }, 'info', `HTML size is acceptable (${sizeKb}KB)`, { value: sizeKb, recommendation: 'Consider reducing HTML size for faster parsing' });
|
|
220
309
|
}
|
|
221
310
|
if (size <= poor) {
|
|
222
|
-
return createResult({ id: 'response-html-size', name: 'HTML Size', category: 'performance', severity: 'warning' }, 'warn', `HTML is large (${sizeKb}KB)`, {
|
|
311
|
+
return createResult({ id: 'response-html-size', name: 'HTML Size', category: 'performance', severity: 'warning' }, 'warn', `HTML is large (${sizeKb}KB)`, {
|
|
312
|
+
value: sizeKb,
|
|
313
|
+
recommendation: 'Reduce HTML size. Check for inline data, remove unused code',
|
|
314
|
+
evidence: {
|
|
315
|
+
found: `${sizeKb}KB HTML`,
|
|
316
|
+
expected: `≤ ${Math.round(warning / 1024)}KB (ideally ≤ ${Math.round(good / 1024)}KB)`,
|
|
317
|
+
impact: 'Large HTML increases parsing time and delays time to interactive',
|
|
318
|
+
learnMore: 'https://web.dev/dom-size/'
|
|
319
|
+
}
|
|
320
|
+
});
|
|
223
321
|
}
|
|
224
|
-
return createResult({ id: 'response-html-size', name: 'HTML Size', category: 'performance', severity: 'warning' }, 'fail', `HTML is very large (${sizeKb}KB)`, {
|
|
322
|
+
return createResult({ id: 'response-html-size', name: 'HTML Size', category: 'performance', severity: 'warning' }, 'fail', `HTML is very large (${sizeKb}KB)`, {
|
|
323
|
+
value: sizeKb,
|
|
324
|
+
recommendation: 'Critical: HTML too large. Use pagination, lazy loading, or split content',
|
|
325
|
+
evidence: {
|
|
326
|
+
found: `${sizeKb}KB HTML`,
|
|
327
|
+
expected: `≤ ${Math.round(poor / 1024)}KB (ideally ≤ ${Math.round(good / 1024)}KB)`,
|
|
328
|
+
impact: 'Very large HTML severely impacts page parsing performance and user experience',
|
|
329
|
+
learnMore: 'https://web.dev/dom-size/'
|
|
330
|
+
}
|
|
331
|
+
});
|
|
225
332
|
},
|
|
226
333
|
},
|
|
227
334
|
{
|
|
@@ -231,16 +338,26 @@ export const performanceRules = [
|
|
|
231
338
|
severity: 'warning',
|
|
232
339
|
description: 'Response should be compressed (gzip/brotli)',
|
|
233
340
|
check: (ctx) => {
|
|
234
|
-
if (ctx.htmlSize === undefined)
|
|
235
|
-
return
|
|
341
|
+
if (ctx.htmlSize === undefined) {
|
|
342
|
+
return createResult({ id: 'response-compression', name: 'Compression', category: 'performance', severity: 'warning' }, 'info', 'Not applicable (HTML size data unavailable)', { recommendation: 'This rule checks for response compression when response data is available' });
|
|
343
|
+
}
|
|
236
344
|
if (ctx.isCompressed === false && ctx.htmlSize > 1024) {
|
|
237
|
-
return createResult({ id: 'response-compression', name: 'Compression', category: 'performance', severity: 'warning' }, 'warn', 'Response is not compressed', {
|
|
345
|
+
return createResult({ id: 'response-compression', name: 'Compression', category: 'performance', severity: 'warning' }, 'warn', 'Response is not compressed', {
|
|
346
|
+
recommendation: 'Enable gzip or brotli compression on server',
|
|
347
|
+
evidence: {
|
|
348
|
+
found: 'No compression (Content-Encoding header missing)',
|
|
349
|
+
expected: 'Content-Encoding: gzip or Content-Encoding: br',
|
|
350
|
+
impact: 'Uncompressed responses are 60-80% larger, wasting bandwidth and slowing downloads',
|
|
351
|
+
example: '# Apache\nAddOutputFilterByType DEFLATE text/html text/css application/javascript\n\n# Nginx\ngzip on;\ngzip_types text/html text/css application/javascript;',
|
|
352
|
+
learnMore: 'https://web.dev/uses-text-compression/'
|
|
353
|
+
}
|
|
354
|
+
});
|
|
238
355
|
}
|
|
239
356
|
if (ctx.isCompressed === true) {
|
|
240
357
|
const ratio = ctx.compressedSize && ctx.htmlSize ? Math.round((ctx.compressedSize / ctx.htmlSize) * 100) : undefined;
|
|
241
358
|
return createResult({ id: 'response-compression', name: 'Compression', category: 'performance', severity: 'warning' }, 'pass', ratio ? `Response compressed (${ratio}% of original)` : 'Response is compressed');
|
|
242
359
|
}
|
|
243
|
-
return
|
|
360
|
+
return createResult({ id: 'response-compression', name: 'Compression', category: 'performance', severity: 'warning' }, 'info', 'Not applicable (small response or compression status unknown)');
|
|
244
361
|
},
|
|
245
362
|
},
|
|
246
363
|
{
|
|
@@ -250,8 +367,9 @@ export const performanceRules = [
|
|
|
250
367
|
severity: 'warning',
|
|
251
368
|
description: 'Static resources should have browser caching enabled',
|
|
252
369
|
check: (ctx) => {
|
|
253
|
-
if (ctx.resourcesWithoutCaching === undefined)
|
|
254
|
-
return
|
|
370
|
+
if (ctx.resourcesWithoutCaching === undefined) {
|
|
371
|
+
return createResult({ id: 'browser-caching', name: 'Browser Caching', category: 'performance', severity: 'warning' }, 'info', 'Not applicable (resource caching data unavailable)', { recommendation: 'This rule checks for browser caching headers on static resources when resource data is available' });
|
|
372
|
+
}
|
|
255
373
|
if (ctx.resourcesWithoutCaching > 0) {
|
|
256
374
|
return createResult({ id: 'browser-caching', name: 'Browser Caching', category: 'performance', severity: 'warning' }, 'warn', `${ctx.resourcesWithoutCaching} resources without cache headers`, {
|
|
257
375
|
value: ctx.resourcesWithoutCaching,
|
|
@@ -268,42 +386,43 @@ export const performanceRules = [
|
|
|
268
386
|
},
|
|
269
387
|
},
|
|
270
388
|
{
|
|
271
|
-
id: 'js-
|
|
272
|
-
name: 'JS
|
|
389
|
+
id: 'js-total-size',
|
|
390
|
+
name: 'JS Total Size',
|
|
273
391
|
category: 'performance',
|
|
274
392
|
severity: 'warning',
|
|
275
|
-
description: 'Total
|
|
393
|
+
description: 'Total JavaScript size should be minimized for mobile performance (Ideal < 150KB)',
|
|
276
394
|
check: (ctx) => {
|
|
277
|
-
const jsSize = ctx.jsTotalSize
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
395
|
+
const jsSize = ctx.jsTotalSize;
|
|
396
|
+
if (jsSize === undefined || jsSize === 0) {
|
|
397
|
+
if (ctx.jsFilesCount && ctx.jsFilesCount > 20) {
|
|
398
|
+
return createResult({ id: 'js-total-size', name: 'JS Total Size', category: 'performance', severity: 'info' }, 'info', `Checking ${ctx.jsFilesCount} JS files (size unknown)`, { recommendation: 'Enable deep analysis to measure transfer size. Aim for < 150KB total JS (gzip) for mobile.' });
|
|
399
|
+
}
|
|
400
|
+
return createResult({ id: 'js-total-size', name: 'JS Total Size', category: 'performance', severity: 'info' }, 'info', 'Not applicable (JS size data unavailable)', { recommendation: 'This rule checks total JavaScript size when resource size data is available' });
|
|
401
|
+
}
|
|
402
|
+
const sizeKb = jsSize / 1024;
|
|
403
|
+
if (sizeKb > 500) {
|
|
404
|
+
return createResult({ id: 'js-total-size', name: 'JS Total Size', category: 'performance', severity: 'error' }, 'fail', `Critical: JS size ${sizeKb.toFixed(0)}KB exceeds 500KB limit`, {
|
|
405
|
+
value: sizeKb,
|
|
406
|
+
recommendation: 'Reduce JS bundle size immediately. 500KB+ significantly hurts INP and LCP on mobile.',
|
|
287
407
|
evidence: {
|
|
288
|
-
found: `${
|
|
289
|
-
expected: '<
|
|
290
|
-
impact: '
|
|
291
|
-
learnMore: 'https://web.dev/total-byte-weight/'
|
|
408
|
+
found: `${sizeKb.toFixed(0)}KB`,
|
|
409
|
+
expected: '< 150KB (Ideal), < 300KB (Max)',
|
|
410
|
+
impact: 'High JS execution time blocks the main thread, causing poor responsiveness.'
|
|
292
411
|
}
|
|
293
412
|
});
|
|
294
413
|
}
|
|
295
|
-
if (
|
|
296
|
-
return createResult({ id: 'js-
|
|
297
|
-
value:
|
|
298
|
-
recommendation: '
|
|
414
|
+
if (sizeKb > 300) {
|
|
415
|
+
return createResult({ id: 'js-total-size', name: 'JS Total Size', category: 'performance', severity: 'warning' }, 'warn', `Risk: JS size ${sizeKb.toFixed(0)}KB (> 300KB)`, {
|
|
416
|
+
value: sizeKb,
|
|
417
|
+
recommendation: 'Optimize JS bundles. Code split routes and defer non-critical scripts.',
|
|
299
418
|
evidence: {
|
|
300
|
-
found: `${
|
|
301
|
-
expected: '<
|
|
302
|
-
impact: '
|
|
419
|
+
found: `${sizeKb.toFixed(0)}KB`,
|
|
420
|
+
expected: '< 300KB',
|
|
421
|
+
impact: 'Mobile devices struggle with parsing/executing large JS bundles.'
|
|
303
422
|
}
|
|
304
423
|
});
|
|
305
424
|
}
|
|
306
|
-
return
|
|
425
|
+
return createResult({ id: 'js-total-size', name: 'JS Total Size', category: 'performance', severity: 'info' }, 'pass', `Excellent: JS size ${sizeKb.toFixed(0)}KB (< 300KB)`, { value: sizeKb });
|
|
307
426
|
},
|
|
308
427
|
},
|
|
309
428
|
{
|
|
@@ -316,8 +435,9 @@ export const performanceRules = [
|
|
|
316
435
|
const jsCount = ctx.jsFilesCount || 0;
|
|
317
436
|
const cssCount = ctx.cssFilesCount || 0;
|
|
318
437
|
const totalFiles = jsCount + cssCount;
|
|
319
|
-
if (totalFiles === 0)
|
|
320
|
-
return
|
|
438
|
+
if (totalFiles === 0) {
|
|
439
|
+
return createResult({ id: 'excessive-js-css-files', name: 'Excessive JS/CSS Files', category: 'performance', severity: 'warning' }, 'info', 'Not applicable (no JS/CSS files detected or file count data unavailable)', { recommendation: 'This rule checks for excessive JS/CSS file count when resource data is available' });
|
|
440
|
+
}
|
|
321
441
|
if (totalFiles > 100) {
|
|
322
442
|
return createResult({ id: 'excessive-js-css-files', name: 'Excessive JS/CSS Files', category: 'performance', severity: 'warning' }, 'fail', `Page loads ${totalFiles} JS/CSS files (exceeds 100)`, {
|
|
323
443
|
value: totalFiles,
|
|
@@ -340,7 +460,7 @@ export const performanceRules = [
|
|
|
340
460
|
}
|
|
341
461
|
});
|
|
342
462
|
}
|
|
343
|
-
return
|
|
463
|
+
return createResult({ id: 'excessive-js-css-files', name: 'Excessive JS/CSS Files', category: 'performance', severity: 'warning' }, 'pass', `JS/CSS file count is acceptable (${totalFiles} files)`, { value: totalFiles });
|
|
344
464
|
},
|
|
345
465
|
},
|
|
346
466
|
{
|
|
@@ -350,8 +470,9 @@ export const performanceRules = [
|
|
|
350
470
|
severity: 'warning',
|
|
351
471
|
description: 'JS and CSS files should be minified',
|
|
352
472
|
check: (ctx) => {
|
|
353
|
-
if (ctx.unminifiedResources === undefined)
|
|
354
|
-
return
|
|
473
|
+
if (ctx.unminifiedResources === undefined) {
|
|
474
|
+
return createResult({ id: 'unminified-resources', name: 'Unminified Resources', category: 'performance', severity: 'warning' }, 'info', 'Not applicable (minification data unavailable)', { recommendation: 'This rule checks for unminified JS/CSS files when resource analysis data is available' });
|
|
475
|
+
}
|
|
355
476
|
if (ctx.unminifiedResources > 0) {
|
|
356
477
|
return createResult({ id: 'unminified-resources', name: 'Unminified Resources', category: 'performance', severity: 'warning' }, 'warn', `${ctx.unminifiedResources} unminified JS/CSS files detected`, {
|
|
357
478
|
value: ctx.unminifiedResources,
|
|
@@ -364,7 +485,254 @@ export const performanceRules = [
|
|
|
364
485
|
}
|
|
365
486
|
});
|
|
366
487
|
}
|
|
367
|
-
return
|
|
488
|
+
return createResult({ id: 'unminified-resources', name: 'Unminified Resources', category: 'performance', severity: 'warning' }, 'pass', 'All resources are minified');
|
|
489
|
+
},
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
id: 'page-weight',
|
|
493
|
+
name: 'Total Page Weight',
|
|
494
|
+
category: 'performance',
|
|
495
|
+
severity: 'warning',
|
|
496
|
+
description: 'Total page size should be under 500KB for optimal performance (Google benchmark)',
|
|
497
|
+
check: (ctx) => {
|
|
498
|
+
const totalSize = ctx.totalPageSize;
|
|
499
|
+
if (totalSize === undefined) {
|
|
500
|
+
return createResult({ id: 'page-weight', name: 'Total Page Weight', category: 'performance', severity: 'warning' }, 'info', 'Not applicable (total page size data unavailable)', { recommendation: 'This rule checks total page weight when resource size data is available' });
|
|
501
|
+
}
|
|
502
|
+
const sizeKb = Math.round(totalSize / 1024);
|
|
503
|
+
const sizeMb = (totalSize / (1024 * 1024)).toFixed(2);
|
|
504
|
+
if (totalSize <= 500 * 1024) {
|
|
505
|
+
return createResult({ id: 'page-weight', name: 'Total Page Weight', category: 'performance', severity: 'warning' }, 'pass', `Page weight is excellent (${sizeKb}KB)`, { value: sizeKb });
|
|
506
|
+
}
|
|
507
|
+
if (totalSize <= 1024 * 1024) {
|
|
508
|
+
return createResult({ id: 'page-weight', name: 'Total Page Weight', category: 'performance', severity: 'warning' }, 'warn', `Page weight exceeds Google benchmark (${sizeKb}KB)`, {
|
|
509
|
+
value: sizeKb,
|
|
510
|
+
recommendation: 'Reduce page size to under 500KB for faster loading',
|
|
511
|
+
evidence: {
|
|
512
|
+
found: `${sizeKb}KB`,
|
|
513
|
+
expected: '< 500KB (Google benchmark)',
|
|
514
|
+
impact: 'Larger pages take longer to download, especially on mobile networks',
|
|
515
|
+
learnMore: 'https://web.dev/total-byte-weight/'
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
if (totalSize <= 2 * 1024 * 1024) {
|
|
520
|
+
return createResult({ id: 'page-weight', name: 'Total Page Weight', category: 'performance', severity: 'warning' }, 'fail', `Page weight is heavy (${sizeMb}MB)`, {
|
|
521
|
+
value: sizeKb,
|
|
522
|
+
recommendation: 'Critical: Page is too heavy. Compress images, remove unused code',
|
|
523
|
+
evidence: {
|
|
524
|
+
found: `${sizeMb}MB`,
|
|
525
|
+
expected: '< 500KB',
|
|
526
|
+
impact: 'Heavy pages cause slow loading and high data costs for users'
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
return createResult({ id: 'page-weight', name: 'Total Page Weight', category: 'performance', severity: 'warning' }, 'fail', `Page weight is critical (${sizeMb}MB)`, {
|
|
531
|
+
value: sizeKb,
|
|
532
|
+
recommendation: 'Urgent: Drastically reduce page weight. Consider lazy loading, pagination, or removing heavy assets',
|
|
533
|
+
evidence: {
|
|
534
|
+
found: `${sizeMb}MB`,
|
|
535
|
+
expected: '< 500KB',
|
|
536
|
+
impact: 'Severely impacts mobile users and SEO rankings'
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
},
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
id: 'request-count',
|
|
543
|
+
name: 'Total HTTP Requests',
|
|
544
|
+
category: 'performance',
|
|
545
|
+
severity: 'warning',
|
|
546
|
+
description: 'Total requests should be under 50 for optimal performance (Google benchmark)',
|
|
547
|
+
check: (ctx) => {
|
|
548
|
+
const totalRequests = ctx.totalRequests;
|
|
549
|
+
if (totalRequests === undefined) {
|
|
550
|
+
return createResult({ id: 'request-count', name: 'Total HTTP Requests', category: 'performance', severity: 'warning' }, 'info', 'Not applicable (total requests data unavailable)', { recommendation: 'This rule checks total HTTP request count when resource data is available' });
|
|
551
|
+
}
|
|
552
|
+
if (totalRequests <= 50) {
|
|
553
|
+
return createResult({ id: 'request-count', name: 'Total HTTP Requests', category: 'performance', severity: 'warning' }, 'pass', `Request count is good (${totalRequests} requests)`, { value: totalRequests });
|
|
554
|
+
}
|
|
555
|
+
if (totalRequests <= 100) {
|
|
556
|
+
return createResult({ id: 'request-count', name: 'Total HTTP Requests', category: 'performance', severity: 'warning' }, 'warn', `Request count exceeds Google benchmark (${totalRequests} requests)`, {
|
|
557
|
+
value: totalRequests,
|
|
558
|
+
recommendation: 'Reduce requests by bundling assets, using CSS sprites, or lazy loading',
|
|
559
|
+
evidence: {
|
|
560
|
+
found: `${totalRequests} requests`,
|
|
561
|
+
expected: '< 50 requests (Google benchmark)',
|
|
562
|
+
impact: 'Each HTTP request adds latency, especially on mobile networks'
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
return createResult({ id: 'request-count', name: 'Total HTTP Requests', category: 'performance', severity: 'warning' }, 'fail', `Too many HTTP requests (${totalRequests} requests)`, {
|
|
567
|
+
value: totalRequests,
|
|
568
|
+
recommendation: 'Critical: Reduce requests significantly. Bundle JS/CSS, use image sprites, implement lazy loading',
|
|
569
|
+
evidence: {
|
|
570
|
+
found: `${totalRequests} requests`,
|
|
571
|
+
expected: '< 50 requests',
|
|
572
|
+
impact: 'Excessive requests severely impact page load time'
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
},
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
id: 'font-files-count',
|
|
579
|
+
name: 'Font Files Count',
|
|
580
|
+
category: 'performance',
|
|
581
|
+
severity: 'info',
|
|
582
|
+
description: 'Limit custom font files to reduce page weight (fonts avg ~123KB)',
|
|
583
|
+
check: (ctx) => {
|
|
584
|
+
const fontCount = ctx.fontFilesCount;
|
|
585
|
+
if (fontCount === undefined) {
|
|
586
|
+
return createResult({ id: 'font-files-count', name: 'Font Files Count', category: 'performance', severity: 'info' }, 'info', 'Not applicable (font file count data unavailable)', { recommendation: 'This rule checks for excessive custom font files when resource data is available' });
|
|
587
|
+
}
|
|
588
|
+
if (fontCount === 0) {
|
|
589
|
+
return createResult({ id: 'font-files-count', name: 'Font Files Count', category: 'performance', severity: 'info' }, 'pass', 'No custom fonts detected (using system fonts)');
|
|
590
|
+
}
|
|
591
|
+
if (fontCount <= 3) {
|
|
592
|
+
return createResult({ id: 'font-files-count', name: 'Font Files Count', category: 'performance', severity: 'info' }, 'pass', `Font files count is acceptable (${fontCount} fonts)`, { value: fontCount });
|
|
593
|
+
}
|
|
594
|
+
if (fontCount <= 6) {
|
|
595
|
+
return createResult({ id: 'font-files-count', name: 'Font Files Count', category: 'performance', severity: 'info' }, 'info', `Multiple font files detected (${fontCount} fonts)`, {
|
|
596
|
+
value: fontCount,
|
|
597
|
+
recommendation: 'Consider reducing font variants or using system fonts',
|
|
598
|
+
evidence: {
|
|
599
|
+
found: `${fontCount} font files`,
|
|
600
|
+
expected: '1-3 font files',
|
|
601
|
+
impact: 'Custom fonts add ~40-100KB each to page weight'
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
return createResult({ id: 'font-files-count', name: 'Font Files Count', category: 'performance', severity: 'info' }, 'warn', `Too many font files (${fontCount} fonts)`, {
|
|
606
|
+
value: fontCount,
|
|
607
|
+
recommendation: 'Reduce font files. Use variable fonts or limit to essential weights/styles',
|
|
608
|
+
evidence: {
|
|
609
|
+
found: `${fontCount} font files`,
|
|
610
|
+
expected: '< 4 font files',
|
|
611
|
+
impact: 'Excessive fonts significantly increase page weight and FOIT/FOUT issues'
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
id: 'font-display-swap',
|
|
618
|
+
name: 'Font Display Swap',
|
|
619
|
+
category: 'performance',
|
|
620
|
+
severity: 'warning',
|
|
621
|
+
description: 'Custom fonts should use font-display: swap to prevent invisible text',
|
|
622
|
+
check: (ctx) => {
|
|
623
|
+
if (!ctx.fontFilesCount || ctx.fontFilesCount === 0) {
|
|
624
|
+
return createResult({ id: 'font-display-swap', name: 'Font Display Swap', category: 'performance', severity: 'warning' }, 'info', 'Not applicable (no custom fonts detected)', { recommendation: 'This rule checks for font-display: swap when custom fonts are present' });
|
|
625
|
+
}
|
|
626
|
+
if (ctx.hasFontDisplaySwap === undefined) {
|
|
627
|
+
return createResult({ id: 'font-display-swap', name: 'Font Display Swap', category: 'performance', severity: 'warning' }, 'info', 'Not applicable (font display swap data unavailable)', { recommendation: 'This rule checks for font-display: swap in @font-face declarations' });
|
|
628
|
+
}
|
|
629
|
+
if (!ctx.hasFontDisplaySwap) {
|
|
630
|
+
return createResult({ id: 'font-display-swap', name: 'Font Display Swap', category: 'performance', severity: 'warning' }, 'warn', 'Custom fonts may block text rendering', {
|
|
631
|
+
recommendation: 'Add font-display: swap to @font-face declarations',
|
|
632
|
+
evidence: {
|
|
633
|
+
found: 'Custom fonts without font-display: swap',
|
|
634
|
+
expected: '@font-face { font-display: swap; }',
|
|
635
|
+
impact: 'Without font-display, text may be invisible until fonts load (FOIT), hurting perceived performance',
|
|
636
|
+
example: '@font-face {\n font-family: "Open Sans";\n src: url("/fonts/OpenSans.woff2");\n font-display: swap; /* Add this */\n}',
|
|
637
|
+
learnMore: 'https://web.dev/font-display/'
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
return createResult({ id: 'font-display-swap', name: 'Font Display Swap', category: 'performance', severity: 'warning' }, 'pass', 'Font display swap is enabled');
|
|
642
|
+
},
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
id: 'external-origins-count',
|
|
646
|
+
name: 'External Origins',
|
|
647
|
+
category: 'performance',
|
|
648
|
+
severity: 'info',
|
|
649
|
+
description: 'Limit external origins to reduce DNS lookups and connection overhead',
|
|
650
|
+
check: (ctx) => {
|
|
651
|
+
const externalOrigins = ctx.externalOrigins;
|
|
652
|
+
if (externalOrigins === undefined) {
|
|
653
|
+
return createResult({ id: 'external-origins-count', name: 'External Origins', category: 'performance', severity: 'info' }, 'info', 'Not applicable (external origins data unavailable)', { recommendation: 'This rule checks for excessive external origins when resource data is available' });
|
|
654
|
+
}
|
|
655
|
+
if (externalOrigins <= 5) {
|
|
656
|
+
return createResult({ id: 'external-origins-count', name: 'External Origins', category: 'performance', severity: 'info' }, 'pass', `External origins count is good (${externalOrigins} origins)`, { value: externalOrigins });
|
|
657
|
+
}
|
|
658
|
+
if (externalOrigins <= 10) {
|
|
659
|
+
return createResult({ id: 'external-origins-count', name: 'External Origins', category: 'performance', severity: 'info' }, 'info', `Multiple external origins (${externalOrigins} origins)`, {
|
|
660
|
+
value: externalOrigins,
|
|
661
|
+
recommendation: 'Use preconnect hints for critical external domains',
|
|
662
|
+
evidence: {
|
|
663
|
+
found: `${externalOrigins} external origins`,
|
|
664
|
+
expected: '< 6 external origins',
|
|
665
|
+
impact: 'Each origin requires DNS lookup and connection setup'
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
return createResult({ id: 'external-origins-count', name: 'External Origins', category: 'performance', severity: 'info' }, 'warn', `Too many external origins (${externalOrigins} origins)`, {
|
|
670
|
+
value: externalOrigins,
|
|
671
|
+
recommendation: 'Reduce third-party dependencies or consolidate to fewer providers',
|
|
672
|
+
evidence: {
|
|
673
|
+
found: `${externalOrigins} external origins`,
|
|
674
|
+
expected: '< 10 external origins',
|
|
675
|
+
impact: 'Excessive external origins increase DNS lookups and connection overhead significantly'
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
id: 'iframe-count',
|
|
682
|
+
name: 'Iframe Count',
|
|
683
|
+
category: 'performance',
|
|
684
|
+
severity: 'info',
|
|
685
|
+
description: 'Excessive iframes (ads, widgets, embeds) impact page performance',
|
|
686
|
+
check: (ctx) => {
|
|
687
|
+
const iframeCount = ctx.iframeCount;
|
|
688
|
+
if (iframeCount === undefined || iframeCount === 0) {
|
|
689
|
+
return createResult({ id: 'iframe-count', name: 'Iframe Count', category: 'performance', severity: 'info' }, 'pass', 'No iframes detected');
|
|
690
|
+
}
|
|
691
|
+
if (iframeCount <= 3) {
|
|
692
|
+
return createResult({ id: 'iframe-count', name: 'Iframe Count', category: 'performance', severity: 'info' }, 'pass', `Iframe count is acceptable (${iframeCount} iframes)`, { value: iframeCount });
|
|
693
|
+
}
|
|
694
|
+
if (iframeCount <= 6) {
|
|
695
|
+
return createResult({ id: 'iframe-count', name: 'Iframe Count', category: 'performance', severity: 'info' }, 'info', `Multiple iframes detected (${iframeCount} iframes)`, {
|
|
696
|
+
value: iframeCount,
|
|
697
|
+
recommendation: 'Consider lazy loading iframes or reducing embedded content',
|
|
698
|
+
evidence: {
|
|
699
|
+
found: `${iframeCount} iframes`,
|
|
700
|
+
expected: '< 4 iframes',
|
|
701
|
+
impact: 'Each iframe loads a separate document, increasing page weight and requests'
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
return createResult({ id: 'iframe-count', name: 'Iframe Count', category: 'performance', severity: 'info' }, 'warn', `Too many iframes (${iframeCount} iframes)`, {
|
|
706
|
+
value: iframeCount,
|
|
707
|
+
recommendation: 'Reduce iframes. Consider removing non-essential ads or widgets',
|
|
708
|
+
evidence: {
|
|
709
|
+
found: `${iframeCount} iframes`,
|
|
710
|
+
expected: '< 4 iframes',
|
|
711
|
+
impact: 'Excessive iframes (often ads) significantly slow down page load'
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
},
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
id: 'large-base64-images',
|
|
718
|
+
name: 'Large Inline Images',
|
|
719
|
+
category: 'performance',
|
|
720
|
+
severity: 'warning',
|
|
721
|
+
description: 'Large base64 inline images bloat HTML and cannot be cached separately',
|
|
722
|
+
check: (ctx) => {
|
|
723
|
+
const largeBase64 = ctx.largeBase64ImagesCount;
|
|
724
|
+
if (largeBase64 === undefined || largeBase64 === 0) {
|
|
725
|
+
return createResult({ id: 'large-base64-images', name: 'Large Inline Images', category: 'performance', severity: 'warning' }, 'pass', 'No large base64 inline images detected');
|
|
726
|
+
}
|
|
727
|
+
return createResult({ id: 'large-base64-images', name: 'Large Inline Images', category: 'performance', severity: 'warning' }, 'warn', `${largeBase64} large base64 image(s) found (> 5KB each)`, {
|
|
728
|
+
value: largeBase64,
|
|
729
|
+
recommendation: 'Use external image files instead of large base64 strings',
|
|
730
|
+
evidence: {
|
|
731
|
+
found: `${largeBase64} large inline images`,
|
|
732
|
+
expected: 'Base64 images should be < 2KB (icons only)',
|
|
733
|
+
impact: 'Large base64 images increase HTML size and cannot be cached separately'
|
|
734
|
+
}
|
|
735
|
+
});
|
|
368
736
|
},
|
|
369
737
|
},
|
|
370
738
|
];
|