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.
Files changed (459) hide show
  1. package/README.md +47 -0
  2. package/dist/bin/recker-linux-x64 +0 -0
  3. package/dist/bin/recker-macos-x64 +0 -0
  4. package/dist/bin/recker-win-x64.exe +0 -0
  5. package/dist/bin/rek.cjs +85152 -100207
  6. package/dist/browser/ai/adaptive-timeout.d.ts +50 -0
  7. package/dist/browser/ai/adaptive-timeout.js +208 -0
  8. package/dist/browser/ai/client.d.ts +22 -0
  9. package/dist/browser/ai/client.js +294 -0
  10. package/dist/browser/ai/index.d.ts +14 -0
  11. package/dist/browser/ai/index.js +11 -0
  12. package/dist/browser/ai/providers/anthropic.d.ts +63 -0
  13. package/dist/browser/ai/providers/anthropic.js +370 -0
  14. package/dist/browser/ai/providers/base.d.ts +48 -0
  15. package/dist/browser/ai/providers/base.js +150 -0
  16. package/dist/browser/ai/providers/google.d.ts +59 -0
  17. package/dist/browser/ai/providers/google.js +305 -0
  18. package/dist/browser/ai/providers/ollama.d.ts +44 -0
  19. package/dist/browser/ai/providers/ollama.js +240 -0
  20. package/dist/browser/ai/providers/openai.d.ts +64 -0
  21. package/dist/browser/ai/providers/openai.js +298 -0
  22. package/dist/browser/ai/rate-limiter.d.ts +43 -0
  23. package/dist/browser/ai/rate-limiter.js +215 -0
  24. package/dist/browser/ai/vector/index.d.ts +2 -0
  25. package/dist/browser/ai/vector/index.js +2 -0
  26. package/dist/browser/ai/vector/similarity.d.ts +2 -0
  27. package/dist/browser/ai/vector/similarity.js +27 -0
  28. package/dist/browser/ai/vector/store.d.ts +27 -0
  29. package/dist/browser/ai/vector/store.js +82 -0
  30. package/dist/browser/browser/cache.d.ts +2 -40
  31. package/dist/browser/browser/cache.js +2 -199
  32. package/dist/browser/browser/index.d.ts +8 -0
  33. package/dist/browser/browser/index.js +8 -0
  34. package/dist/browser/browser/recker.d.ts +8 -1
  35. package/dist/browser/browser/recker.js +8 -2
  36. package/dist/browser/cache/indexed-db.d.ts +10 -0
  37. package/dist/browser/cache/indexed-db.js +88 -0
  38. package/dist/browser/cache/service-worker-cache.d.ts +18 -0
  39. package/dist/browser/cache/service-worker-cache.js +103 -0
  40. package/dist/browser/cache.d.ts +2 -40
  41. package/dist/browser/cache.js +2 -199
  42. package/dist/browser/constants/user-agents.d.ts +7 -0
  43. package/dist/browser/constants/user-agents.js +7 -0
  44. package/dist/browser/core/client.d.ts +2 -0
  45. package/dist/browser/core/client.js +19 -1
  46. package/dist/browser/index.d.ts +8 -0
  47. package/dist/browser/index.js +8 -0
  48. package/dist/browser/plugins/har-recorder.d.ts +40 -0
  49. package/dist/browser/plugins/har-recorder.js +120 -0
  50. package/dist/browser/plugins/network-simulation.d.ts +7 -0
  51. package/dist/browser/plugins/network-simulation.js +13 -0
  52. package/dist/browser/presets/android.d.ts +2 -0
  53. package/dist/browser/presets/android.js +16 -0
  54. package/dist/browser/presets/anthropic.d.ts +8 -0
  55. package/dist/browser/presets/anthropic.js +27 -0
  56. package/dist/browser/presets/aws.d.ts +19 -0
  57. package/dist/browser/presets/aws.js +68 -0
  58. package/dist/browser/presets/azure-openai.d.ts +10 -0
  59. package/dist/browser/presets/azure-openai.js +35 -0
  60. package/dist/browser/presets/azure.d.ts +41 -0
  61. package/dist/browser/presets/azure.js +104 -0
  62. package/dist/browser/presets/chaturbate.d.ts +2 -0
  63. package/dist/browser/presets/chaturbate.js +17 -0
  64. package/dist/browser/presets/cloudflare.d.ts +12 -0
  65. package/dist/browser/presets/cloudflare.js +39 -0
  66. package/dist/browser/presets/cohere.d.ts +7 -0
  67. package/dist/browser/presets/cohere.js +22 -0
  68. package/dist/browser/presets/deepseek.d.ts +7 -0
  69. package/dist/browser/presets/deepseek.js +22 -0
  70. package/dist/browser/presets/digitalocean.d.ts +5 -0
  71. package/dist/browser/presets/digitalocean.js +16 -0
  72. package/dist/browser/presets/discord.d.ts +6 -0
  73. package/dist/browser/presets/discord.js +17 -0
  74. package/dist/browser/presets/elevenlabs.d.ts +6 -0
  75. package/dist/browser/presets/elevenlabs.js +20 -0
  76. package/dist/browser/presets/enhancers.d.ts +20 -0
  77. package/dist/browser/presets/enhancers.js +85 -0
  78. package/dist/browser/presets/fireworks.d.ts +7 -0
  79. package/dist/browser/presets/fireworks.js +22 -0
  80. package/dist/browser/presets/gcp.d.ts +34 -0
  81. package/dist/browser/presets/gcp.js +91 -0
  82. package/dist/browser/presets/gemini.d.ts +7 -0
  83. package/dist/browser/presets/gemini.js +23 -0
  84. package/dist/browser/presets/github.d.ts +6 -0
  85. package/dist/browser/presets/github.js +17 -0
  86. package/dist/browser/presets/gitlab.d.ts +6 -0
  87. package/dist/browser/presets/gitlab.js +16 -0
  88. package/dist/browser/presets/groq.d.ts +7 -0
  89. package/dist/browser/presets/groq.js +22 -0
  90. package/dist/browser/presets/hubspot.d.ts +9 -0
  91. package/dist/browser/presets/hubspot.js +28 -0
  92. package/dist/browser/presets/huggingface.d.ts +7 -0
  93. package/dist/browser/presets/huggingface.js +23 -0
  94. package/dist/browser/presets/index.d.ts +47 -0
  95. package/dist/browser/presets/index.js +47 -0
  96. package/dist/browser/presets/ios.d.ts +2 -0
  97. package/dist/browser/presets/ios.js +13 -0
  98. package/dist/browser/presets/linear.d.ts +5 -0
  99. package/dist/browser/presets/linear.js +16 -0
  100. package/dist/browser/presets/mailgun.d.ts +7 -0
  101. package/dist/browser/presets/mailgun.js +20 -0
  102. package/dist/browser/presets/meta.d.ts +10 -0
  103. package/dist/browser/presets/meta.js +33 -0
  104. package/dist/browser/presets/mistral.d.ts +7 -0
  105. package/dist/browser/presets/mistral.js +22 -0
  106. package/dist/browser/presets/notion.d.ts +6 -0
  107. package/dist/browser/presets/notion.js +17 -0
  108. package/dist/browser/presets/openai.d.ts +9 -0
  109. package/dist/browser/presets/openai.js +30 -0
  110. package/dist/browser/presets/oracle.d.ts +19 -0
  111. package/dist/browser/presets/oracle.js +117 -0
  112. package/dist/browser/presets/perplexity.d.ts +7 -0
  113. package/dist/browser/presets/perplexity.js +22 -0
  114. package/dist/browser/presets/pinecone.d.ts +8 -0
  115. package/dist/browser/presets/pinecone.js +42 -0
  116. package/dist/browser/presets/registry.d.ts +23 -0
  117. package/dist/browser/presets/registry.js +519 -0
  118. package/dist/browser/presets/replicate.d.ts +7 -0
  119. package/dist/browser/presets/replicate.js +23 -0
  120. package/dist/browser/presets/sendgrid.d.ts +6 -0
  121. package/dist/browser/presets/sendgrid.js +20 -0
  122. package/dist/browser/presets/sentry.d.ts +11 -0
  123. package/dist/browser/presets/sentry.js +48 -0
  124. package/dist/browser/presets/sinch.d.ts +9 -0
  125. package/dist/browser/presets/sinch.js +39 -0
  126. package/dist/browser/presets/slack.d.ts +5 -0
  127. package/dist/browser/presets/slack.js +16 -0
  128. package/dist/browser/presets/square.d.ts +10 -0
  129. package/dist/browser/presets/square.js +33 -0
  130. package/dist/browser/presets/stripe.d.ts +7 -0
  131. package/dist/browser/presets/stripe.js +23 -0
  132. package/dist/browser/presets/supabase.d.ts +6 -0
  133. package/dist/browser/presets/supabase.js +18 -0
  134. package/dist/browser/presets/tiktok.d.ts +10 -0
  135. package/dist/browser/presets/tiktok.js +38 -0
  136. package/dist/browser/presets/together.d.ts +7 -0
  137. package/dist/browser/presets/together.js +22 -0
  138. package/dist/browser/presets/twilio.d.ts +6 -0
  139. package/dist/browser/presets/twilio.js +17 -0
  140. package/dist/browser/presets/vercel.d.ts +6 -0
  141. package/dist/browser/presets/vercel.js +23 -0
  142. package/dist/browser/presets/vultr.d.ts +5 -0
  143. package/dist/browser/presets/vultr.js +16 -0
  144. package/dist/browser/presets/xai.d.ts +8 -0
  145. package/dist/browser/presets/xai.js +23 -0
  146. package/dist/browser/presets/youtube.d.ts +5 -0
  147. package/dist/browser/presets/youtube.js +20 -0
  148. package/dist/browser/recker.d.ts +8 -1
  149. package/dist/browser/recker.js +8 -2
  150. package/dist/browser/scrape/document.d.ts +5 -4
  151. package/dist/browser/scrape/document.js +89 -76
  152. package/dist/browser/scrape/element.d.ts +10 -8
  153. package/dist/browser/scrape/element.js +295 -81
  154. package/dist/browser/scrape/extractors.d.ts +11 -11
  155. package/dist/browser/scrape/extractors.js +145 -113
  156. package/dist/browser/scrape/parser/back.d.ts +1 -0
  157. package/dist/browser/scrape/parser/back.js +3 -0
  158. package/dist/browser/scrape/parser/index.d.ts +20 -0
  159. package/dist/browser/scrape/parser/index.js +19 -0
  160. package/dist/browser/scrape/parser/matcher.d.ts +30 -0
  161. package/dist/browser/scrape/parser/matcher.js +99 -0
  162. package/dist/browser/scrape/parser/nodes/comment.d.ts +12 -0
  163. package/dist/browser/scrape/parser/nodes/comment.js +21 -0
  164. package/dist/browser/scrape/parser/nodes/html.d.ts +110 -0
  165. package/dist/browser/scrape/parser/nodes/html.js +978 -0
  166. package/dist/browser/scrape/parser/nodes/node.d.ts +18 -0
  167. package/dist/browser/scrape/parser/nodes/node.js +31 -0
  168. package/dist/browser/scrape/parser/nodes/text.d.ts +14 -0
  169. package/dist/browser/scrape/parser/nodes/text.js +30 -0
  170. package/dist/browser/scrape/parser/nodes/type.d.ts +6 -0
  171. package/dist/browser/scrape/parser/nodes/type.js +7 -0
  172. package/dist/browser/scrape/parser/parse.d.ts +1 -0
  173. package/dist/browser/scrape/parser/parse.js +1 -0
  174. package/dist/browser/scrape/parser/valid.d.ts +2 -0
  175. package/dist/browser/scrape/parser/valid.js +5 -0
  176. package/dist/browser/scrape/parser/void-tag.d.ts +7 -0
  177. package/dist/browser/scrape/parser/void-tag.js +43 -0
  178. package/dist/browser/scrape/types.d.ts +7 -0
  179. package/dist/browser/seo/analyzer.d.ts +59 -0
  180. package/dist/browser/seo/analyzer.js +1399 -0
  181. package/dist/browser/seo/keywords.d.ts +16 -0
  182. package/dist/browser/seo/keywords.js +55 -0
  183. package/dist/browser/seo/rules/accessibility.d.ts +2 -0
  184. package/dist/browser/seo/rules/accessibility.js +733 -0
  185. package/dist/browser/seo/rules/ai-search.d.ts +2 -0
  186. package/dist/browser/seo/rules/ai-search.js +436 -0
  187. package/dist/browser/seo/rules/analytics.d.ts +2 -0
  188. package/dist/browser/seo/rules/analytics.js +306 -0
  189. package/dist/browser/seo/rules/best-practices.d.ts +2 -0
  190. package/dist/browser/seo/rules/best-practices.js +195 -0
  191. package/dist/browser/seo/rules/canonical.d.ts +12 -0
  192. package/dist/browser/seo/rules/canonical.js +270 -0
  193. package/dist/browser/seo/rules/content.d.ts +2 -0
  194. package/dist/browser/seo/rules/content.js +522 -0
  195. package/dist/browser/seo/rules/crawl.d.ts +2 -0
  196. package/dist/browser/seo/rules/crawl.js +435 -0
  197. package/dist/browser/seo/rules/cwv.d.ts +2 -0
  198. package/dist/browser/seo/rules/cwv.js +248 -0
  199. package/dist/browser/seo/rules/ecommerce.d.ts +2 -0
  200. package/dist/browser/seo/rules/ecommerce.js +312 -0
  201. package/dist/browser/seo/rules/i18n.d.ts +2 -0
  202. package/dist/browser/seo/rules/i18n.js +288 -0
  203. package/dist/browser/seo/rules/images.d.ts +2 -0
  204. package/dist/browser/seo/rules/images.js +255 -0
  205. package/dist/browser/seo/rules/index.d.ts +52 -0
  206. package/dist/browser/seo/rules/index.js +159 -0
  207. package/dist/browser/seo/rules/internal-linking.d.ts +2 -0
  208. package/dist/browser/seo/rules/internal-linking.js +394 -0
  209. package/dist/browser/seo/rules/links.d.ts +2 -0
  210. package/dist/browser/seo/rules/links.js +498 -0
  211. package/dist/browser/seo/rules/local.d.ts +2 -0
  212. package/dist/browser/seo/rules/local.js +289 -0
  213. package/dist/browser/seo/rules/meta.d.ts +2 -0
  214. package/dist/browser/seo/rules/meta.js +805 -0
  215. package/dist/browser/seo/rules/mobile.d.ts +2 -0
  216. package/dist/browser/seo/rules/mobile.js +161 -0
  217. package/dist/browser/seo/rules/performance.d.ts +2 -0
  218. package/dist/browser/seo/rules/performance.js +738 -0
  219. package/dist/browser/seo/rules/pwa.d.ts +2 -0
  220. package/dist/browser/seo/rules/pwa.js +299 -0
  221. package/dist/browser/seo/rules/readability.d.ts +2 -0
  222. package/dist/browser/seo/rules/readability.js +264 -0
  223. package/dist/browser/seo/rules/redirects.d.ts +16 -0
  224. package/dist/browser/seo/rules/redirects.js +199 -0
  225. package/dist/browser/seo/rules/resources.d.ts +2 -0
  226. package/dist/browser/seo/rules/resources.js +390 -0
  227. package/dist/browser/seo/rules/schema.d.ts +2 -0
  228. package/dist/browser/seo/rules/schema.js +379 -0
  229. package/dist/browser/seo/rules/security.d.ts +2 -0
  230. package/dist/browser/seo/rules/security.js +877 -0
  231. package/dist/browser/seo/rules/social.d.ts +2 -0
  232. package/dist/browser/seo/rules/social.js +603 -0
  233. package/dist/browser/seo/rules/structural.d.ts +2 -0
  234. package/dist/browser/seo/rules/structural.js +223 -0
  235. package/dist/browser/seo/rules/technical-advanced.d.ts +10 -0
  236. package/dist/browser/seo/rules/technical-advanced.js +289 -0
  237. package/dist/browser/seo/rules/technical.d.ts +2 -0
  238. package/dist/browser/seo/rules/technical.js +480 -0
  239. package/dist/browser/seo/rules/thresholds.d.ts +196 -0
  240. package/dist/browser/seo/rules/thresholds.js +118 -0
  241. package/dist/browser/seo/rules/types.d.ts +498 -0
  242. package/dist/browser/seo/rules/types.js +11 -0
  243. package/dist/browser/seo/types.d.ts +211 -0
  244. package/dist/browser/seo/types.js +1 -0
  245. package/dist/browser/transport/curl.d.ts +4 -0
  246. package/dist/browser/transport/curl.js +101 -0
  247. package/dist/browser/transport/undici.js +1 -2
  248. package/dist/browser/transport/worker.d.ts +18 -0
  249. package/dist/browser/transport/worker.js +278 -0
  250. package/dist/browser/types/index.d.ts +4 -1
  251. package/dist/browser/utils/binary-manager.d.ts +4 -0
  252. package/dist/browser/utils/binary-manager.js +72 -0
  253. package/dist/browser/utils/user-agent.js +2 -13
  254. package/dist/cache/indexed-db.d.ts +10 -0
  255. package/dist/cache/indexed-db.js +88 -0
  256. package/dist/cache/service-worker-cache.d.ts +18 -0
  257. package/dist/cache/service-worker-cache.js +103 -0
  258. package/dist/cli/commands/ai.d.ts +2 -0
  259. package/dist/cli/commands/ai.js +162 -0
  260. package/dist/cli/commands/bench.d.ts +2 -0
  261. package/dist/cli/commands/bench.js +51 -0
  262. package/dist/cli/commands/dns.d.ts +2 -0
  263. package/dist/cli/commands/dns.js +295 -0
  264. package/dist/cli/commands/har.d.ts +2 -0
  265. package/dist/cli/commands/har.js +171 -0
  266. package/dist/cli/commands/hls.d.ts +2 -0
  267. package/dist/cli/commands/hls.js +192 -0
  268. package/dist/cli/commands/network.d.ts +2 -0
  269. package/dist/cli/commands/network.js +288 -0
  270. package/dist/cli/commands/protocols.d.ts +2 -0
  271. package/dist/cli/commands/protocols.js +344 -0
  272. package/dist/cli/commands/scrape.d.ts +2 -0
  273. package/dist/cli/commands/scrape.js +176 -0
  274. package/dist/cli/commands/security.d.ts +2 -0
  275. package/dist/cli/commands/security.js +57 -0
  276. package/dist/cli/commands/seo.d.ts +2 -0
  277. package/dist/cli/commands/seo.js +125 -0
  278. package/dist/cli/commands/serve.d.ts +2 -0
  279. package/dist/cli/commands/serve.js +531 -0
  280. package/dist/cli/commands/spider.d.ts +3 -0
  281. package/dist/cli/commands/spider.js +456 -0
  282. package/dist/cli/commands/utils.d.ts +2 -0
  283. package/dist/cli/commands/utils.js +176 -0
  284. package/dist/cli/commands/vector.d.ts +2 -0
  285. package/dist/cli/commands/vector.js +158 -0
  286. package/dist/cli/handler.d.ts +2 -2
  287. package/dist/cli/handler.js +6 -6
  288. package/dist/cli/helpers.d.ts +7 -0
  289. package/dist/cli/helpers.js +128 -0
  290. package/dist/cli/index.js +96 -5228
  291. package/dist/cli/parser/help.d.ts +2 -0
  292. package/dist/cli/parser/help.js +52 -0
  293. package/dist/cli/parser/index.d.ts +3 -0
  294. package/dist/cli/parser/index.js +3 -0
  295. package/dist/cli/parser/parser.d.ts +4 -0
  296. package/dist/cli/parser/parser.js +146 -0
  297. package/dist/cli/parser/types.d.ts +41 -0
  298. package/dist/cli/parser/types.js +1 -0
  299. package/dist/cli/presets.d.ts +1 -1
  300. package/dist/cli/presets.js +1 -1
  301. package/dist/cli/router.d.ts +36 -0
  302. package/dist/cli/router.js +195 -0
  303. package/dist/cli/tui/ai-chat.js +1 -1
  304. package/dist/cli/tui/commands/context.d.ts +9 -0
  305. package/dist/cli/tui/commands/context.js +1 -0
  306. package/dist/cli/tui/commands/dns.d.ts +10 -0
  307. package/dist/cli/tui/commands/dns.js +461 -0
  308. package/dist/cli/tui/commands/hls.d.ts +2 -0
  309. package/dist/cli/tui/commands/hls.js +162 -0
  310. package/dist/cli/tui/commands/ip.d.ts +2 -0
  311. package/dist/cli/tui/commands/ip.js +45 -0
  312. package/dist/cli/tui/commands/network.d.ts +3 -0
  313. package/dist/cli/tui/commands/network.js +81 -0
  314. package/dist/cli/tui/commands/protocols.d.ts +6 -0
  315. package/dist/cli/tui/commands/protocols.js +531 -0
  316. package/dist/cli/tui/commands/security.d.ts +2 -0
  317. package/dist/cli/tui/commands/security.js +48 -0
  318. package/dist/cli/tui/commands/seo.d.ts +2 -0
  319. package/dist/cli/tui/commands/seo.js +74 -0
  320. package/dist/cli/tui/context.d.ts +12 -0
  321. package/dist/cli/tui/context.js +1 -0
  322. package/dist/cli/tui/shell.d.ts +11 -20
  323. package/dist/cli/tui/shell.js +216 -1873
  324. package/dist/constants/user-agents.d.ts +7 -0
  325. package/dist/constants/user-agents.js +7 -0
  326. package/dist/core/client.d.ts +2 -0
  327. package/dist/core/client.js +19 -1
  328. package/dist/index.d.ts +1 -0
  329. package/dist/index.js +1 -0
  330. package/dist/mcp/cli.js +2 -3
  331. package/dist/mcp/data/embeddings.json +1 -1
  332. package/dist/mcp/tools/network.js +298 -158
  333. package/dist/plugins/har-player.d.ts +23 -0
  334. package/dist/plugins/har-player.js +49 -0
  335. package/dist/plugins/har-recorder.d.ts +37 -3
  336. package/dist/plugins/har-recorder.js +116 -63
  337. package/dist/plugins/network-simulation.d.ts +7 -0
  338. package/dist/plugins/network-simulation.js +13 -0
  339. package/dist/presets/android.d.ts +2 -0
  340. package/dist/presets/android.js +16 -0
  341. package/dist/presets/chaturbate.d.ts +2 -0
  342. package/dist/presets/chaturbate.js +17 -0
  343. package/dist/presets/elevenlabs.d.ts +6 -0
  344. package/dist/presets/elevenlabs.js +20 -0
  345. package/dist/presets/enhancers.d.ts +20 -0
  346. package/dist/presets/enhancers.js +85 -0
  347. package/dist/presets/hubspot.d.ts +9 -0
  348. package/dist/presets/hubspot.js +28 -0
  349. package/dist/presets/index.d.ts +10 -0
  350. package/dist/presets/index.js +10 -0
  351. package/dist/presets/ios.d.ts +2 -0
  352. package/dist/presets/ios.js +13 -0
  353. package/dist/presets/pinecone.d.ts +8 -0
  354. package/dist/presets/pinecone.js +42 -0
  355. package/dist/presets/registry.js +60 -0
  356. package/dist/presets/sendgrid.d.ts +6 -0
  357. package/dist/presets/sendgrid.js +20 -0
  358. package/dist/presets/sentry.d.ts +11 -0
  359. package/dist/presets/sentry.js +48 -0
  360. package/dist/presets/square.d.ts +10 -0
  361. package/dist/presets/square.js +33 -0
  362. package/dist/recker.d.ts +3 -0
  363. package/dist/recker.js +4 -0
  364. package/dist/scrape/document.d.ts +5 -4
  365. package/dist/scrape/document.js +89 -76
  366. package/dist/scrape/element.d.ts +10 -8
  367. package/dist/scrape/element.js +295 -81
  368. package/dist/scrape/extractors.d.ts +11 -11
  369. package/dist/scrape/extractors.js +145 -113
  370. package/dist/scrape/index.d.ts +2 -0
  371. package/dist/scrape/index.js +1 -0
  372. package/dist/scrape/parser/back.d.ts +1 -0
  373. package/dist/scrape/parser/back.js +3 -0
  374. package/dist/scrape/parser/index.d.ts +20 -0
  375. package/dist/scrape/parser/index.js +19 -0
  376. package/dist/scrape/parser/matcher.d.ts +30 -0
  377. package/dist/scrape/parser/matcher.js +99 -0
  378. package/dist/scrape/parser/nodes/comment.d.ts +12 -0
  379. package/dist/scrape/parser/nodes/comment.js +21 -0
  380. package/dist/scrape/parser/nodes/html.d.ts +110 -0
  381. package/dist/scrape/parser/nodes/html.js +978 -0
  382. package/dist/scrape/parser/nodes/node.d.ts +18 -0
  383. package/dist/scrape/parser/nodes/node.js +31 -0
  384. package/dist/scrape/parser/nodes/text.d.ts +14 -0
  385. package/dist/scrape/parser/nodes/text.js +30 -0
  386. package/dist/scrape/parser/nodes/type.d.ts +6 -0
  387. package/dist/scrape/parser/nodes/type.js +7 -0
  388. package/dist/scrape/parser/parse.d.ts +1 -0
  389. package/dist/scrape/parser/parse.js +1 -0
  390. package/dist/scrape/parser/valid.d.ts +2 -0
  391. package/dist/scrape/parser/valid.js +5 -0
  392. package/dist/scrape/parser/void-tag.d.ts +7 -0
  393. package/dist/scrape/parser/void-tag.js +43 -0
  394. package/dist/scrape/spider.d.ts +19 -0
  395. package/dist/scrape/spider.js +28 -3
  396. package/dist/scrape/types.d.ts +7 -0
  397. package/dist/seo/analyzer.d.ts +15 -5
  398. package/dist/seo/analyzer.js +636 -175
  399. package/dist/seo/formatter.d.ts +16 -0
  400. package/dist/seo/formatter.js +228 -0
  401. package/dist/seo/index.d.ts +2 -0
  402. package/dist/seo/index.js +1 -0
  403. package/dist/seo/keywords.d.ts +16 -0
  404. package/dist/seo/keywords.js +55 -0
  405. package/dist/seo/rules/accessibility.js +96 -57
  406. package/dist/seo/rules/ai-search.js +44 -31
  407. package/dist/seo/rules/analytics.d.ts +2 -0
  408. package/dist/seo/rules/analytics.js +306 -0
  409. package/dist/seo/rules/best-practices.js +21 -14
  410. package/dist/seo/rules/canonical.js +53 -32
  411. package/dist/seo/rules/content.js +317 -31
  412. package/dist/seo/rules/crawl.js +55 -40
  413. package/dist/seo/rules/cwv.js +21 -15
  414. package/dist/seo/rules/ecommerce.js +82 -22
  415. package/dist/seo/rules/i18n.js +75 -36
  416. package/dist/seo/rules/images.js +109 -30
  417. package/dist/seo/rules/index.js +2 -0
  418. package/dist/seo/rules/internal-linking.js +58 -39
  419. package/dist/seo/rules/links.js +79 -52
  420. package/dist/seo/rules/local.js +49 -25
  421. package/dist/seo/rules/meta.js +339 -81
  422. package/dist/seo/rules/mobile.js +112 -2
  423. package/dist/seo/rules/performance.js +434 -66
  424. package/dist/seo/rules/pwa.js +36 -39
  425. package/dist/seo/rules/readability.js +31 -22
  426. package/dist/seo/rules/redirects.js +21 -15
  427. package/dist/seo/rules/resources.js +59 -42
  428. package/dist/seo/rules/schema.js +333 -8
  429. package/dist/seo/rules/security.js +142 -80
  430. package/dist/seo/rules/social.js +277 -47
  431. package/dist/seo/rules/structural.js +87 -19
  432. package/dist/seo/rules/technical-advanced.js +30 -24
  433. package/dist/seo/rules/technical.js +243 -42
  434. package/dist/seo/rules/types.d.ts +53 -1
  435. package/dist/seo/seo-spider.d.ts +22 -0
  436. package/dist/seo/seo-spider.js +77 -13
  437. package/dist/seo/types.d.ts +8 -1
  438. package/dist/seo/validators/llms-txt.js +19 -0
  439. package/dist/seo/validators/rss.d.ts +11 -0
  440. package/dist/seo/validators/rss.js +93 -0
  441. package/dist/seo/validators/sitemap.js +36 -26
  442. package/dist/transport/curl.d.ts +4 -0
  443. package/dist/transport/curl.js +101 -0
  444. package/dist/transport/udp.js +0 -1
  445. package/dist/transport/undici.js +1 -2
  446. package/dist/transport/worker.d.ts +18 -0
  447. package/dist/transport/worker.js +278 -0
  448. package/dist/types/index.d.ts +4 -1
  449. package/dist/utils/binary-manager.d.ts +4 -0
  450. package/dist/utils/binary-manager.js +72 -0
  451. package/dist/utils/optional-require.d.ts +7 -8
  452. package/dist/utils/optional-require.js +2 -21
  453. package/dist/utils/upload.d.ts +6 -0
  454. package/dist/utils/upload.js +11 -0
  455. package/dist/utils/user-agent.js +2 -13
  456. package/dist/version.js +1 -1
  457. package/package.json +12 -6
  458. package/dist/browser/utils/optional-require.d.ts +0 -19
  459. package/dist/browser/utils/optional-require.js +0 -105
@@ -0,0 +1,738 @@
1
+ import { createResult } from './types.js';
2
+ import { SEO_THRESHOLDS } from './thresholds.js';
3
+ export const performanceRules = [
4
+ {
5
+ id: 'perf-preconnect',
6
+ name: 'Preconnect Hints',
7
+ category: 'performance',
8
+ severity: 'info',
9
+ description: 'Use preconnect for important third-party origins',
10
+ check: (ctx) => {
11
+ if (!ctx.hasPreconnect && ctx.externalLinks && ctx.externalLinks > 5) {
12
+ return createResult({ id: 'perf-preconnect', name: 'Preconnect Hints', category: 'performance', severity: 'info' }, 'info', 'No preconnect hints found', { recommendation: 'Add <link rel="preconnect" href="..."> for important third-party domains' });
13
+ }
14
+ if (ctx.preconnectCount && ctx.preconnectCount > 0) {
15
+ return createResult({ id: 'perf-preconnect', name: 'Preconnect Hints', category: 'performance', severity: 'info' }, 'pass', `${ctx.preconnectCount} preconnect hint(s) found`, { value: ctx.preconnectCount });
16
+ }
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
+ },
19
+ },
20
+ {
21
+ id: 'perf-dns-prefetch',
22
+ name: 'DNS Prefetch',
23
+ category: 'performance',
24
+ severity: 'info',
25
+ description: 'Use dns-prefetch for external domains',
26
+ check: (ctx) => {
27
+ if (ctx.dnsPrefetchCount && ctx.dnsPrefetchCount > 0) {
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
+ }
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
+ },
32
+ },
33
+ {
34
+ id: 'perf-preload',
35
+ name: 'Preload Hints',
36
+ category: 'performance',
37
+ severity: 'info',
38
+ description: 'Use preload for critical resources',
39
+ check: (ctx) => {
40
+ if (ctx.preloadCount && ctx.preloadCount > 0) {
41
+ return createResult({ id: 'perf-preload', name: 'Preload Hints', category: 'performance', severity: 'info' }, 'pass', `${ctx.preloadCount} preload hint(s) found`, { value: ctx.preloadCount });
42
+ }
43
+ return createResult({ id: 'perf-preload', name: 'Preload Hints', category: 'performance', severity: 'info' }, 'pass', 'No preload hints (consider adding for critical resources)');
44
+ },
45
+ },
46
+ {
47
+ id: 'perf-render-blocking',
48
+ name: 'Render Blocking',
49
+ category: 'performance',
50
+ severity: 'warning',
51
+ description: 'Minimize render-blocking resources',
52
+ check: (ctx) => {
53
+ const blocking = ctx.renderBlockingResources ?? 0;
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>`, {
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
+ });
66
+ }
67
+ if (blocking > 0) {
68
+ return createResult({ id: 'perf-render-blocking', name: 'Render Blocking', category: 'performance', severity: 'warning' }, 'info', `${blocking} render-blocking resource(s) in <head>`, { value: blocking });
69
+ }
70
+ return createResult({ id: 'perf-render-blocking', name: 'Render Blocking', category: 'performance', severity: 'warning' }, 'pass', 'No render-blocking resources detected in <head>');
71
+ },
72
+ },
73
+ {
74
+ id: 'perf-inline-styles',
75
+ name: 'Inline Styles',
76
+ category: 'performance',
77
+ severity: 'info',
78
+ description: 'Excessive inline styles can increase page size',
79
+ check: (ctx) => {
80
+ const inline = ctx.inlineStylesCount ?? 0;
81
+ if (inline > 10) {
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' });
83
+ }
84
+ return createResult({ id: 'perf-inline-styles', name: 'Inline Styles', category: 'performance', severity: 'info' }, 'pass', `Inline styles count is acceptable (${inline} blocks)`);
85
+ },
86
+ },
87
+ {
88
+ id: 'cwv-lcp-lazy',
89
+ name: 'LCP Image Lazy',
90
+ category: 'performance',
91
+ severity: 'warning',
92
+ description: 'Above-the-fold images should not use lazy loading',
93
+ check: (ctx) => {
94
+ if (ctx.lcpHints?.hasLazyLcp) {
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
+ });
105
+ }
106
+ return createResult({ id: 'cwv-lcp-lazy', name: 'LCP Image Lazy', category: 'performance', severity: 'warning' }, 'pass', 'No lazy loading detected on LCP images');
107
+ },
108
+ },
109
+ {
110
+ id: 'cwv-lcp-priority',
111
+ name: 'LCP Priority Hints',
112
+ category: 'performance',
113
+ severity: 'info',
114
+ description: 'Use fetchpriority="high" for LCP images',
115
+ check: (ctx) => {
116
+ if (ctx.lcpHints?.hasLargeImages && !ctx.lcpHints?.hasPriorityHints) {
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' });
118
+ }
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');
120
+ },
121
+ },
122
+ {
123
+ id: 'cwv-cls-images',
124
+ name: 'CLS Image Dimensions',
125
+ category: 'performance',
126
+ severity: 'warning',
127
+ description: 'Images without dimensions cause layout shifts',
128
+ check: (ctx) => {
129
+ const missing = ctx.clsHints?.imagesWithoutDimensions ?? ctx.imagesMissingDimensions ?? 0;
130
+ if (missing > 0) {
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
+ });
142
+ }
143
+ return createResult({ id: 'cwv-cls-images', name: 'CLS Image Dimensions', category: 'performance', severity: 'warning' }, 'pass', 'All images have proper dimensions');
144
+ },
145
+ },
146
+ {
147
+ id: 'timing-ttfb',
148
+ name: 'Time to First Byte',
149
+ category: 'performance',
150
+ severity: 'error',
151
+ description: 'TTFB should be under 600ms (ideally under 200ms)',
152
+ check: (ctx) => {
153
+ const ttfb = ctx.timings?.ttfb;
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
+ }
157
+ const { good, needsImprovement, poor } = SEO_THRESHOLDS.timing.ttfb;
158
+ if (ttfb <= good) {
159
+ return createResult({ id: 'timing-ttfb', name: 'TTFB', category: 'performance', severity: 'error' }, 'pass', `Excellent TTFB (${ttfb}ms)`, { value: ttfb });
160
+ }
161
+ if (ttfb <= needsImprovement) {
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` });
163
+ }
164
+ if (ttfb <= poor) {
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
+ });
175
+ }
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
+ });
186
+ },
187
+ },
188
+ {
189
+ id: 'timing-total',
190
+ name: 'Total Load Time',
191
+ category: 'performance',
192
+ severity: 'warning',
193
+ description: 'Total page load should be under 2.5s',
194
+ check: (ctx) => {
195
+ const total = ctx.timings?.total;
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
+ }
199
+ const { good, needsImprovement, poor } = SEO_THRESHOLDS.timing.total;
200
+ if (total <= good) {
201
+ return createResult({ id: 'timing-total', name: 'Load Time', category: 'performance', severity: 'warning' }, 'pass', `Fast page load (${total}ms)`, { value: total });
202
+ }
203
+ if (total <= needsImprovement) {
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` });
205
+ }
206
+ if (total <= poor) {
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
+ });
217
+ }
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
+ });
228
+ },
229
+ },
230
+ {
231
+ id: 'timing-dns',
232
+ name: 'DNS Lookup',
233
+ category: 'performance',
234
+ severity: 'info',
235
+ description: 'DNS lookup should be under 50ms',
236
+ check: (ctx) => {
237
+ const dns = ctx.timings?.dnsLookup;
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
+ }
241
+ const { good, poor } = SEO_THRESHOLDS.timing.dnsLookup;
242
+ if (dns <= good) {
243
+ return createResult({ id: 'timing-dns', name: 'DNS Lookup', category: 'performance', severity: 'info' }, 'pass', `Fast DNS lookup (${dns}ms)`, { value: dns });
244
+ }
245
+ if (dns <= poor) {
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' });
247
+ }
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
+ });
259
+ },
260
+ },
261
+ {
262
+ id: 'timing-tls',
263
+ name: 'TLS Handshake',
264
+ category: 'performance',
265
+ severity: 'info',
266
+ description: 'TLS handshake should be under 100ms',
267
+ check: (ctx) => {
268
+ const tls = ctx.timings?.tlsHandshake;
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
+ }
272
+ const { good, poor } = SEO_THRESHOLDS.timing.tlsHandshake;
273
+ if (tls <= good) {
274
+ return createResult({ id: 'timing-tls', name: 'TLS Handshake', category: 'performance', severity: 'info' }, 'pass', `Fast TLS handshake (${tls}ms)`, { value: tls });
275
+ }
276
+ if (tls <= poor) {
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' });
278
+ }
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
+ });
289
+ },
290
+ },
291
+ {
292
+ id: 'response-html-size',
293
+ name: 'HTML Size',
294
+ category: 'performance',
295
+ severity: 'warning',
296
+ description: 'HTML should be under 500KB (ideally under 100KB)',
297
+ check: (ctx) => {
298
+ const size = ctx.htmlSize;
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
+ }
302
+ const { good, warning, poor } = SEO_THRESHOLDS.responseSize.html;
303
+ const sizeKb = Math.round(size / 1024);
304
+ if (size <= good) {
305
+ return createResult({ id: 'response-html-size', name: 'HTML Size', category: 'performance', severity: 'warning' }, 'pass', `HTML size is good (${sizeKb}KB)`, { value: sizeKb });
306
+ }
307
+ if (size <= warning) {
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' });
309
+ }
310
+ if (size <= poor) {
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
+ });
321
+ }
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
+ });
332
+ },
333
+ },
334
+ {
335
+ id: 'response-compression',
336
+ name: 'Compression',
337
+ category: 'performance',
338
+ severity: 'warning',
339
+ description: 'Response should be compressed (gzip/brotli)',
340
+ check: (ctx) => {
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
+ }
344
+ if (ctx.isCompressed === false && ctx.htmlSize > 1024) {
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
+ });
355
+ }
356
+ if (ctx.isCompressed === true) {
357
+ const ratio = ctx.compressedSize && ctx.htmlSize ? Math.round((ctx.compressedSize / ctx.htmlSize) * 100) : undefined;
358
+ return createResult({ id: 'response-compression', name: 'Compression', category: 'performance', severity: 'warning' }, 'pass', ratio ? `Response compressed (${ratio}% of original)` : 'Response is compressed');
359
+ }
360
+ return createResult({ id: 'response-compression', name: 'Compression', category: 'performance', severity: 'warning' }, 'info', 'Not applicable (small response or compression status unknown)');
361
+ },
362
+ },
363
+ {
364
+ id: 'browser-caching',
365
+ name: 'Browser Caching',
366
+ category: 'performance',
367
+ severity: 'warning',
368
+ description: 'Static resources should have browser caching enabled',
369
+ check: (ctx) => {
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
+ }
373
+ if (ctx.resourcesWithoutCaching > 0) {
374
+ return createResult({ id: 'browser-caching', name: 'Browser Caching', category: 'performance', severity: 'warning' }, 'warn', `${ctx.resourcesWithoutCaching} resources without cache headers`, {
375
+ value: ctx.resourcesWithoutCaching,
376
+ recommendation: 'Enable browser caching with Cache-Control or Expires headers',
377
+ evidence: {
378
+ found: `${ctx.resourcesWithoutCaching} uncached resources`,
379
+ expected: 'All static resources should have cache headers',
380
+ impact: 'Without caching, browsers download resources repeatedly, increasing load time',
381
+ learnMore: 'https://web.dev/uses-long-cache-ttl/'
382
+ }
383
+ });
384
+ }
385
+ return createResult({ id: 'browser-caching', name: 'Browser Caching', category: 'performance', severity: 'warning' }, 'pass', 'Resources have proper cache headers');
386
+ },
387
+ },
388
+ {
389
+ id: 'js-total-size',
390
+ name: 'JS Total Size',
391
+ category: 'performance',
392
+ severity: 'warning',
393
+ description: 'Total JavaScript size should be minimized for mobile performance (Ideal < 150KB)',
394
+ check: (ctx) => {
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.',
407
+ evidence: {
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.'
411
+ }
412
+ });
413
+ }
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.',
418
+ evidence: {
419
+ found: `${sizeKb.toFixed(0)}KB`,
420
+ expected: '< 300KB',
421
+ impact: 'Mobile devices struggle with parsing/executing large JS bundles.'
422
+ }
423
+ });
424
+ }
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 });
426
+ },
427
+ },
428
+ {
429
+ id: 'excessive-js-css-files',
430
+ name: 'Excessive JS/CSS Files',
431
+ category: 'performance',
432
+ severity: 'warning',
433
+ description: 'Pages should not load more than 100 JS/CSS files',
434
+ check: (ctx) => {
435
+ const jsCount = ctx.jsFilesCount || 0;
436
+ const cssCount = ctx.cssFilesCount || 0;
437
+ const totalFiles = jsCount + cssCount;
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
+ }
441
+ if (totalFiles > 100) {
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)`, {
443
+ value: totalFiles,
444
+ recommendation: 'Combine files or use bundling to reduce HTTP requests',
445
+ evidence: {
446
+ found: `${totalFiles} files (JS: ${jsCount}, CSS: ${cssCount})`,
447
+ expected: '<100 files',
448
+ impact: 'Each file requires a separate HTTP request, increasing page load time'
449
+ }
450
+ });
451
+ }
452
+ if (totalFiles > 50) {
453
+ return createResult({ id: 'excessive-js-css-files', name: 'Excessive JS/CSS Files', category: 'performance', severity: 'warning' }, 'warn', `Page loads ${totalFiles} JS/CSS files`, {
454
+ value: totalFiles,
455
+ recommendation: 'Consider bundling resources to reduce requests',
456
+ evidence: {
457
+ found: `${totalFiles} files`,
458
+ expected: '<50 files recommended',
459
+ impact: 'Many files increase connection overhead'
460
+ }
461
+ });
462
+ }
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 });
464
+ },
465
+ },
466
+ {
467
+ id: 'unminified-resources',
468
+ name: 'Unminified Resources',
469
+ category: 'performance',
470
+ severity: 'warning',
471
+ description: 'JS and CSS files should be minified',
472
+ check: (ctx) => {
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
+ }
476
+ if (ctx.unminifiedResources > 0) {
477
+ return createResult({ id: 'unminified-resources', name: 'Unminified Resources', category: 'performance', severity: 'warning' }, 'warn', `${ctx.unminifiedResources} unminified JS/CSS files detected`, {
478
+ value: ctx.unminifiedResources,
479
+ recommendation: 'Minify JavaScript and CSS files to reduce file size',
480
+ evidence: {
481
+ found: `${ctx.unminifiedResources} unminified files`,
482
+ expected: 'All JS/CSS files should be minified',
483
+ impact: 'Minification reduces file size by removing whitespace and comments',
484
+ learnMore: 'https://web.dev/minify-css/'
485
+ }
486
+ });
487
+ }
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
+ });
736
+ },
737
+ },
738
+ ];