recker 1.0.42 → 1.0.43-next.7a08602
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/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 +93958 -0
- 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 +722 -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 +258 -0
- package/dist/browser/seo/rules/content.d.ts +2 -0
- package/dist/browser/seo/rules/content.js +424 -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 +283 -0
- package/dist/browser/seo/rules/i18n.d.ts +2 -0
- package/dist/browser/seo/rules/i18n.js +277 -0
- package/dist/browser/seo/rules/images.d.ts +2 -0
- package/dist/browser/seo/rules/images.js +235 -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 +490 -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 +646 -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 +625 -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 +179 -0
- package/dist/browser/seo/rules/technical-advanced.d.ts +10 -0
- package/dist/browser/seo/rules/technical-advanced.js +288 -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 -0
- 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 +85 -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 +31 -22
- package/dist/seo/rules/content.js +207 -19
- package/dist/seo/rules/crawl.js +55 -40
- package/dist/seo/rules/cwv.js +21 -15
- package/dist/seo/rules/ecommerce.js +51 -20
- package/dist/seo/rules/i18n.js +61 -33
- package/dist/seo/rules/images.js +87 -28
- package/dist/seo/rules/index.js +2 -0
- package/dist/seo/rules/internal-linking.js +58 -39
- package/dist/seo/rules/links.js +70 -51
- package/dist/seo/rules/local.js +49 -25
- package/dist/seo/rules/meta.js +161 -62
- package/dist/seo/rules/mobile.js +112 -2
- package/dist/seo/rules/performance.js +309 -54
- 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 +40 -16
- package/dist/seo/rules/technical-advanced.js +27 -22
- 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 +14 -5
- package/dist/browser/utils/optional-require.d.ts +0 -19
- package/dist/browser/utils/optional-require.js +0 -105
package/dist/seo/analyzer.js
CHANGED
|
@@ -1,40 +1,52 @@
|
|
|
1
|
+
import { parse } from '../scrape/parser/index.js';
|
|
1
2
|
import { extractMeta, extractOpenGraph, extractTwitterCard, extractJsonLd, extractLinks, extractImages, } from '../scrape/extractors.js';
|
|
2
|
-
import {
|
|
3
|
+
import { generateKeywordCloud } from './keywords.js';
|
|
3
4
|
import { createRulesEngine, SEO_THRESHOLDS, } from './rules/index.js';
|
|
4
|
-
let cheerioModule = null;
|
|
5
|
-
async function loadCheerio() {
|
|
6
|
-
if (cheerioModule)
|
|
7
|
-
return cheerioModule;
|
|
8
|
-
cheerioModule = await requireOptional('cheerio', 'recker/seo');
|
|
9
|
-
return cheerioModule;
|
|
10
|
-
}
|
|
11
5
|
export class SeoAnalyzer {
|
|
12
|
-
|
|
6
|
+
root;
|
|
13
7
|
options;
|
|
14
8
|
rulesEngine;
|
|
15
|
-
constructor(
|
|
16
|
-
this
|
|
9
|
+
constructor(root, options = {}) {
|
|
10
|
+
this.root = root;
|
|
17
11
|
this.options = options;
|
|
18
12
|
this.rulesEngine = createRulesEngine(options.rules);
|
|
19
13
|
}
|
|
20
14
|
static async fromHtml(html, options = {}) {
|
|
21
|
-
const
|
|
22
|
-
return new SeoAnalyzer(
|
|
15
|
+
const root = parse(html);
|
|
16
|
+
return new SeoAnalyzer(root, options);
|
|
23
17
|
}
|
|
24
18
|
analyze() {
|
|
25
19
|
const url = this.options.baseUrl || '';
|
|
26
|
-
const meta = extractMeta(this
|
|
27
|
-
const og = extractOpenGraph(this
|
|
28
|
-
const twitter = extractTwitterCard(this
|
|
29
|
-
const jsonLd = extractJsonLd(this
|
|
30
|
-
const links = extractLinks(this
|
|
31
|
-
const images = extractImages(this
|
|
20
|
+
const meta = extractMeta(this.root);
|
|
21
|
+
const og = extractOpenGraph(this.root);
|
|
22
|
+
const twitter = extractTwitterCard(this.root);
|
|
23
|
+
const jsonLd = extractJsonLd(this.root);
|
|
24
|
+
const links = extractLinks(this.root, { baseUrl: this.options.baseUrl });
|
|
25
|
+
const images = extractImages(this.root, { baseUrl: this.options.baseUrl });
|
|
26
|
+
const visibleText = this.getVisibleText();
|
|
27
|
+
const keywords = generateKeywordCloud({
|
|
28
|
+
visibleText,
|
|
29
|
+
title: meta.title,
|
|
30
|
+
description: meta.description,
|
|
31
|
+
keywords: meta.keywords?.join(', ')
|
|
32
|
+
});
|
|
33
|
+
const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
|
|
34
|
+
const emailsFound = (visibleText.match(emailRegex) || []).filter(e => !e.endsWith('.png') && !e.endsWith('.jpg') && !e.endsWith('.webp'));
|
|
35
|
+
const socialDomains = ['facebook.com', 'twitter.com', 'x.com', 'instagram.com', 'linkedin.com', 'youtube.com', 'pinterest.com', 'tiktok.com', 'github.com', 'reddit.com', 'snapchat.com', 'whatsapp.com', 'telegram.org', 'discord.com', 'threads.net'];
|
|
36
|
+
const socialLinksFound = links
|
|
37
|
+
.map(l => l.href)
|
|
38
|
+
.filter(href => socialDomains.some(d => href.toLowerCase().includes(d)));
|
|
39
|
+
const socialLinkDetails = this.analyzeSocialLinks(links, socialDomains);
|
|
32
40
|
const headings = this.analyzeHeadings();
|
|
33
41
|
const content = this.analyzeContent(headings);
|
|
34
42
|
const linkAnalysis = this.buildLinkAnalysis(links);
|
|
35
43
|
const imageAnalysis = this.buildImageAnalysis(images);
|
|
36
44
|
const social = this.buildSocialAnalysis(og, twitter);
|
|
37
45
|
const technical = this.buildTechnicalAnalysis(meta);
|
|
46
|
+
const resources = this.analyzeResources();
|
|
47
|
+
const analytics = this.analyzeAnalytics();
|
|
48
|
+
const feeds = this.analyzeFeeds();
|
|
49
|
+
const conversion = this.analyzeConversionElements(links, visibleText);
|
|
38
50
|
const context = this.buildRuleContext({
|
|
39
51
|
meta,
|
|
40
52
|
og,
|
|
@@ -45,6 +57,14 @@ export class SeoAnalyzer {
|
|
|
45
57
|
linkAnalysis,
|
|
46
58
|
imageAnalysis,
|
|
47
59
|
links,
|
|
60
|
+
keywords,
|
|
61
|
+
resources,
|
|
62
|
+
emailsFound,
|
|
63
|
+
socialLinksFound,
|
|
64
|
+
socialLinkDetails,
|
|
65
|
+
analytics,
|
|
66
|
+
feeds,
|
|
67
|
+
conversion,
|
|
48
68
|
});
|
|
49
69
|
const ruleResults = this.rulesEngine.evaluate(context);
|
|
50
70
|
const checks = this.convertToCheckResults(ruleResults);
|
|
@@ -65,23 +85,33 @@ export class SeoAnalyzer {
|
|
|
65
85
|
score,
|
|
66
86
|
summary,
|
|
67
87
|
checks,
|
|
68
|
-
title: meta.title
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
description:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
title: meta.title
|
|
89
|
+
? { text: meta.title, length: meta.title.length }
|
|
90
|
+
: undefined,
|
|
91
|
+
metaDescription: meta.description
|
|
92
|
+
? { text: meta.description, length: meta.description.length }
|
|
93
|
+
: undefined,
|
|
94
|
+
openGraph: Object.keys(og).length > 0
|
|
95
|
+
? {
|
|
96
|
+
title: og.title,
|
|
97
|
+
description: og.description,
|
|
98
|
+
image: Array.isArray(og.image) ? og.image[0] : og.image,
|
|
99
|
+
url: og.url,
|
|
100
|
+
type: og.type,
|
|
101
|
+
siteName: og.siteName,
|
|
102
|
+
}
|
|
103
|
+
: undefined,
|
|
104
|
+
twitterCard: Object.keys(twitter).length > 0
|
|
105
|
+
? {
|
|
106
|
+
card: twitter.card,
|
|
107
|
+
title: twitter.title,
|
|
108
|
+
description: twitter.description,
|
|
109
|
+
image: Array.isArray(twitter.image)
|
|
110
|
+
? twitter.image[0]
|
|
111
|
+
: twitter.image,
|
|
112
|
+
site: twitter.site,
|
|
113
|
+
}
|
|
114
|
+
: undefined,
|
|
85
115
|
structuredData: {
|
|
86
116
|
count: jsonLd.length,
|
|
87
117
|
types: jsonLd.map((j) => j['@type']).filter(Boolean),
|
|
@@ -89,32 +119,66 @@ export class SeoAnalyzer {
|
|
|
89
119
|
},
|
|
90
120
|
headings: headings,
|
|
91
121
|
content,
|
|
122
|
+
keywords,
|
|
92
123
|
links: linkAnalysis,
|
|
93
124
|
images: imageAnalysis,
|
|
94
125
|
social,
|
|
95
126
|
technical,
|
|
96
127
|
};
|
|
97
128
|
}
|
|
129
|
+
getMainBody() {
|
|
130
|
+
const bodies = this.root.querySelectorAll('body');
|
|
131
|
+
if (bodies.length === 0)
|
|
132
|
+
return null;
|
|
133
|
+
if (bodies.length === 1)
|
|
134
|
+
return bodies[0];
|
|
135
|
+
return bodies.reduce((prev, curr) => curr.text.length > prev.text.length ? curr : prev);
|
|
136
|
+
}
|
|
137
|
+
getVisibleText() {
|
|
138
|
+
const body = this.getMainBody();
|
|
139
|
+
if (!body)
|
|
140
|
+
return '';
|
|
141
|
+
const clone = body.clone();
|
|
142
|
+
const tagsToRemove = ['script', 'style', 'noscript', 'iframe', 'svg', 'header', 'footer', 'nav'];
|
|
143
|
+
try {
|
|
144
|
+
const elements = clone.querySelectorAll(tagsToRemove.join(','));
|
|
145
|
+
elements.forEach(el => el.remove());
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
tagsToRemove.forEach(tag => {
|
|
149
|
+
clone.querySelectorAll(tag).forEach(el => el.remove());
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return clone.text.replace(/\s+/g, ' ').trim();
|
|
153
|
+
}
|
|
98
154
|
buildRuleContext(data) {
|
|
99
|
-
const { meta, og, twitter, jsonLd, headings, content, linkAnalysis, imageAnalysis, links } = data;
|
|
100
|
-
const
|
|
155
|
+
const { meta, og, twitter, jsonLd, headings, content, linkAnalysis, imageAnalysis, links, keywords, resources, emailsFound, socialLinksFound, socialLinkDetails, analytics, feeds, conversion, } = data;
|
|
156
|
+
const html = this.root.querySelector('html');
|
|
157
|
+
const htmlLang = html ? html.getAttribute('lang') : undefined;
|
|
101
158
|
const hreflangTags = [];
|
|
102
|
-
this
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
159
|
+
this.root
|
|
160
|
+
.querySelectorAll('link[rel="alternate"][hreflang]')
|
|
161
|
+
.forEach((el) => {
|
|
162
|
+
const lang = el.getAttribute('hreflang');
|
|
163
|
+
const href = el.getAttribute('href');
|
|
106
164
|
if (lang && href) {
|
|
107
165
|
hreflangTags.push({ lang, href });
|
|
108
166
|
}
|
|
109
167
|
});
|
|
110
|
-
const
|
|
168
|
+
const ogLocaleEl = this.root.querySelector('meta[property="og:locale"]');
|
|
169
|
+
const ogLocale = ogLocaleEl ? ogLocaleEl.getAttribute('content') : undefined;
|
|
111
170
|
const genericTexts = SEO_THRESHOLDS.links.genericTexts;
|
|
112
171
|
const genericTextLinks = links.filter((l) => {
|
|
113
172
|
const text = l.text?.toLowerCase().trim();
|
|
114
173
|
return text && genericTexts.some((g) => text === g || text.includes(g));
|
|
115
174
|
});
|
|
116
175
|
const linksWithGenericText = genericTextLinks.length;
|
|
117
|
-
const linksWithoutTextArray = links.filter((l) =>
|
|
176
|
+
const linksWithoutTextArray = links.filter((l) => {
|
|
177
|
+
const hasText = l.text && l.text.trim() !== '';
|
|
178
|
+
const hasContent = l.hasImageWithAlt || l.hasSvgWithTitle;
|
|
179
|
+
const hasA11yLabel = l.ariaLabel || l.title;
|
|
180
|
+
return !hasText && !hasContent && !hasA11yLabel;
|
|
181
|
+
});
|
|
118
182
|
const externalBlankLinks = links.filter((l) => l.type === 'external' && l.target === '_blank');
|
|
119
183
|
const missingNoopenerLinks = externalBlankLinks.filter((l) => !l.rel?.includes('noopener'));
|
|
120
184
|
const missingNoreferrerLinks = externalBlankLinks.filter((l) => !l.rel?.includes('noreferrer'));
|
|
@@ -125,11 +189,37 @@ export class SeoAnalyzer {
|
|
|
125
189
|
missingNoreferrer: missingNoreferrerLinks,
|
|
126
190
|
};
|
|
127
191
|
const hasMixedContent = this.checkMixedContent();
|
|
128
|
-
const h1Elements = this
|
|
129
|
-
const h1Text = h1Elements.
|
|
130
|
-
const
|
|
192
|
+
const h1Elements = this.root.querySelectorAll('h1');
|
|
193
|
+
const h1Text = h1Elements.length > 0 ? h1Elements[0].text.trim() : '';
|
|
194
|
+
const iframeCount = this.root.querySelectorAll('iframe').length;
|
|
195
|
+
const topKeywords = keywords.topKeywords.slice(0, 5).map(k => k.word);
|
|
196
|
+
const mainKeyword = topKeywords.length > 0 ? topKeywords[0] : undefined;
|
|
197
|
+
const keywordsInTitle = topKeywords.some(kw => meta.title?.toLowerCase().includes(kw));
|
|
198
|
+
const keywordsInDescription = topKeywords.some(kw => meta.description?.toLowerCase().includes(kw));
|
|
199
|
+
const keywordsInH1 = topKeywords.some(kw => h1Text.toLowerCase().includes(kw));
|
|
200
|
+
const urlPath = this.options.baseUrl ? new URL(this.options.baseUrl).pathname.toLowerCase().replace(/[-_]/g, ' ') : '';
|
|
201
|
+
const keywordsInUrl = topKeywords.some(kw => urlPath.includes(kw));
|
|
202
|
+
const firstParagraph = this.root.querySelector('p')?.text?.toLowerCase() || '';
|
|
203
|
+
const keywordsInFirstParagraph = topKeywords.some(kw => firstParagraph.includes(kw));
|
|
204
|
+
const imageAlts = imageAnalysis.imageAltTexts || [];
|
|
205
|
+
const keywordsInAltText = imageAlts.some(alt => topKeywords.some(kw => alt.includes(kw)));
|
|
206
|
+
const keywordConsistencyDetails = mainKeyword ? {
|
|
207
|
+
inTitle: meta.title?.toLowerCase().includes(mainKeyword) || false,
|
|
208
|
+
inDescription: meta.description?.toLowerCase().includes(mainKeyword) || false,
|
|
209
|
+
inH1: h1Text.toLowerCase().includes(mainKeyword) || false,
|
|
210
|
+
inUrl: urlPath.includes(mainKeyword),
|
|
211
|
+
inFirstParagraph: firstParagraph.includes(mainKeyword),
|
|
212
|
+
inAltText: imageAlts.some(alt => alt.includes(mainKeyword)),
|
|
213
|
+
} : undefined;
|
|
214
|
+
const keywordConsistencyScore = keywordConsistencyDetails
|
|
215
|
+
? Object.values(keywordConsistencyDetails).filter(Boolean).length
|
|
216
|
+
: undefined;
|
|
217
|
+
const viewportEl = this.root.querySelector('meta[name="viewport"]');
|
|
218
|
+
const viewportContent = viewportEl
|
|
219
|
+
? viewportEl.getAttribute('content')
|
|
220
|
+
: undefined;
|
|
131
221
|
const a11yMetrics = this.analyzeAccessibility();
|
|
132
|
-
const imagesWithEmptyAlt = this
|
|
222
|
+
const imagesWithEmptyAlt = this.root.querySelectorAll('img[alt=""]').length;
|
|
133
223
|
const linkSecurityMetrics = this.analyzeLinkSecurity();
|
|
134
224
|
const faviconInfo = this.detectFavicon();
|
|
135
225
|
const perfHints = this.analyzePerformanceHints();
|
|
@@ -137,11 +227,34 @@ export class SeoAnalyzer {
|
|
|
137
227
|
const structuralHtml = this.analyzeStructuralHtml();
|
|
138
228
|
const breadcrumbs = this.analyzeBreadcrumbs(jsonLd.map((j) => j['@type']).filter(Boolean));
|
|
139
229
|
const multimedia = this.analyzeMultimedia();
|
|
230
|
+
const advancedImages = this.analyzeAdvancedImages();
|
|
231
|
+
const responsiveImages = this.analyzeResponsiveImages();
|
|
232
|
+
const inlineImages = this.analyzeInlineImages();
|
|
140
233
|
const trustSignals = this.analyzeTrustSignals(links);
|
|
141
|
-
const totalSubheadings = (headings.structure.filter((h) => h.level === 2).length || 0) +
|
|
142
|
-
|
|
234
|
+
const totalSubheadings = (headings.structure.filter((h) => h.level === 2).length || 0) +
|
|
235
|
+
(headings.structure.filter((h) => h.level === 3).length || 0);
|
|
236
|
+
const subheadingFrequency = content.wordCount > 0
|
|
237
|
+
? (totalSubheadings / content.wordCount) * 100
|
|
238
|
+
: 0;
|
|
143
239
|
const textHtmlRatio = this.calculateTextHtmlRatio(content.characterCount);
|
|
144
240
|
return {
|
|
241
|
+
jsFilesCount: resources.jsFilesCount,
|
|
242
|
+
cssFilesCount: resources.cssFilesCount,
|
|
243
|
+
unminifiedResources: resources.unminifiedResources,
|
|
244
|
+
unminifiedResourceUrls: resources.unminifiedResourceUrls,
|
|
245
|
+
emailsFound,
|
|
246
|
+
socialLinksFound,
|
|
247
|
+
...socialLinkDetails,
|
|
248
|
+
keywordsInTitle,
|
|
249
|
+
keywordsInDescription,
|
|
250
|
+
keywordsInH1,
|
|
251
|
+
keywordsInUrl,
|
|
252
|
+
keywordsInFirstParagraph,
|
|
253
|
+
keywordsInAltText,
|
|
254
|
+
keywordConsistencyScore,
|
|
255
|
+
keywordConsistencyDetails,
|
|
256
|
+
topKeywords,
|
|
257
|
+
mainKeyword,
|
|
145
258
|
title: meta.title,
|
|
146
259
|
titleLength: meta.title?.length,
|
|
147
260
|
metaDescription: meta.description,
|
|
@@ -157,7 +270,9 @@ export class SeoAnalyzer {
|
|
|
157
270
|
twitterCard: twitter.card,
|
|
158
271
|
twitterTitle: twitter.title,
|
|
159
272
|
twitterDescription: twitter.description,
|
|
160
|
-
twitterImage: Array.isArray(twitter.image)
|
|
273
|
+
twitterImage: Array.isArray(twitter.image)
|
|
274
|
+
? twitter.image[0]
|
|
275
|
+
: twitter.image,
|
|
161
276
|
twitterSite: twitter.site,
|
|
162
277
|
h1Count: headings.h1Count,
|
|
163
278
|
h1Text: h1Text || undefined,
|
|
@@ -174,13 +289,18 @@ export class SeoAnalyzer {
|
|
|
174
289
|
imagesWithEmptyAlt,
|
|
175
290
|
imagesUsingModernFormats: imageAnalysis.modernFormats,
|
|
176
291
|
altTextLengths: imageAnalysis.altTextLengths,
|
|
292
|
+
imageAltTexts: imageAnalysis.imageAltTexts,
|
|
177
293
|
imageFilenames: imageAnalysis.imageFilenames,
|
|
178
294
|
imagesWithAsyncDecoding: imageAnalysis.imagesWithAsyncDecoding,
|
|
295
|
+
imagesWithSrcset: responsiveImages.imagesWithSrcset,
|
|
296
|
+
largeBase64ImagesCount: inlineImages.largeBase64ImagesCount,
|
|
179
297
|
...a11yMetrics,
|
|
180
298
|
allLinks: links,
|
|
181
299
|
totalLinks: linkAnalysis.total,
|
|
182
300
|
internalLinks: linkAnalysis.internal,
|
|
183
301
|
externalLinks: linkAnalysis.external,
|
|
302
|
+
internalHttpLinks: linkAnalysis.internalHttpLinks,
|
|
303
|
+
internalHttpLinkUrls: linkAnalysis.internalHttpLinkUrls,
|
|
184
304
|
linksWithoutText: linkAnalysis.withoutText,
|
|
185
305
|
nofollowLinks: linkAnalysis.nofollow,
|
|
186
306
|
sponsoredLinks: linkAnalysis.sponsoredLinks,
|
|
@@ -221,6 +341,12 @@ export class SeoAnalyzer {
|
|
|
221
341
|
hasMixedContent,
|
|
222
342
|
responseHeaders: this.options.responseHeaders,
|
|
223
343
|
textHtmlRatio,
|
|
344
|
+
hasFrameTags: this.root.querySelectorAll('frame, frameset').length > 0,
|
|
345
|
+
iframeCount: this.root.querySelectorAll('iframe').length,
|
|
346
|
+
hasDeprecatedPlugins: this.root.querySelectorAll('object, embed, applet').length > 0,
|
|
347
|
+
deprecatedTagsCount: this.root.querySelectorAll('center, font, strike, u, marquee, blink, big, tt').length,
|
|
348
|
+
deprecatedTagsFound: ['center', 'font', 'strike', 'u', 'marquee', 'blink', 'big', 'tt'].filter(t => this.root.querySelectorAll(t).length > 0),
|
|
349
|
+
hasAppleTouchIcon: this.root.querySelectorAll('link[rel="apple-touch-icon"], link[rel="apple-touch-icon-precomposed"]').length > 0,
|
|
224
350
|
...faviconInfo,
|
|
225
351
|
...perfHints,
|
|
226
352
|
lcpHints: cwvHints.lcpHints,
|
|
@@ -229,11 +355,20 @@ export class SeoAnalyzer {
|
|
|
229
355
|
jsonLdTypes: jsonLd.map((j) => j['@type']).filter(Boolean),
|
|
230
356
|
url: this.options.baseUrl,
|
|
231
357
|
urlLength: this.options.baseUrl?.length,
|
|
232
|
-
titleMatchesH1: meta.title && h1Text
|
|
358
|
+
titleMatchesH1: meta.title && h1Text
|
|
359
|
+
? meta.title.toLowerCase().trim() === h1Text.toLowerCase().trim()
|
|
360
|
+
: undefined,
|
|
233
361
|
...this.analyzeUrlQuality(),
|
|
234
362
|
...this.analyzeJsRendering(content),
|
|
235
363
|
hreflangTags: hreflangTags.length > 0 ? hreflangTags : undefined,
|
|
236
364
|
ogLocale,
|
|
365
|
+
analyticsDetected: analytics.analyticsDetected,
|
|
366
|
+
analyticsProviders: analytics.analyticsProviders,
|
|
367
|
+
...feeds,
|
|
368
|
+
ctaButtonsCount: conversion.ctaButtonsCount,
|
|
369
|
+
formCount: conversion.formCount,
|
|
370
|
+
hasWhatsAppLink: conversion.hasWhatsAppLink,
|
|
371
|
+
hasPhoneOnPage: conversion.hasPhoneOnPage,
|
|
237
372
|
};
|
|
238
373
|
}
|
|
239
374
|
analyzeUrlQuality() {
|
|
@@ -249,7 +384,7 @@ export class SeoAnalyzer {
|
|
|
249
384
|
const path = url.pathname + url.search;
|
|
250
385
|
const urlHasUppercase = /[A-Z]/.test(path);
|
|
251
386
|
const urlHasAccents = /[àáâãäåæçèéêëìíîïñòóôõöùúûüýÿ]/i.test(path);
|
|
252
|
-
const urlHasSpecialChars = /[<>{}
|
|
387
|
+
const urlHasSpecialChars = /[<>{}|\^`\[\]]/.test(path) || /%[0-9A-F]{2}/i.test(path);
|
|
253
388
|
return { urlHasUppercase, urlHasSpecialChars, urlHasAccents };
|
|
254
389
|
}
|
|
255
390
|
catch {
|
|
@@ -262,20 +397,40 @@ export class SeoAnalyzer {
|
|
|
262
397
|
}
|
|
263
398
|
analyzeJsRendering(content) {
|
|
264
399
|
const bodyTextLength = content.characterCount;
|
|
265
|
-
const scriptCount = this
|
|
266
|
-
const
|
|
267
|
-
const
|
|
400
|
+
const scriptCount = this.root.querySelectorAll('script').length;
|
|
401
|
+
const noscriptEl = this.root.querySelector('noscript');
|
|
402
|
+
const noscriptContent = noscriptEl ? noscriptEl.text.trim() : '';
|
|
403
|
+
const hasNoscriptContent = noscriptContent.length > 0;
|
|
268
404
|
return { bodyTextLength, scriptCount, hasNoscriptContent };
|
|
269
405
|
}
|
|
406
|
+
analyzeResponsiveImages() {
|
|
407
|
+
let imagesWithSrcset = 0;
|
|
408
|
+
this.root.querySelectorAll('img').forEach((img) => {
|
|
409
|
+
if (img.getAttribute('srcset') || (img.parentNode && img.parentNode.tagName === 'PICTURE')) {
|
|
410
|
+
imagesWithSrcset++;
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
return { imagesWithSrcset };
|
|
414
|
+
}
|
|
415
|
+
analyzeInlineImages() {
|
|
416
|
+
let largeBase64ImagesCount = 0;
|
|
417
|
+
this.root.querySelectorAll('img').forEach((img) => {
|
|
418
|
+
const src = img.getAttribute('src') || '';
|
|
419
|
+
if (src.startsWith('data:image') && src.length > 5 * 1024) {
|
|
420
|
+
largeBase64ImagesCount++;
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
return { largeBase64ImagesCount };
|
|
424
|
+
}
|
|
270
425
|
checkMixedContent() {
|
|
271
426
|
let hasMixed = false;
|
|
272
|
-
this
|
|
427
|
+
this.root.querySelectorAll('img[src^="http://"]').forEach(() => {
|
|
273
428
|
hasMixed = true;
|
|
274
429
|
});
|
|
275
|
-
this
|
|
430
|
+
this.root.querySelectorAll('script[src^="http://"]').forEach(() => {
|
|
276
431
|
hasMixed = true;
|
|
277
432
|
});
|
|
278
|
-
this
|
|
433
|
+
this.root.querySelectorAll('link[href^="http://"]').forEach(() => {
|
|
279
434
|
hasMixed = true;
|
|
280
435
|
});
|
|
281
436
|
return hasMixed;
|
|
@@ -283,10 +438,11 @@ export class SeoAnalyzer {
|
|
|
283
438
|
analyzeLinkSecurity() {
|
|
284
439
|
let withoutNoopener = 0;
|
|
285
440
|
let withoutNoreferrer = 0;
|
|
286
|
-
this
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const
|
|
441
|
+
this.root
|
|
442
|
+
.querySelectorAll('a[href^="http"][target="_blank"]')
|
|
443
|
+
.forEach((el) => {
|
|
444
|
+
const href = el.getAttribute('href') || '';
|
|
445
|
+
const rel = (el.getAttribute('rel') || '').toLowerCase();
|
|
290
446
|
if (this.options.baseUrl && href.startsWith(this.options.baseUrl)) {
|
|
291
447
|
return;
|
|
292
448
|
}
|
|
@@ -310,39 +466,42 @@ export class SeoAnalyzer {
|
|
|
310
466
|
'link[rel="apple-touch-icon-precomposed"]',
|
|
311
467
|
];
|
|
312
468
|
for (const selector of faviconSelectors) {
|
|
313
|
-
const favicon = this
|
|
314
|
-
if (favicon
|
|
469
|
+
const favicon = this.root.querySelector(selector);
|
|
470
|
+
if (favicon) {
|
|
315
471
|
return {
|
|
316
472
|
hasFavicon: true,
|
|
317
|
-
faviconUrl: favicon.
|
|
473
|
+
faviconUrl: favicon.getAttribute('href'),
|
|
318
474
|
};
|
|
319
475
|
}
|
|
320
476
|
}
|
|
321
477
|
return { hasFavicon: false };
|
|
322
478
|
}
|
|
323
479
|
analyzePerformanceHints() {
|
|
324
|
-
const preconnectLinks = this
|
|
480
|
+
const preconnectLinks = this.root.querySelectorAll('link[rel="preconnect"]');
|
|
325
481
|
const preconnectCount = preconnectLinks.length;
|
|
326
|
-
const dnsPrefetchLinks = this
|
|
482
|
+
const dnsPrefetchLinks = this.root.querySelectorAll('link[rel="dns-prefetch"]');
|
|
327
483
|
const dnsPrefetchCount = dnsPrefetchLinks.length;
|
|
328
|
-
const preloadLinks = this
|
|
484
|
+
const preloadLinks = this.root.querySelectorAll('link[rel="preload"]');
|
|
329
485
|
const preloadCount = preloadLinks.length;
|
|
330
486
|
let renderBlockingResources = 0;
|
|
331
|
-
this
|
|
487
|
+
this.root
|
|
488
|
+
.querySelectorAll('head script[src]:not([async]):not([defer])')
|
|
489
|
+
.forEach(() => {
|
|
332
490
|
renderBlockingResources++;
|
|
333
491
|
});
|
|
334
|
-
this
|
|
335
|
-
const
|
|
336
|
-
const media = $el.attr('media');
|
|
492
|
+
this.root.querySelectorAll('head link[rel="stylesheet"]').forEach((el) => {
|
|
493
|
+
const media = el.getAttribute('media');
|
|
337
494
|
if (!media || media === 'all' || media === 'screen') {
|
|
338
495
|
renderBlockingResources++;
|
|
339
496
|
}
|
|
340
497
|
});
|
|
341
|
-
const inlineScriptsCount = this
|
|
342
|
-
|
|
498
|
+
const inlineScriptsCount = this.root
|
|
499
|
+
.querySelectorAll('script:not([src])')
|
|
500
|
+
.filter((el) => {
|
|
501
|
+
const content = el.innerHTML || '';
|
|
343
502
|
return content.trim().length > 0;
|
|
344
503
|
}).length;
|
|
345
|
-
const inlineStylesCount = this
|
|
504
|
+
const inlineStylesCount = this.root.querySelectorAll('style').length;
|
|
346
505
|
return {
|
|
347
506
|
hasPreconnect: preconnectCount > 0,
|
|
348
507
|
preconnectCount,
|
|
@@ -356,16 +515,15 @@ export class SeoAnalyzer {
|
|
|
356
515
|
};
|
|
357
516
|
}
|
|
358
517
|
analyzeCWVHints() {
|
|
359
|
-
const images = this
|
|
518
|
+
const images = this.root.querySelectorAll('img');
|
|
360
519
|
let hasLargeImages = false;
|
|
361
520
|
let hasLazyLcp = false;
|
|
362
521
|
let hasPriorityHints = false;
|
|
363
|
-
images.slice(0, 3).
|
|
364
|
-
const
|
|
365
|
-
const
|
|
366
|
-
const
|
|
367
|
-
const
|
|
368
|
-
const fetchPriority = $el.attr('fetchpriority');
|
|
522
|
+
images.slice(0, 3).forEach((el, index) => {
|
|
523
|
+
const width = parseInt(el.getAttribute('width') || '0', 10);
|
|
524
|
+
const height = parseInt(el.getAttribute('height') || '0', 10);
|
|
525
|
+
const loading = el.getAttribute('loading');
|
|
526
|
+
const fetchPriority = el.getAttribute('fetchpriority');
|
|
369
527
|
if (width >= 400 || height >= 300) {
|
|
370
528
|
hasLargeImages = true;
|
|
371
529
|
}
|
|
@@ -376,10 +534,15 @@ export class SeoAnalyzer {
|
|
|
376
534
|
hasPriorityHints = true;
|
|
377
535
|
}
|
|
378
536
|
});
|
|
379
|
-
this
|
|
537
|
+
this.root
|
|
538
|
+
.querySelectorAll('[style*="background-image"]')
|
|
539
|
+
.slice(0, 3)
|
|
540
|
+
.forEach(() => {
|
|
380
541
|
hasLargeImages = true;
|
|
381
542
|
});
|
|
382
|
-
const imagesWithoutDimensions = this
|
|
543
|
+
const imagesWithoutDimensions = this.root.querySelectorAll('img:not([width]):not([height])').length +
|
|
544
|
+
this.root.querySelectorAll('img[width="auto"], img[height="auto"]')
|
|
545
|
+
.length;
|
|
383
546
|
return {
|
|
384
547
|
lcpHints: {
|
|
385
548
|
hasLargeImages,
|
|
@@ -393,65 +556,75 @@ export class SeoAnalyzer {
|
|
|
393
556
|
}
|
|
394
557
|
analyzeAccessibility() {
|
|
395
558
|
let buttonsWithoutAriaLabel = 0;
|
|
396
|
-
this
|
|
397
|
-
const
|
|
398
|
-
const
|
|
399
|
-
const
|
|
400
|
-
const
|
|
401
|
-
const title = $el.attr('title');
|
|
559
|
+
this.root.querySelectorAll('button').forEach((el) => {
|
|
560
|
+
const text = el.text.trim();
|
|
561
|
+
const ariaLabel = el.getAttribute('aria-label');
|
|
562
|
+
const ariaLabelledBy = el.getAttribute('aria-labelledby');
|
|
563
|
+
const title = el.getAttribute('title');
|
|
402
564
|
if (!text && !ariaLabel && !ariaLabelledBy && !title) {
|
|
403
565
|
buttonsWithoutAriaLabel++;
|
|
404
566
|
}
|
|
405
567
|
});
|
|
406
568
|
let linksWithoutAriaLabel = 0;
|
|
407
|
-
this
|
|
408
|
-
const
|
|
409
|
-
const
|
|
410
|
-
const
|
|
411
|
-
const
|
|
412
|
-
const
|
|
413
|
-
const
|
|
414
|
-
|
|
569
|
+
this.root.querySelectorAll('a[href]').forEach((el) => {
|
|
570
|
+
const text = el.text.trim();
|
|
571
|
+
const ariaLabel = el.getAttribute('aria-label');
|
|
572
|
+
const ariaLabelledBy = el.getAttribute('aria-labelledby');
|
|
573
|
+
const title = el.getAttribute('title');
|
|
574
|
+
const imgs = el.querySelectorAll('img');
|
|
575
|
+
const hasImgWithAlt = imgs.filter((img) => !!img.getAttribute('alt')?.trim()).length > 0;
|
|
576
|
+
const svgs = el.querySelectorAll('svg');
|
|
577
|
+
const hasSvgWithTitle = svgs.filter((svg) => svg.querySelectorAll('title').length > 0 ||
|
|
578
|
+
!!svg.getAttribute('aria-label')).length > 0;
|
|
579
|
+
const hasContent = text || hasImgWithAlt || hasSvgWithTitle;
|
|
580
|
+
const hasLabel = ariaLabel || ariaLabelledBy || title;
|
|
581
|
+
if (!hasContent && !hasLabel) {
|
|
415
582
|
linksWithoutAriaLabel++;
|
|
416
583
|
}
|
|
417
584
|
});
|
|
418
585
|
let inputsWithoutLabel = 0;
|
|
419
|
-
this
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const
|
|
423
|
-
const
|
|
424
|
-
const
|
|
425
|
-
const
|
|
426
|
-
const
|
|
427
|
-
const
|
|
428
|
-
|
|
586
|
+
this.root
|
|
587
|
+
.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]), select, textarea')
|
|
588
|
+
.forEach((el) => {
|
|
589
|
+
const id = el.getAttribute('id');
|
|
590
|
+
const ariaLabel = el.getAttribute('aria-label');
|
|
591
|
+
const ariaLabelledBy = el.getAttribute('aria-labelledby');
|
|
592
|
+
const placeholder = el.getAttribute('placeholder');
|
|
593
|
+
const title = el.getAttribute('title');
|
|
594
|
+
const hasLabel = id
|
|
595
|
+
? this.root.querySelectorAll(`label[for="${id}"]`).length > 0
|
|
596
|
+
: false;
|
|
597
|
+
const wrappedInLabel = el.closest('label') !== null;
|
|
598
|
+
if (!hasLabel &&
|
|
599
|
+
!wrappedInLabel &&
|
|
600
|
+
!ariaLabel &&
|
|
601
|
+
!ariaLabelledBy &&
|
|
602
|
+
!title &&
|
|
603
|
+
!placeholder) {
|
|
429
604
|
inputsWithoutLabel++;
|
|
430
605
|
}
|
|
431
606
|
});
|
|
432
|
-
const iframesWithoutTitle = this
|
|
607
|
+
const iframesWithoutTitle = this.root.querySelectorAll('iframe:not([title])').length;
|
|
433
608
|
let tablesWithoutCaption = 0;
|
|
434
|
-
this
|
|
435
|
-
const
|
|
436
|
-
const
|
|
437
|
-
const
|
|
438
|
-
const
|
|
439
|
-
const role = $el.attr('role');
|
|
609
|
+
this.root.querySelectorAll('table').forEach((el) => {
|
|
610
|
+
const hasCaption = el.querySelectorAll('caption').length > 0;
|
|
611
|
+
const ariaLabel = el.getAttribute('aria-label');
|
|
612
|
+
const ariaLabelledBy = el.getAttribute('aria-labelledby');
|
|
613
|
+
const role = el.getAttribute('role');
|
|
440
614
|
if (role === 'presentation' || role === 'none')
|
|
441
615
|
return;
|
|
442
|
-
const hasHeaders =
|
|
616
|
+
const hasHeaders = el.querySelectorAll('th').length > 0;
|
|
443
617
|
if (hasHeaders && !hasCaption && !ariaLabel && !ariaLabelledBy) {
|
|
444
618
|
tablesWithoutCaption++;
|
|
445
619
|
}
|
|
446
620
|
});
|
|
447
621
|
let svgsWithoutTitle = 0;
|
|
448
|
-
this
|
|
449
|
-
const
|
|
450
|
-
const
|
|
451
|
-
const
|
|
452
|
-
const
|
|
453
|
-
const
|
|
454
|
-
const role = $el.attr('role');
|
|
622
|
+
this.root.querySelectorAll('svg').forEach((el) => {
|
|
623
|
+
const hasTitle = el.querySelectorAll('title').length > 0;
|
|
624
|
+
const ariaLabel = el.getAttribute('aria-label');
|
|
625
|
+
const ariaLabelledBy = el.getAttribute('aria-labelledby');
|
|
626
|
+
const ariaHidden = el.getAttribute('aria-hidden');
|
|
627
|
+
const role = el.getAttribute('role');
|
|
455
628
|
if (ariaHidden === 'true' || role === 'presentation' || role === 'none')
|
|
456
629
|
return;
|
|
457
630
|
if (!hasTitle && !ariaLabel && !ariaLabelledBy) {
|
|
@@ -469,36 +642,271 @@ export class SeoAnalyzer {
|
|
|
469
642
|
}
|
|
470
643
|
analyzeStructuralHtml() {
|
|
471
644
|
return {
|
|
472
|
-
hasHeader: this
|
|
473
|
-
hasNav: this
|
|
474
|
-
hasMain: this
|
|
475
|
-
hasArticle: this
|
|
476
|
-
hasSection: this
|
|
477
|
-
hasFooter: this
|
|
645
|
+
hasHeader: this.root.querySelectorAll('header').length > 0,
|
|
646
|
+
hasNav: this.root.querySelectorAll('nav').length > 0,
|
|
647
|
+
hasMain: this.root.querySelectorAll('main').length > 0,
|
|
648
|
+
hasArticle: this.root.querySelectorAll('article').length > 0,
|
|
649
|
+
hasSection: this.root.querySelectorAll('section').length > 0,
|
|
650
|
+
hasFooter: this.root.querySelectorAll('footer').length > 0,
|
|
478
651
|
};
|
|
479
652
|
}
|
|
480
653
|
analyzeBreadcrumbs(jsonLdTypes) {
|
|
481
|
-
const hasBreadcrumbsHtml = this
|
|
654
|
+
const hasBreadcrumbsHtml = this.root.querySelectorAll('nav[aria-label="breadcrumb"], .breadcrumb, .breadcrumbs').length > 0;
|
|
482
655
|
const hasBreadcrumbsSchema = jsonLdTypes.includes('BreadcrumbList');
|
|
483
656
|
return { hasBreadcrumbsHtml, hasBreadcrumbsSchema };
|
|
484
657
|
}
|
|
485
658
|
analyzeMultimedia() {
|
|
659
|
+
const videos = this.root.querySelectorAll('video');
|
|
660
|
+
const audios = this.root.querySelectorAll('audio');
|
|
661
|
+
let hasAutoplay = false;
|
|
662
|
+
const checkAutoplay = (list) => {
|
|
663
|
+
list.forEach(el => {
|
|
664
|
+
if (el.getAttribute('autoplay') !== undefined || el.hasAttribute('autoplay')) {
|
|
665
|
+
hasAutoplay = true;
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
};
|
|
669
|
+
checkAutoplay(videos);
|
|
670
|
+
checkAutoplay(audios);
|
|
486
671
|
return {
|
|
487
|
-
videoCount:
|
|
488
|
-
audioCount:
|
|
672
|
+
videoCount: videos.length,
|
|
673
|
+
audioCount: audios.length,
|
|
674
|
+
hasAutoplay
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
analyzeSocialLinks(links, socialDomains) {
|
|
678
|
+
const socialLinks = links.filter(l => socialDomains.some(d => l.href.toLowerCase().includes(d)));
|
|
679
|
+
const platformMap = {
|
|
680
|
+
'facebook.com': 'facebook',
|
|
681
|
+
'twitter.com': 'twitter',
|
|
682
|
+
'x.com': 'twitter',
|
|
683
|
+
'instagram.com': 'instagram',
|
|
684
|
+
'linkedin.com': 'linkedin',
|
|
685
|
+
'youtube.com': 'youtube',
|
|
686
|
+
'pinterest.com': 'pinterest',
|
|
687
|
+
'tiktok.com': 'tiktok',
|
|
688
|
+
'github.com': 'github',
|
|
689
|
+
'reddit.com': 'reddit',
|
|
690
|
+
'snapchat.com': 'snapchat',
|
|
691
|
+
'whatsapp.com': 'whatsapp',
|
|
692
|
+
'telegram.org': 'telegram',
|
|
693
|
+
'discord.com': 'discord',
|
|
694
|
+
'threads.net': 'threads',
|
|
695
|
+
};
|
|
696
|
+
const socialLinkDetails = [];
|
|
697
|
+
let socialLinksInHeader = 0;
|
|
698
|
+
let socialLinksInFooter = 0;
|
|
699
|
+
let socialLinksWithoutAccessibility = 0;
|
|
700
|
+
let socialLinksWithoutNewTab = 0;
|
|
701
|
+
let socialLinksWithoutNoopener = 0;
|
|
702
|
+
const platformsFound = new Set();
|
|
703
|
+
const headerLinks = this.root.querySelectorAll('header a[href]');
|
|
704
|
+
const footerLinks = this.root.querySelectorAll('footer a[href]');
|
|
705
|
+
const headerHrefs = new Set(headerLinks.map(l => l.getAttribute('href') || ''));
|
|
706
|
+
const footerHrefs = new Set(footerLinks.map(l => l.getAttribute('href') || ''));
|
|
707
|
+
for (const link of socialLinks) {
|
|
708
|
+
let platform = 'unknown';
|
|
709
|
+
for (const [domain, name] of Object.entries(platformMap)) {
|
|
710
|
+
if (link.href.toLowerCase().includes(domain)) {
|
|
711
|
+
platform = name;
|
|
712
|
+
platformsFound.add(name);
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
let location = 'body';
|
|
717
|
+
if (headerHrefs.has(link.href)) {
|
|
718
|
+
location = 'header';
|
|
719
|
+
socialLinksInHeader++;
|
|
720
|
+
}
|
|
721
|
+
else if (footerHrefs.has(link.href)) {
|
|
722
|
+
location = 'footer';
|
|
723
|
+
socialLinksInFooter++;
|
|
724
|
+
}
|
|
725
|
+
const hasAccessibility = !!(link.ariaLabel || link.title || (link.text && link.text.trim()));
|
|
726
|
+
if (!hasAccessibility) {
|
|
727
|
+
socialLinksWithoutAccessibility++;
|
|
728
|
+
}
|
|
729
|
+
const hasNewTab = link.target === '_blank';
|
|
730
|
+
if (!hasNewTab) {
|
|
731
|
+
socialLinksWithoutNewTab++;
|
|
732
|
+
}
|
|
733
|
+
const hasNoopener = !!(link.rel && link.rel.includes('noopener'));
|
|
734
|
+
if (hasNewTab && !hasNoopener) {
|
|
735
|
+
socialLinksWithoutNoopener++;
|
|
736
|
+
}
|
|
737
|
+
socialLinkDetails.push({
|
|
738
|
+
href: link.href,
|
|
739
|
+
platform,
|
|
740
|
+
hasAccessibility,
|
|
741
|
+
hasNewTab,
|
|
742
|
+
hasNoopener,
|
|
743
|
+
location,
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
return {
|
|
747
|
+
totalSocialLinks: socialLinks.length,
|
|
748
|
+
socialLinksInHeader,
|
|
749
|
+
socialLinksInFooter,
|
|
750
|
+
socialLinksWithoutAccessibility,
|
|
751
|
+
socialLinksWithoutNewTab,
|
|
752
|
+
socialLinksWithoutNoopener,
|
|
753
|
+
platformsFound: Array.from(platformsFound),
|
|
754
|
+
socialLinkDetails,
|
|
489
755
|
};
|
|
490
756
|
}
|
|
491
757
|
analyzeTrustSignals(links) {
|
|
492
|
-
const linkHrefs = links.map(l => l.href.toLowerCase());
|
|
758
|
+
const linkHrefs = links.map((l) => l.href.toLowerCase());
|
|
493
759
|
return {
|
|
494
|
-
hasAboutPageLink: linkHrefs.some(href => href.includes('about') || href.includes('quem-somos')),
|
|
495
|
-
hasContactPageLink: linkHrefs.some(href => href.includes('contact') || href.includes('contato')),
|
|
496
|
-
hasPrivacyPolicyLink: linkHrefs.some(href => href.includes('privacy') || href.includes('privacidade')),
|
|
497
|
-
hasTermsOfServiceLink: linkHrefs.some(href => href.includes('terms') || href.includes('termos-de-uso')),
|
|
760
|
+
hasAboutPageLink: linkHrefs.some((href) => href.includes('about') || href.includes('quem-somos')),
|
|
761
|
+
hasContactPageLink: linkHrefs.some((href) => href.includes('contact') || href.includes('contato')),
|
|
762
|
+
hasPrivacyPolicyLink: linkHrefs.some((href) => href.includes('privacy') || href.includes('privacidade')),
|
|
763
|
+
hasTermsOfServiceLink: linkHrefs.some((href) => href.includes('terms') || href.includes('termos-de-uso')),
|
|
498
764
|
};
|
|
499
765
|
}
|
|
766
|
+
analyzeAnalytics() {
|
|
767
|
+
const providers = [];
|
|
768
|
+
const html = this.root.innerHTML || '';
|
|
769
|
+
const scripts = this.root.querySelectorAll('script');
|
|
770
|
+
const scriptSources = [];
|
|
771
|
+
const scriptContents = [];
|
|
772
|
+
scripts.forEach((s) => {
|
|
773
|
+
const src = s.getAttribute('src') || '';
|
|
774
|
+
const content = s.innerHTML || '';
|
|
775
|
+
if (src)
|
|
776
|
+
scriptSources.push(src.toLowerCase());
|
|
777
|
+
if (content)
|
|
778
|
+
scriptContents.push(content.toLowerCase());
|
|
779
|
+
});
|
|
780
|
+
const allScriptText = scriptSources.join(' ') + ' ' + scriptContents.join(' ');
|
|
781
|
+
if (allScriptText.includes('gtag') && allScriptText.includes('g-')) {
|
|
782
|
+
providers.push('Google Analytics 4 (GA4)');
|
|
783
|
+
}
|
|
784
|
+
if (allScriptText.includes('analytics.js') || allScriptText.includes('ga.js') ||
|
|
785
|
+
(allScriptText.includes('ua-') && !allScriptText.includes('g-'))) {
|
|
786
|
+
providers.push('Universal Analytics (UA)');
|
|
787
|
+
}
|
|
788
|
+
if (allScriptText.includes('googletagmanager.com/gtm') || allScriptText.includes('gtm-')) {
|
|
789
|
+
providers.push('Google Tag Manager');
|
|
790
|
+
}
|
|
791
|
+
if (allScriptText.includes('hotjar.com') || allScriptText.includes('hj(')) {
|
|
792
|
+
providers.push('Hotjar');
|
|
793
|
+
}
|
|
794
|
+
if (allScriptText.includes('clarity.ms') || allScriptText.includes('clarity(')) {
|
|
795
|
+
providers.push('Microsoft Clarity');
|
|
796
|
+
}
|
|
797
|
+
if (allScriptText.includes('fullstory.com') || allScriptText.includes('fs.identify')) {
|
|
798
|
+
providers.push('FullStory');
|
|
799
|
+
}
|
|
800
|
+
if (allScriptText.includes('luckyorange.com')) {
|
|
801
|
+
providers.push('Lucky Orange');
|
|
802
|
+
}
|
|
803
|
+
if (allScriptText.includes('crazyegg.com')) {
|
|
804
|
+
providers.push('Crazy Egg');
|
|
805
|
+
}
|
|
806
|
+
if (allScriptText.includes('mixpanel.com') || allScriptText.includes('mixpanel.init')) {
|
|
807
|
+
providers.push('Mixpanel');
|
|
808
|
+
}
|
|
809
|
+
if (allScriptText.includes('heap.io') || allScriptText.includes('heapanalytics')) {
|
|
810
|
+
providers.push('Heap');
|
|
811
|
+
}
|
|
812
|
+
if (allScriptText.includes('amplitude.com') || allScriptText.includes('amplitude.init')) {
|
|
813
|
+
providers.push('Amplitude');
|
|
814
|
+
}
|
|
815
|
+
if (allScriptText.includes('segment.com') || allScriptText.includes('analytics.load')) {
|
|
816
|
+
providers.push('Segment');
|
|
817
|
+
}
|
|
818
|
+
if (allScriptText.includes('plausible.io')) {
|
|
819
|
+
providers.push('Plausible');
|
|
820
|
+
}
|
|
821
|
+
if (allScriptText.includes('matomo') || allScriptText.includes('piwik')) {
|
|
822
|
+
providers.push('Matomo');
|
|
823
|
+
}
|
|
824
|
+
if (allScriptText.includes('posthog.com') || allScriptText.includes('posthog.init')) {
|
|
825
|
+
providers.push('PostHog');
|
|
826
|
+
}
|
|
827
|
+
if (allScriptText.includes('connect.facebook.net') || allScriptText.includes('fbq(')) {
|
|
828
|
+
providers.push('Facebook Pixel');
|
|
829
|
+
}
|
|
830
|
+
if (allScriptText.includes('snap.licdn.com') || allScriptText.includes('_linkedin_partner_id')) {
|
|
831
|
+
providers.push('LinkedIn Insight');
|
|
832
|
+
}
|
|
833
|
+
if (allScriptText.includes('static.ads-twitter.com') || allScriptText.includes('twq(')) {
|
|
834
|
+
providers.push('Twitter Pixel');
|
|
835
|
+
}
|
|
836
|
+
if (allScriptText.includes('pintrk(') || allScriptText.includes('pinterest.com/ct')) {
|
|
837
|
+
providers.push('Pinterest Tag');
|
|
838
|
+
}
|
|
839
|
+
return {
|
|
840
|
+
analyticsDetected: providers.length > 0,
|
|
841
|
+
analyticsProviders: providers,
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
analyzeFeeds() {
|
|
845
|
+
let hasRssFeed = false;
|
|
846
|
+
let rssFeedUrl;
|
|
847
|
+
let hasAtomFeed = false;
|
|
848
|
+
let atomFeedUrl;
|
|
849
|
+
const rssLink = this.root.querySelector('link[type="application/rss+xml"]');
|
|
850
|
+
if (rssLink) {
|
|
851
|
+
hasRssFeed = true;
|
|
852
|
+
rssFeedUrl = rssLink.getAttribute('href') || undefined;
|
|
853
|
+
}
|
|
854
|
+
const atomLink = this.root.querySelector('link[type="application/atom+xml"]');
|
|
855
|
+
if (atomLink) {
|
|
856
|
+
hasAtomFeed = true;
|
|
857
|
+
atomFeedUrl = atomLink.getAttribute('href') || undefined;
|
|
858
|
+
}
|
|
859
|
+
return { hasRssFeed, rssFeedUrl, hasAtomFeed, atomFeedUrl };
|
|
860
|
+
}
|
|
861
|
+
analyzeConversionElements(links, visibleText) {
|
|
862
|
+
const formCount = this.root.querySelectorAll('form').length;
|
|
863
|
+
const ctaPatterns = [
|
|
864
|
+
/^(get started|start|begin|try|sign up|register|subscribe|join|buy|purchase|order|add to cart|checkout|download|contact|request|schedule|book|reserve|learn more|read more|discover|explore|view|see|watch|listen|play|submit|send|apply|claim|grab|unlock|access)$/i,
|
|
865
|
+
/^(obter|começar|iniciar|experimentar|inscrever|registrar|assinar|entrar|comprar|pedir|adicionar|finalizar|baixar|contato|solicitar|agendar|reservar|saber mais|ler mais|descobrir|explorar|ver|assistir|ouvir|enviar|aplicar|reivindicar|acessar)$/i,
|
|
866
|
+
];
|
|
867
|
+
let ctaButtonsCount = 0;
|
|
868
|
+
this.root.querySelectorAll('button').forEach((btn) => {
|
|
869
|
+
const text = btn.text.trim().toLowerCase();
|
|
870
|
+
if (ctaPatterns.some(p => p.test(text))) {
|
|
871
|
+
ctaButtonsCount++;
|
|
872
|
+
}
|
|
873
|
+
});
|
|
874
|
+
this.root.querySelectorAll('a[class*="btn"], a[class*="button"], a[role="button"]').forEach((link) => {
|
|
875
|
+
const text = link.text.trim().toLowerCase();
|
|
876
|
+
if (ctaPatterns.some(p => p.test(text))) {
|
|
877
|
+
ctaButtonsCount++;
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
const hasWhatsAppLink = links.some(l => l.href.includes('wa.me') ||
|
|
881
|
+
l.href.includes('whatsapp.com') ||
|
|
882
|
+
l.href.includes('api.whatsapp.com'));
|
|
883
|
+
const phoneRegex = /(?:\+?\d{1,3}[-.\s]?)?\(?\d{2,4}\)?[-.\s]?\d{3,5}[-.\s]?\d{3,5}/g;
|
|
884
|
+
const hasPhoneOnPage = phoneRegex.test(visibleText) ||
|
|
885
|
+
links.some(l => l.href.startsWith('tel:'));
|
|
886
|
+
return {
|
|
887
|
+
ctaButtonsCount,
|
|
888
|
+
formCount,
|
|
889
|
+
hasWhatsAppLink,
|
|
890
|
+
hasPhoneOnPage,
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
analyzeAdvancedImages() {
|
|
894
|
+
let imagesWithSrcset = 0;
|
|
895
|
+
let largeBase64ImagesCount = 0;
|
|
896
|
+
const imgs = this.root.querySelectorAll('img');
|
|
897
|
+
imgs.forEach((img) => {
|
|
898
|
+
if (img.getAttribute('srcset') || (img.parentNode && img.parentNode.tagName === 'PICTURE')) {
|
|
899
|
+
imagesWithSrcset++;
|
|
900
|
+
}
|
|
901
|
+
const src = img.getAttribute('src') || '';
|
|
902
|
+
if (src.startsWith('data:image') && src.length > 5 * 1024) {
|
|
903
|
+
largeBase64ImagesCount++;
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
return { imagesWithSrcset, largeBase64ImagesCount };
|
|
907
|
+
}
|
|
500
908
|
calculateTextHtmlRatio(bodyTextLength) {
|
|
501
|
-
const htmlSize = this
|
|
909
|
+
const htmlSize = this.root.innerHTML?.length;
|
|
502
910
|
if (htmlSize && htmlSize > 0) {
|
|
503
911
|
return (bodyTextLength / htmlSize) * 100;
|
|
504
912
|
}
|
|
@@ -507,6 +915,7 @@ export class SeoAnalyzer {
|
|
|
507
915
|
convertToCheckResults(results) {
|
|
508
916
|
return results.map((r) => ({
|
|
509
917
|
name: r.name,
|
|
918
|
+
category: r.category,
|
|
510
919
|
status: r.status,
|
|
511
920
|
message: r.message,
|
|
512
921
|
value: r.value,
|
|
@@ -515,10 +924,10 @@ export class SeoAnalyzer {
|
|
|
515
924
|
}));
|
|
516
925
|
}
|
|
517
926
|
buildSummary(ruleResults, checks, data) {
|
|
518
|
-
const passed = checks.filter(c => c.status === 'pass').length;
|
|
519
|
-
const warnings = checks.filter(c => c.status === 'warn').length;
|
|
520
|
-
const errors = checks.filter(c => c.status === 'fail').length;
|
|
521
|
-
const infos = checks.filter(c => c.status === 'info').length;
|
|
927
|
+
const passed = checks.filter((c) => c.status === 'pass').length;
|
|
928
|
+
const warnings = checks.filter((c) => c.status === 'warn').length;
|
|
929
|
+
const errors = checks.filter((c) => c.status === 'fail').length;
|
|
930
|
+
const infos = checks.filter((c) => c.status === 'info').length;
|
|
522
931
|
const totalChecks = checks.length;
|
|
523
932
|
const scoringChecks = totalChecks - infos;
|
|
524
933
|
const passRate = scoringChecks > 0 ? Math.round((passed / scoringChecks) * 100) : 100;
|
|
@@ -536,7 +945,7 @@ export class SeoAnalyzer {
|
|
|
536
945
|
issuesByCategory[cat].errors++;
|
|
537
946
|
}
|
|
538
947
|
const topIssues = ruleResults
|
|
539
|
-
.filter(r => r.status === 'fail' || r.status === 'warn')
|
|
948
|
+
.filter((r) => r.status === 'fail' || r.status === 'warn')
|
|
540
949
|
.sort((a, b) => {
|
|
541
950
|
if (a.status === 'fail' && b.status !== 'fail')
|
|
542
951
|
return -1;
|
|
@@ -545,7 +954,7 @@ export class SeoAnalyzer {
|
|
|
545
954
|
return 0;
|
|
546
955
|
})
|
|
547
956
|
.slice(0, 5)
|
|
548
|
-
.map(r => ({
|
|
957
|
+
.map((r) => ({
|
|
549
958
|
name: r.name,
|
|
550
959
|
message: r.message,
|
|
551
960
|
category: r.category,
|
|
@@ -569,8 +978,8 @@ export class SeoAnalyzer {
|
|
|
569
978
|
if (data.linkAnalysis.withoutText > 0)
|
|
570
979
|
quickWins.push(`Add text to ${data.linkAnalysis.withoutText} empty link(s)`);
|
|
571
980
|
const limitedQuickWins = quickWins.slice(0, 5);
|
|
572
|
-
const htmlSize = this
|
|
573
|
-
const domElements = this
|
|
981
|
+
const htmlSize = this.root.innerHTML?.length;
|
|
982
|
+
const domElements = this.root.querySelectorAll('*').length;
|
|
574
983
|
const vitals = {
|
|
575
984
|
htmlSize,
|
|
576
985
|
domElements,
|
|
@@ -703,14 +1112,23 @@ export class SeoAnalyzer {
|
|
|
703
1112
|
const issues = [];
|
|
704
1113
|
const structure = [];
|
|
705
1114
|
const sectionWordCounts = [];
|
|
706
|
-
const counts = {
|
|
1115
|
+
const counts = {
|
|
1116
|
+
1: 0,
|
|
1117
|
+
2: 0,
|
|
1118
|
+
3: 0,
|
|
1119
|
+
4: 0,
|
|
1120
|
+
5: 0,
|
|
1121
|
+
6: 0,
|
|
1122
|
+
};
|
|
707
1123
|
let currentSectionWordCount = 0;
|
|
708
1124
|
let inSection = false;
|
|
709
|
-
this
|
|
1125
|
+
const body = this.getMainBody();
|
|
1126
|
+
const allElements = body ? body.querySelectorAll('*') : [];
|
|
1127
|
+
allElements.forEach((el) => {
|
|
710
1128
|
const tagName = el.tagName.toLowerCase();
|
|
711
1129
|
if (tagName.match(/^h[1-6]$/)) {
|
|
712
1130
|
const level = parseInt(tagName.substring(1), 10);
|
|
713
|
-
const text =
|
|
1131
|
+
const text = el.text.trim();
|
|
714
1132
|
counts[level] = (counts[level] || 0) + 1;
|
|
715
1133
|
structure.push({ level, text: text.slice(0, 80), count: 1 });
|
|
716
1134
|
if (level === 2) {
|
|
@@ -721,8 +1139,14 @@ export class SeoAnalyzer {
|
|
|
721
1139
|
inSection = true;
|
|
722
1140
|
}
|
|
723
1141
|
}
|
|
724
|
-
else if (inSection &&
|
|
725
|
-
|
|
1142
|
+
else if (inSection &&
|
|
1143
|
+
['p', 'ul', 'ol', 'div', 'article', 'section'].includes(tagName)) {
|
|
1144
|
+
let text = '';
|
|
1145
|
+
el.childNodes.forEach(n => {
|
|
1146
|
+
if (n.nodeType === 3)
|
|
1147
|
+
text += n.text;
|
|
1148
|
+
});
|
|
1149
|
+
text = text.trim();
|
|
726
1150
|
if (text.length > 0) {
|
|
727
1151
|
currentSectionWordCount += text.split(/\s+/).length;
|
|
728
1152
|
}
|
|
@@ -755,18 +1179,19 @@ export class SeoAnalyzer {
|
|
|
755
1179
|
};
|
|
756
1180
|
}
|
|
757
1181
|
analyzeContent(headings) {
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
const bodyText = $body.text().replace(/\s+/g, ' ').trim();
|
|
1182
|
+
const body = this.getMainBody();
|
|
1183
|
+
const bodyText = body ? body.text.replace(/\s+/g, ' ').trim() : '';
|
|
761
1184
|
const words = bodyText.split(/\s+/).filter((w) => w.length > 0);
|
|
762
|
-
const sentences = bodyText
|
|
763
|
-
|
|
1185
|
+
const sentences = bodyText
|
|
1186
|
+
.split(/[.!?]+/)
|
|
1187
|
+
.filter((s) => s.trim().length > 0);
|
|
1188
|
+
const paragraphs = this.root.querySelectorAll('p');
|
|
764
1189
|
let totalParagraphLength = 0;
|
|
765
1190
|
const paragraphWordCounts = [];
|
|
766
|
-
paragraphs.
|
|
767
|
-
const text =
|
|
1191
|
+
paragraphs.forEach((el) => {
|
|
1192
|
+
const text = el.text.trim();
|
|
768
1193
|
totalParagraphLength += text.length;
|
|
769
|
-
const pWords = text.split(/\s+/).filter(w => w.length > 0).length;
|
|
1194
|
+
const pWords = text.split(/\s+/).filter((w) => w.length > 0).length;
|
|
770
1195
|
if (pWords > 0)
|
|
771
1196
|
paragraphWordCounts.push(pWords);
|
|
772
1197
|
});
|
|
@@ -774,15 +1199,17 @@ export class SeoAnalyzer {
|
|
|
774
1199
|
const readingTimeMinutes = Math.ceil(wordCount / 200);
|
|
775
1200
|
const avgWordsPerSentence = sentences.length > 0 ? Math.round(wordCount / sentences.length) : 0;
|
|
776
1201
|
let faqCount = 0;
|
|
777
|
-
headings.structure.forEach(h => {
|
|
778
|
-
if (h.level === 3 &&
|
|
1202
|
+
headings.structure.forEach((h) => {
|
|
1203
|
+
if (h.level === 3 &&
|
|
1204
|
+
/^(what|how|why|when|where|who|can|do|is|are)\b/i.test(h.text)) {
|
|
779
1205
|
faqCount++;
|
|
780
1206
|
}
|
|
781
1207
|
});
|
|
782
|
-
const imageCount = this
|
|
1208
|
+
const imageCount = this.root.querySelectorAll('img').length;
|
|
783
1209
|
const imagePerWordRatio = wordCount > 0 ? imageCount / wordCount : 0;
|
|
784
1210
|
const fleschReadingEase = undefined;
|
|
785
|
-
const hasQuestionHeadings = headings.structure.some((h) => (h.level === 2 || h.level === 3) &&
|
|
1211
|
+
const hasQuestionHeadings = headings.structure.some((h) => (h.level === 2 || h.level === 3) &&
|
|
1212
|
+
/^(what|how|why|when|where|who|can|do|is|are)\b/i.test(h.text));
|
|
786
1213
|
return {
|
|
787
1214
|
wordCount,
|
|
788
1215
|
characterCount: bodyText.length,
|
|
@@ -790,10 +1217,12 @@ export class SeoAnalyzer {
|
|
|
790
1217
|
paragraphCount: paragraphs.length,
|
|
791
1218
|
readingTimeMinutes,
|
|
792
1219
|
avgWordsPerSentence,
|
|
793
|
-
avgParagraphLength: paragraphs.length > 0
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
1220
|
+
avgParagraphLength: paragraphs.length > 0
|
|
1221
|
+
? Math.round(totalParagraphLength / paragraphs.length)
|
|
1222
|
+
: 0,
|
|
1223
|
+
listCount: this.root.querySelectorAll('ul, ol').length,
|
|
1224
|
+
strongTagCount: this.root.querySelectorAll('strong').length,
|
|
1225
|
+
emTagCount: this.root.querySelectorAll('em').length,
|
|
797
1226
|
paragraphWordCounts,
|
|
798
1227
|
avgSentenceLength: avgWordsPerSentence,
|
|
799
1228
|
faqCount,
|
|
@@ -803,6 +1232,9 @@ export class SeoAnalyzer {
|
|
|
803
1232
|
};
|
|
804
1233
|
}
|
|
805
1234
|
buildLinkAnalysis(links) {
|
|
1235
|
+
const internalHttpLinkUrls = links
|
|
1236
|
+
.filter((l) => l.type === 'internal' && l.href.startsWith('http://'))
|
|
1237
|
+
.map((l) => l.href);
|
|
806
1238
|
return {
|
|
807
1239
|
total: links.length,
|
|
808
1240
|
internal: links.filter((l) => l.type === 'internal').length,
|
|
@@ -812,6 +1244,8 @@ export class SeoAnalyzer {
|
|
|
812
1244
|
ugcLinks: links.filter((l) => l.rel?.includes('ugc')).length,
|
|
813
1245
|
broken: 0,
|
|
814
1246
|
withoutText: links.filter((l) => !l.text?.trim()).length,
|
|
1247
|
+
internalHttpLinks: internalHttpLinkUrls.length,
|
|
1248
|
+
internalHttpLinkUrls,
|
|
815
1249
|
};
|
|
816
1250
|
}
|
|
817
1251
|
buildImageAnalysis(images) {
|
|
@@ -821,9 +1255,12 @@ export class SeoAnalyzer {
|
|
|
821
1255
|
withoutAlt: images.filter((i) => !i.alt || i.alt.trim().length === 0).length,
|
|
822
1256
|
lazy: images.filter((i) => i.loading === 'lazy').length,
|
|
823
1257
|
missingDimensions: images.filter((i) => !i.width || !i.height).length,
|
|
824
|
-
modernFormats: images.filter((i) => /\.(webp|avif)$/i.test(i.src))
|
|
825
|
-
|
|
826
|
-
|
|
1258
|
+
modernFormats: images.filter((i) => /\.(webp|avif)$/i.test(i.src))
|
|
1259
|
+
.length,
|
|
1260
|
+
altTextLengths: images.filter((i) => i.alt).map((i) => i.alt.length),
|
|
1261
|
+
imageAltTexts: images.filter((i) => i.alt).map((i) => i.alt.toLowerCase()),
|
|
1262
|
+
imageFilenames: images
|
|
1263
|
+
.map((i) => {
|
|
827
1264
|
try {
|
|
828
1265
|
const url = new URL(i.src);
|
|
829
1266
|
return url.pathname.split('/').pop() || '';
|
|
@@ -831,8 +1268,10 @@ export class SeoAnalyzer {
|
|
|
831
1268
|
catch {
|
|
832
1269
|
return '';
|
|
833
1270
|
}
|
|
834
|
-
})
|
|
835
|
-
|
|
1271
|
+
})
|
|
1272
|
+
.filter(Boolean),
|
|
1273
|
+
imagesWithAsyncDecoding: images.filter((i) => i.decoding === 'async')
|
|
1274
|
+
.length,
|
|
836
1275
|
};
|
|
837
1276
|
}
|
|
838
1277
|
buildSocialAnalysis(og, twitter) {
|
|
@@ -884,7 +1323,8 @@ export class SeoAnalyzer {
|
|
|
884
1323
|
};
|
|
885
1324
|
}
|
|
886
1325
|
buildTechnicalAnalysis(meta) {
|
|
887
|
-
const
|
|
1326
|
+
const html = this.root.querySelector('html');
|
|
1327
|
+
const htmlLang = html ? html.getAttribute('lang') : undefined;
|
|
888
1328
|
return {
|
|
889
1329
|
hasCanonical: !!meta.canonical,
|
|
890
1330
|
canonicalUrl: meta.canonical,
|
|
@@ -896,6 +1336,27 @@ export class SeoAnalyzer {
|
|
|
896
1336
|
langValue: htmlLang,
|
|
897
1337
|
};
|
|
898
1338
|
}
|
|
1339
|
+
analyzeResources() {
|
|
1340
|
+
const scripts = this.root.querySelectorAll('script[src]');
|
|
1341
|
+
const styles = this.root.querySelectorAll('link[rel="stylesheet"]');
|
|
1342
|
+
const unminified = [];
|
|
1343
|
+
scripts.forEach((s) => {
|
|
1344
|
+
const src = s.getAttribute('src');
|
|
1345
|
+
if (src && !src.includes('.min.') && !src.includes('cdn'))
|
|
1346
|
+
unminified.push(src);
|
|
1347
|
+
});
|
|
1348
|
+
styles.forEach((s) => {
|
|
1349
|
+
const href = s.getAttribute('href');
|
|
1350
|
+
if (href && !href.includes('.min.') && !href.includes('cdn'))
|
|
1351
|
+
unminified.push(href);
|
|
1352
|
+
});
|
|
1353
|
+
return {
|
|
1354
|
+
jsFilesCount: scripts.length,
|
|
1355
|
+
cssFilesCount: styles.length,
|
|
1356
|
+
unminifiedResources: unminified.length,
|
|
1357
|
+
unminifiedResourceUrls: unminified
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
899
1360
|
calculateScore(checks) {
|
|
900
1361
|
const weights = {
|
|
901
1362
|
pass: 100,
|
|
@@ -935,4 +1396,4 @@ export async function analyzeSeo(html, options = {}) {
|
|
|
935
1396
|
const analyzer = await SeoAnalyzer.fromHtml(html, options);
|
|
936
1397
|
return analyzer.analyze();
|
|
937
1398
|
}
|
|
938
|
-
export { SEO_THRESHOLDS, createRulesEngine, SeoRulesEngine } from './rules/index.js';
|
|
1399
|
+
export { SEO_THRESHOLDS, createRulesEngine, SeoRulesEngine, } from './rules/index.js';
|