UNPKG

kanmi-perf-pro

Version:

Advanced Web Vitals Toolkit with timeline, LCP/CLS overlays, and exportable logs. Built for CI/CD and devtool integration.

3 lines (2 loc) 11.1 kB
var KanmiPerfPro=function(){"use strict";const e={LCP:{good:2500,needsImprovement:4e3},CLS:{good:.1,needsImprovement:.25},TTFB:{good:800,needsImprovement:1800},INP:{good:200,needsImprovement:500},FCP:{good:1800,needsImprovement:3e3}},t={LCP:.25,CLS:.2,TTFB:.2,INP:.2,FCP:.15};let n=!1;return(()=>{const o={_observers:[],_longTaskQueue:[],_loafEntries:[],_longTaskTimer:null};window.__kanmiVitalsLog=window.__kanmiVitalsLog||[],o.timeline=[],o.metrics={FCP:0,LCP:0,CLS:0,INP:0,TTFB:0,LoAF:[],LongTasks:[]},o.issues=[],o.getReport=()=>({metrics:o.metrics,issues:o.issues,timeline:o.timeline,env:{userAgent:navigator.userAgent,connection:navigator.connection?.effectiveType||"unknown",device:window.innerWidth<768?"mobile":"desktop"}});const r=(e,t,n)=>e<=t?"✅ Good":e<=n?"🟡 Needs Improvement":"🚨 Poor";return o.getScores=()=>{const t=e;return{LCP:r(o.metrics.LCP,t.LCP.good,t.LCP.needsImprovement),CLS:r(o.metrics.CLS,t.CLS.good,t.CLS.needsImprovement),TTFB:r(o.metrics.TTFB,t.TTFB.good,t.TTFB.needsImprovement),INP:r(o.metrics.INP,t.INP.good,t.INP.needsImprovement),FCP:r(o.metrics.FCP,t.FCP.good,t.FCP.needsImprovement)}},o.getPageScore=()=>{const e=o.getScores(),n=t;let r=0;const i={"✅ Good":100,"🟡 Needs Improvement":60,"🚨 Poor":20};for(const t in n){const o=e[t]||"🟡 Needs Improvement";r+=n[t]*(i[o]||50)}return Math.round(r)},o.log=(e,t)=>{const n=(new Date).toISOString();console.groupCollapsed(`[KanmiPerf 🚀] ⚠️ ${e} at ${n}`),t.forEach((e=>console.log(`- ${e}`))),console.groupEnd(),window.__kanmiVitalsLog.push({type:e,issues:t,timestamp:n}),o.timeline.push({type:e,issues:t,time:performance.now()}),t.forEach((t=>{o.issues.push(`[${e}] ${t}`)}))},o.pageName=()=>{const e=[...document.querySelectorAll('link[rel="canonical"]')].map((e=>e.href));o.log("Page",e)},o.domAnalysis=()=>{const e=[...document.querySelectorAll("img:not([width]), img:not([height])")];e.length&&o.log("DOM Issues",[`${e.length} image${e.length>1?"s":""} missing dimensions (causes CLS)`]);const t=[...document.querySelectorAll("img:not([alt])")];t.length&&o.log("DOM Accessibility Issues",[`${t.length} image${t.length>1?"s":""} missing alt text`]);const n=[...document.querySelectorAll("[style]")];n.length&&o.log("DOM Styling Issues",[`${n.length} element${n.length>1?"s":""} with inline styles`]);const r=(e,t=0)=>e.children&&0!==e.children.length?Math.max(...[...e.children].map((e=>r(e,t+1)))):t,i=r(document.body);i>10&&o.log("DOM Structure Issues",[`DOM nesting depth is ${i}, which may hurt performance`])},o.headAnalysis=()=>{const e=[],t=[...document.head.querySelectorAll("script[src]")].filter((e=>!e.defer&&!e.async));t.length&&e.push(`${t.length} blocking script${t.length>1?"s":""} without async/defer`,...t.map((e=>`Suggest moving ${e.src} lower or load it with async/defer`)));const n=document.head?.outerHTML||"",r=(n.length/1024).toFixed(1);n.length>8e3&&e.push(`The <head> section is large (${r} KB)`,"→ Consider optimizing it."),e.length>0&&o.log("<head> Issues",e)},o.thirdPartyAnalysis=()=>{const e=location.hostname,t=[...document.scripts].filter((t=>t.src&&!t.src.includes(e))).filter((e=>!e.defer&&!e.async));t.length&&o.log("Third-party Issues",t.map((e=>`${new URL(e.src).hostname} synchronously loaded, delays rendering → Suggest loading async/defer or using Partytown`)))},o.monitorFCP=()=>{if(!PerformanceObserver.supportedEntryTypes.includes("paint"))return;let e;const t=new PerformanceObserver((t=>{t.getEntries().forEach((t=>{"first-contentful-paint"===t.name&&(e=t)}))}));t.observe({type:"paint",buffered:!0}),o._observers.push(t),document.addEventListener("visibilitychange",(()=>{if("hidden"===document.visibilityState&&e){const t=Math.round(e.startTime);o.log("FCP",[`First Contentful Paint: ${t}ms`,"→ Ensure critical resources are loaded efficiently."]),o.metrics.FCP=t}}))},o.monitorLongTasks=()=>{if(!("PerformanceObserver"in window))return;const e=[];new PerformanceObserver((t=>{t.getEntries().forEach((t=>{if(t.duration>50){const n=Math.round(t.duration);let r="🟡 Minor";n>200?r="🚨 Severe":n>125&&(r="⚠️ Moderate"),o.log("Long Task",[`${r} – Main thread blocked for ${n}ms`,"→ Break into smaller functions, use requestIdleCallback or Web Workers for heavy work"]),e.push(n)}}))})).observe({type:"longtask",buffered:!0}),o.metrics.LongTasks=e},o.monitorLoAF=()=>{if(!PerformanceObserver.supportedEntryTypes.includes("long-animation-frame"))return;new PerformanceObserver((e=>{e.getEntries().forEach((e=>{const{duration:t,blockingDuration:n,renderTime:r,styleAndLayoutDuration:i}=e,s={duration:Number.isFinite(t)?Math.round(t):null,blockingDuration:Number.isFinite(n)?Math.round(n):null,renderTime:Number.isFinite(r)?Math.round(r):null,styleAndLayoutDuration:Number.isFinite(i)?Math.round(i):null,timestamp:Date.now()};o._loafEntries.push(s),o.metrics.LoAF.push(s);const a=e=>Number.isFinite(e)?Math.round(e):"N/A";let l=[`Duration: ${a(t)}ms, Blocking: ${a(n)}ms`,`Render: ${a(r)}ms, Layout: ${a(i)}ms`];void 0!==r&&void 0!==i||l.push("Note: Browser does not support detailed render/layout metrics for LoAF entries."),l.push("→ Investigate heavy scripts or CSS causing recalculations"),o.log("LoAF Detected",l),window.KanmiPerfDebug&&console.debug("[KanmiPerf Debug] LoAF entry:",s)}))})).observe({type:"long-animation-frame",buffered:!0})},o.monitorINP=()=>{if(!PerformanceObserver.supportedEntryTypes.includes("event"))return;new PerformanceObserver((e=>{e.getEntries().forEach((e=>{if(e.duration>300){let t=[`${e.name} delayed interaction response by ${Math.round(e.duration)}ms`];if(e.target)try{const n=e.target.outerHTML?.slice(0,100)||`[${e.target.nodeName}]`;t.push(`Element: ${n}`),e.target.style.outline="2px solid purple",setTimeout((()=>e.target.style.outline=""),2500)}catch(e){t.push("Element: [unreadable]")}t.push("→ Optimize interaction handlers. Avoid blocking JS, reflows, and heavy tasks."),o.log("INP Interaction Issue",t),o.metrics.INP=Math.round(e.duration)}}))})).observe({type:"event",durationThreshold:300,buffered:!0})},o.monitorLCP=()=>{if(!PerformanceObserver.supportedEntryTypes.includes("largest-contentful-paint"))return;let e;new PerformanceObserver((t=>{t.getEntries().forEach((t=>{e=t,console.debug("[KanmiPerf Debug] LCP entry captured:",t)}))})).observe({type:"largest-contentful-paint",buffered:!0}),document.addEventListener("visibilitychange",(()=>{if("hidden"===document.visibilityState&&e){const t=Math.round(e.renderTime||e.loadTime);let n=e.element?e.element.outerHTML.slice(0,100):"N/A";e.element&&(e.element.style.outline="2px solid red",setTimeout((()=>{e.element.style.outline=""}),3e3)),o.log("LCP",[`Largest Contentful Paint: ${t}ms`,`Element: ${n}`,"→ Optimize images, text, and video content for faster loading"]),o.metrics.LCP=t}}))},o.monitorCLS=()=>{if(!PerformanceObserver.supportedEntryTypes.includes("layout-shift"))return;let e=0,t=[];const n=[];new PerformanceObserver((o=>{o.getEntries().forEach((o=>{!o.hadRecentInput&&o.value>0&&(e+=o.value,(o.sources||[]).slice(0,3).forEach((e=>{try{e.node&&e.node.outerHTML?(t.push(e.node.outerHTML.slice(0,100)),n.push(e.node)):t.push("[Unknown Source]")}catch(e){t.push("[Error Reading Source]")}})),console.debug("[KanmiPerf Debug] Layout shift entry:",o))}))})).observe({type:"layout-shift",buffered:!0}),document.addEventListener("visibilitychange",(()=>{if("hidden"===document.visibilityState){const r=+e.toFixed(3);let i="Good";r>.25?i="Poor":r>.1&&(i="Needs Improvement");const s=[`Cumulative Layout Shift: ${r} (${i})`,"→ Consider reserving space for images, ads, embeds, or dynamic content to reduce unexpected shifts."];t.length&&s.push(`Top shifting elements: ${t.join(" | ")}`),o.log("CLS",s),o.metrics.CLS=r,n.forEach((e=>{e&&e.style&&(e.style.outline="2px dashed orange",setTimeout((()=>e.style.outline=""),3e3))}))}}))},o.timingAnalysis=()=>{const t=performance.getEntriesByType("navigation")[0];if(t){const n=Math.round(t.responseStart),r=e.TTFB,i=[`Time To First Byte: ${n}ms`];n<=r.good?i.push("TTFB is in the Good range."):n<=r.needsImprovement?i.push("→ TTFB is slightly high. Review server response time and cache strategy."):i.push("→ TTFB is very high. Optimize backend performance, CDN, and server-side rendering.");const s=document.head?.outerHTML||"",a=(s.length/1024).toFixed(1);s.length>1e4&&i.push(`→ The <head> section is large (${a} KB). Consider optimizing it.`);const l=document.head.querySelectorAll("script").length,d=document.head.querySelectorAll("script:not([src])").length,c=document.head.querySelectorAll("link[rel='stylesheet']").length,u=document.head.querySelectorAll("style").length,m=document.head.querySelectorAll("link[rel='preload']").length;l>5&&i.push(`→ There are ${l} <script> tags in <head>. Consider deferring non-critical JS.`),d>1&&i.push("→ Multiple inline <script> blocks found. Consider externalizing where possible."),c>3&&i.push(`→ ${c} stylesheets detected. Consolidate critical CSS to reduce requests.`),u>1&&i.push(`→ ${u} <style> blocks found. Inline styles can delay parsing.`),m>5&&i.push(`→ ${m} preload tags found. Verify they are used early to avoid wasted bytes.`),o.log("TTFB & <head> Analysis",i),o.metrics.TTFB=n}},o.monitorFCP=()=>{if(!PerformanceObserver.supportedEntryTypes.includes("paint"))return;let e;new PerformanceObserver((t=>{t.getEntries().forEach((t=>{"first-contentful-paint"===t.name&&(e=t)}))})).observe({type:"paint",buffered:!0}),document.addEventListener("visibilitychange",(()=>{if("hidden"===document.visibilityState&&e){const t=Math.round(e.startTime);o.log("FCP",[`First Contentful Paint: ${t}ms`,"→ Ensure critical resources are loaded efficiently."]),o.metrics.FCP=t}}))},o.analyzeTimeline=()=>{if(o.timeline.length>1){const e=[...o.timeline].sort(((e,t)=>e.time-t.time)),t=e[0].time,n=e.map(((e,n)=>{const o=Math.round(e.time-t);return`${n+1}. ${e.type}${o}ms`}));o.log("Timeline Report",n)}else o.log("Timeline Report",["Insufficient data to generate timeline"])},o.run=()=>{o.pageName(),o.analyzeTimeline(),o.domAnalysis(),o.headAnalysis(),o.thirdPartyAnalysis(),o.monitorLongTasks(),o.monitorLoAF(),o.monitorINP(),o.monitorLCP(),o.monitorCLS(),o.timingAnalysis(),o.monitorFCP()},o.init=async(e={})=>{const t=e.licenseKey||window.KanmiPerfProLicenseKey;if(!t)return void console.error("[KanmiPerf Pro] ⚠️ No licenceKey provided. Purchase one at https://knfrmd.com/pricing");const r=await async function(e){try{const t=await fetch(`https://www.knfrmd.com/api/license/validate?key=${encodeURIComponent(e)}`),o=await t.json();n=!!o?.valid}catch(e){console.warn("[KanmiPerf Pro] Licence validation failed – running in demo mode.",e),n=!1}return n}(t);r?("complete"===document.readyState?o.run():window.addEventListener("load",o.run),document.addEventListener("visibilitychange",(()=>{"hidden"===document.visibilityState&&o.analyzeTimeline()}))):console.error("[KanmiPerf Pro] ⚠️ Licence key invalid. Aborting run.")},window.onerror=(e,t,n,r,i)=>{const s=`${e} at ${t}:${n}:${r}`;return o.log("JS Error",[s]),!1},window.KanmiPerf=o,o})()}(); //# sourceMappingURL=kanmi-perf-pro.js.map