Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/cwv-stats-monthly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
run: |
container_id=$(docker run -d cwv-stats)
docker wait "$container_id"
docker cp "$container_id:/app/cwv-stats.json" ./packages/cwv-stats/cwv-stats.json
docker cp "$container_id:/app/cwv-stats.json" ./packages/docs/src/content/cwv/cwv-stats.json
cat ./packages/cwv-stats/cwv-stats.json
docker rm "$container_id"

Expand Down
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@
**/output
.changeset/*.md
pnpm-lock.yaml
/packages/cwv-stats/cwv-stats.json
/packages/docs/src/content/cwv/cwv-stats.json
58 changes: 0 additions & 58 deletions packages/cwv-stats/cwv-stats.json

This file was deleted.

33 changes: 23 additions & 10 deletions packages/cwv-stats/src/cwv/cwv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {

// httparchive allows us to pull FID but does not include any metrics at this current time so we can ignore it.
type FrameworkCWV = {
id: string
framework: Framework
date: string
} & {
Expand All @@ -31,12 +32,9 @@ export async function getLatestFrameworksCWV(): Promise<Array<FrameworkCWV>> {
}

async function getHttpArchiveCWV() {
const url = new URL('https://cdn.httparchive.org/v1/cwv')
frameworks.forEach((framework) =>
url.searchParams.append('technology', framework),
)
url.searchParams.append('geo', 'ALL')
url.searchParams.append('rank', 'ALL')
const baseUrl = 'https://cdn.httparchive.org/v1/cwv'
const queryString = frameworks.map(buildFrameworkQueryParam).join('&')
const url = `${baseUrl}?${queryString}&geo=ALL&rank=ALL`

const response = await fetch(url)
if (!response.ok) {
Expand All @@ -48,6 +46,10 @@ async function getHttpArchiveCWV() {
return data
}

function buildFrameworkQueryParam(framework: Framework) {
return `technology=${framework.replace(' ', '+')}`
}

function getLatestCWVForFrameworks(frameworksCWV: HTTPArchiveCWVSnapshot[]) {
const latestStats = new Map<Framework, HTTPArchiveCWVSnapshot>()

Expand All @@ -70,6 +72,7 @@ function validateAllCWVIsSameDate(

function buildFrameworkCWV(latestFrameworkCWV: HTTPArchiveCWVSnapshot[]) {
const frameworkVitals = latestFrameworkCWV.map((stat) => ({
id: stat.technology.toLowerCase().replace(/[.\s]/g, '-'),
framework: stat.technology,
date: stat.date,
overall: getCWV('overall', stat),
Expand All @@ -91,13 +94,23 @@ function getCWV(cwv: HTTPArchiveCWV, stat: HTTPArchiveCWVSnapshot) {
)
}

const hasMobileVital = vital.mobile.tested > 0
const hasDesktopVital = vital.desktop.tested > 0
const vitalMobile = vital.mobile ?? {
tested: 0,
good_number: 0,
}

const vitalDesktop = vital.desktop ?? {
tested: 0,
good_number: 0,
}

const hasMobileVital = vitalMobile.tested > 0
const hasDesktopVital = vitalDesktop.tested > 0

return {
mobile: hasMobileVital ? vital.mobile.good_number / vital.mobile.tested : 0,
mobile: hasMobileVital ? vitalMobile.good_number / vitalMobile.tested : 0,
desktop: hasDesktopVital
? vital.desktop.good_number / vital.desktop.tested
? vitalDesktop.good_number / vitalDesktop.tested
: 0,
}
}
9 changes: 8 additions & 1 deletion packages/cwv-stats/src/frameworks/frameworks.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
export const frameworks = ['Svelte', 'Next.js'] as const
export const frameworks = [
'Next.js',
'SolidStart',
'Astro',
'Nuxt.js',
'SvelteKit',
'React Router',
] as const

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rdforte We will have to contribute I guess to get TanStack? Can you add as an issue in the repo with any info that will help

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, happy to pick this one up


export type Framework = (typeof frameworks)[number]
4 changes: 2 additions & 2 deletions packages/cwv-stats/src/httparchive/httparchive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ export type HTTPArchiveCWV = z.infer<typeof httpArchiveCWVSchema>

const httpArchiveVitalSchema = z.object({
name: httpArchiveCWVSchema,
desktop: httpArchiveDeviceSchema,
mobile: httpArchiveDeviceSchema,
desktop: httpArchiveDeviceSchema.nullish(),
mobile: httpArchiveDeviceSchema.nullish(),
})

const httpArchiveCWVSnapshot = z.object({
Expand Down
41 changes: 39 additions & 2 deletions packages/docs/src/components/ChartTabs.astro
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,29 @@ interface Props {
tab2Label: string
tab3Label?: string
tab4Label?: string
tab5Label?: string
}

const { sectionId, label, tab1Label, tab2Label, tab3Label, tab4Label } =
Astro.props
const {
sectionId,
label,
tab1Label,
tab2Label,
tab3Label,
tab4Label,
tab5Label,
} = Astro.props

const tab1Id = `${sectionId}-tab-1`
const tab2Id = `${sectionId}-tab-2`
const tab3Id = `${sectionId}-tab-3`
const tab4Id = `${sectionId}-tab-4`
const tab5Id = `${sectionId}-tab-5`
const panel1Id = `${sectionId}-panel-1`
const panel2Id = `${sectionId}-panel-2`
const panel3Id = `${sectionId}-panel-3`
const panel4Id = `${sectionId}-panel-4`
const panel5Id = `${sectionId}-panel-5`
---

<div class="chart-tabs-container" data-tab-section={sectionId}>
Expand Down Expand Up @@ -71,6 +81,20 @@ const panel4Id = `${sectionId}-panel-4`
</button>
)
}
{
tab5Label && (
<button
type="button"
role="tab"
id={tab5Id}
aria-selected="false"
aria-controls={panel5Id}
class="tab"
>
{tab5Label}
</button>
)
}
</div>
<div class="chart-tabpanels">
<div
Expand Down Expand Up @@ -116,6 +140,19 @@ const panel4Id = `${sectionId}-panel-4`
</div>
)
}
{
tab5Label && (
<div
role="tabpanel"
id={panel5Id}
aria-labelledby={tab5Id}
class="chart-tabpanel"
hidden
>
<slot name="panel-5" />
</div>
)
}
</div>
</div>

Expand Down
4 changes: 3 additions & 1 deletion packages/docs/src/components/ComparisonBarChart.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { ChartDatum } from '../lib/types'

interface Props {
title: string
description?: string
data: ChartDatum[]
valueFormat: 'count' | 'mb' | 'kb' | 'ms' | 's'
yAxisLabel: string
Expand All @@ -11,7 +12,7 @@ interface Props {
const CHART_HEIGHT_PX = 400
const MOBILE_CHART_HEIGHT_PX = 300

const { title, data, valueFormat, yAxisLabel } = Astro.props
const { title, description, data, valueFormat, yAxisLabel } = Astro.props
const chartData = data.map((d) => ({
name: String(d?.name ?? ''),
value: Math.max(0, Number(d.value) || 0),
Expand All @@ -26,6 +27,7 @@ const chartPayload = JSON.stringify({

<div class="comparison-chart">
<h3 class="comparison-chart-title">{title}</h3>
{description ? <p>{description}</p> : null}
<div class="comparison-chart-wrapper" data-comparison={chartPayload}>
<canvas aria-label={title} role="img">{title} chart</canvas>
</div>
Expand Down
35 changes: 35 additions & 0 deletions packages/docs/src/components/CoreWebVitalChart.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
import { getCWVDesktopStatsChartData } from '../lib/collections'
import type { CWV } from '../lib/collections'
import ComparisonBarChart from './ComparisonBarChart.astro'

interface Props {
cwv: CWV
}

const { cwv } = Astro.props

const cwvTitle: Record<CWV, string> = {
lcp: 'Good Largest Contentful Paint',
cls: 'Good Cumulative Layout Shift',
fcp: 'Good First Contentful Paint',
ttfb: 'Good Time To First Byte',
inp: 'Good Interaction to Next Paint',
}

const cwvDescription: Record<CWV, string> = {
lcp: `Measures how fast a page's main content loads. To provide a good user experience, the LCP should be 2.5 seconds or less.`,
cls: `Measures how much and how far content unexpectedly moves on a page. To provide a good user experience, sites should maintain a CLS score of 0.1 or less for at least 75% of page visits.`,
fcp: `Measures initial loading speed. To provide a good user experience, the FCP should be 1.8 seconds or less.`,
ttfb: `Measures the time between the request for a resource and when the first byte of a response begins to arrive. A good TTFB is less than or equal to 800ms.`,
inp: `Measures overall page responsiveness to user actions. To provide a good user experience, the INP should be 200ms or less.`,
}
---

<ComparisonBarChart
title={cwvTitle[cwv]}
description={cwvDescription[cwv]}
data={getCWVDesktopStatsChartData(cwv)}
valueFormat="count"
yAxisLabel="%"
/>
20 changes: 20 additions & 0 deletions packages/docs/src/components/CoreWebVitalsCharts.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
import ChartTabs from './ChartTabs.astro'
import CoreWebVitalChart from './CoreWebVitalChart.astro'
---

<ChartTabs
sectionId="client-side-rendered"
label="Core Web Vitals Desktop"
tab1Label="LCP"
tab2Label="CLS"
tab3Label="FCP"
tab4Label="TTFB"
tab5Label="INP"
>
<CoreWebVitalChart slot="panel-1" cwv="lcp" />
<CoreWebVitalChart slot="panel-2" cwv="cls" />
<CoreWebVitalChart slot="panel-3" cwv="fcp" />
<CoreWebVitalChart slot="panel-4" cwv="ttfb" />
<CoreWebVitalChart slot="panel-5" cwv="inp" />
</ChartTabs>
22 changes: 22 additions & 0 deletions packages/docs/src/components/CoreWebVitalsTable.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
import { cwvStats } from '../lib/collections'
import { getFrameworkSlug } from '../lib/utils'
import StatsTable from './StatsTable.astro'

const columns = [
{
key: 'framework',
header: 'Framework',
nameCell: true,
href: (row: Record<string, unknown>) =>
`/framework/${getFrameworkSlug(row.id as string)}`,
},
{ key: 'lcpDesktopPercent', header: 'LCP%' },
{ key: 'clsDesktopPercent', header: 'CLS%' },
{ key: 'fcpDesktopPercent', header: 'FCP%' },
{ key: 'ttfbDesktopPercent', header: 'TTFB%' },
{ key: 'inpDesktopPercent', header: 'INP%' },
]
---

<StatsTable label="CWV by framework" columns={columns} data={cwvStats} />
36 changes: 35 additions & 1 deletion packages/docs/src/content.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { defineCollection } from 'astro:content'
import { glob } from 'astro/loaders'
import { file, glob } from 'astro/loaders'
import { z } from 'astro/zod'

const timeSchema = z.object({
Expand Down Expand Up @@ -111,7 +111,41 @@ const runtimeCollection = defineCollection({
}),
})

const cwvCollection = defineCollection({
loader: file('src/content/cwv/cwv-stats.json'),
schema: z.object({
id: z.string(),
framework: z.string(),
date: z.string(),
overall: z.object({
mobile: z.number(),
desktop: z.number(),
}),
lcp: z.object({
mobile: z.number(),
desktop: z.number(),
}),
cls: z.object({
mobile: z.number(),
desktop: z.number(),
}),
fcp: z.object({
mobile: z.number(),
desktop: z.number(),
}),
ttfb: z.object({
mobile: z.number(),
desktop: z.number(),
}),
inp: z.object({
mobile: z.number(),
desktop: z.number(),
}),
}),
})

export const collections = {
devtime: devtimeCollection,
runtime: runtimeCollection,
cwv: cwvCollection,
}
Loading
Loading