Skip to content

Commit 5a9644a

Browse files
committed
feat: optimize JSON-LD analysis with reactive signals and memoization
1 parent 3512313 commit 5a9644a

File tree

1 file changed

+57
-39
lines changed

1 file changed

+57
-39
lines changed

packages/devtools-seo/src/tabs/json-ld-preview.tsx

Lines changed: 57 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { For, Show } from 'solid-js'
1+
import { For, Show, createMemo, createSignal } from 'solid-js'
22
import { Section, SectionDescription } from '@tanstack/devtools-ui'
3+
import { useHeadChanges } from '../hooks/use-head-changes'
4+
import { useLocationChanges } from '../hooks/use-location-changes'
35
import { isInsideDevtools } from '../utils/devtools-dom-filter'
46
import { sectionHealthScore } from '../utils/seo-section-summary'
57
import { useSeoStyles } from '../utils/use-seo-styles'
@@ -340,7 +342,7 @@ function analyzeJsonLdScripts(): Array<JsonLdEntry> {
340342
).filter((script) => !isInsideDevtools(script))
341343

342344
return scripts.map((script, index) => {
343-
const raw = script.textContent.trim() || ''
345+
const raw = script.text.trim()
344346
if (raw.length === 0) {
345347
return {
346348
id: `jsonld-${index}`,
@@ -600,21 +602,34 @@ function JsonLdBlock(props: { entry: JsonLdEntry; index: number }) {
600602
}
601603

602604
export function JsonLdPreviewSection() {
603-
const entries = analyzeJsonLdScripts()
604605
const styles = useSeoStyles()
605-
const score = getJsonLdScore(entries)
606+
const [tick, setTick] = createSignal(0)
607+
608+
useHeadChanges(() => {
609+
setTick((t) => t + 1)
610+
})
611+
612+
useLocationChanges(() => {
613+
setTick((t) => t + 1)
614+
})
615+
616+
const entries = createMemo(() => {
617+
void tick()
618+
return analyzeJsonLdScripts()
619+
})
620+
const score = createMemo(() => getJsonLdScore(entries()))
606621
const s = styles()
607-
const fieldGaps = sumMissingSchemaFieldCounts(entries)
622+
const fieldGaps = createMemo(() => sumMissingSchemaFieldCounts(entries()))
608623
const healthScoreClass = () => {
609-
const tier = seoHealthTier(score)
624+
const tier = seoHealthTier(score())
610625
return tier === 'good'
611626
? s.seoHealthScoreGood
612627
: tier === 'fair'
613628
? s.seoHealthScoreFair
614629
: s.seoHealthScorePoor
615630
}
616631
const healthFillClass = () => {
617-
const tier = seoHealthTier(score)
632+
const tier = seoHealthTier(score())
618633
const tierFill =
619634
tier === 'good'
620635
? s.seoHealthFillGood
@@ -623,55 +638,56 @@ export function JsonLdPreviewSection() {
623638
: s.seoHealthFillPoor
624639
return `${s.seoHealthFill} ${tierFill}`
625640
}
626-
const errorCount = entries.reduce(
641+
const errorCount = () => entries().reduce(
627642
(total, entry) =>
628643
total + entry.issues.filter((issue) => issue.severity === 'error').length,
629644
0,
630645
)
631-
const warningCount = entries.reduce(
646+
const warningCount = () => entries().reduce(
632647
(total, entry) =>
633648
total +
634649
entry.issues.filter((issue) => issue.severity === 'warning').length,
635650
0,
636651
)
637-
const infoCount = entries.reduce(
652+
const infoCount = () => entries().reduce(
638653
(total, entry) =>
639654
total + entry.issues.filter((issue) => issue.severity === 'info').length,
640655
0,
641656
)
642-
const progressAriaLabel = (() => {
643-
const parts = [`JSON-LD health ${Math.round(score)} percent`]
657+
const progressAriaLabel = createMemo(() => {
658+
const parts = [`JSON-LD health ${Math.round(score())} percent`]
644659
const sev = [
645-
errorCount && `${errorCount} error${errorCount === 1 ? '' : 's'}`,
646-
warningCount && `${warningCount} warning${warningCount === 1 ? '' : 's'}`,
647-
infoCount && `${infoCount} info`,
660+
errorCount() && `${errorCount()} error${errorCount() === 1 ? '' : 's'}`,
661+
warningCount() &&
662+
`${warningCount()} warning${warningCount() === 1 ? '' : 's'}`,
663+
infoCount() && `${infoCount()} info`,
648664
].filter(Boolean)
649665
if (sev.length) parts.push(sev.join(', '))
650666
const gapBits: Array<string> = []
651-
if (fieldGaps.required > 0)
667+
if (fieldGaps().required > 0)
652668
gapBits.push(
653-
`${fieldGaps.required} required field${fieldGaps.required === 1 ? '' : 's'}`,
669+
`${fieldGaps().required} required field${fieldGaps().required === 1 ? '' : 's'}`,
654670
)
655-
if (fieldGaps.recommended > 0)
671+
if (fieldGaps().recommended > 0)
656672
gapBits.push(
657-
`${fieldGaps.recommended} recommended field${fieldGaps.recommended === 1 ? '' : 's'}`,
673+
`${fieldGaps().recommended} recommended field${fieldGaps().recommended === 1 ? '' : 's'}`,
658674
)
659-
if (fieldGaps.optional > 0)
675+
if (fieldGaps().optional > 0)
660676
gapBits.push(
661-
`${fieldGaps.optional} optional field${fieldGaps.optional === 1 ? '' : 's'}`,
677+
`${fieldGaps().optional} optional field${fieldGaps().optional === 1 ? '' : 's'}`,
662678
)
663679
if (gapBits.length) parts.push(`Missing: ${gapBits.join(', ')}`)
664680
return parts.join('. ')
665-
})()
666-
const missingFieldsLine = (() => {
681+
})
682+
const missingFieldsLine = createMemo(() => {
667683
const bits: Array<string> = []
668-
if (fieldGaps.required > 0) bits.push(`${fieldGaps.required} required`)
669-
if (fieldGaps.recommended > 0)
670-
bits.push(`${fieldGaps.recommended} recommended`)
671-
if (fieldGaps.optional > 0) bits.push(`${fieldGaps.optional} optional`)
684+
if (fieldGaps().required > 0) bits.push(`${fieldGaps().required} required`)
685+
if (fieldGaps().recommended > 0)
686+
bits.push(`${fieldGaps().recommended} recommended`)
687+
if (fieldGaps().optional > 0) bits.push(`${fieldGaps().optional} optional`)
672688
if (bits.length === 0) return null
673689
return `Missing schema fields: ${bits.join(' · ')}`
674-
})()
690+
})
675691

676692
return (
677693
<Section>
@@ -693,7 +709,7 @@ export function JsonLdPreviewSection() {
693709
</div>
694710
</div>
695711
<Show
696-
when={entries.length > 0}
712+
when={entries().length > 0}
697713
fallback={
698714
<div class={styles().seoMissingTagsSection}>
699715
No JSON-LD scripts were detected on this page.
@@ -703,37 +719,39 @@ export function JsonLdPreviewSection() {
703719
<div class={s.seoJsonLdHealthCard}>
704720
<div class={s.seoHealthHeaderRow}>
705721
<span class={s.seoJsonLdHealthTitle}>JSON-LD Health</span>
706-
<span class={healthScoreClass()}>{score}%</span>
722+
<span class={healthScoreClass()}>{score()}%</span>
707723
</div>
708724
<div
709725
class={s.seoHealthTrack}
710726
role="progressbar"
711727
aria-valuemin={0}
712728
aria-valuemax={100}
713-
aria-valuenow={Math.round(score)}
714-
aria-label={progressAriaLabel}
729+
aria-valuenow={Math.round(score())}
730+
aria-label={progressAriaLabel()}
715731
>
716732
<div
717733
class={healthFillClass()}
718-
style={{ width: `${Math.min(100, Math.max(0, score))}%` }}
734+
style={{ width: `${Math.min(100, Math.max(0, score()))}%` }}
719735
/>
720736
</div>
721737
<div class={s.seoHealthCountsRow}>
722738
<span class={s.seoHealthCountError}>
723-
{errorCount} error{errorCount === 1 ? '' : 's'}
739+
{errorCount()} error{errorCount() === 1 ? '' : 's'}
724740
</span>
725741
<span class={s.seoHealthCountWarning}>
726-
{warningCount} warning{warningCount === 1 ? '' : 's'}
742+
{warningCount()} warning{warningCount() === 1 ? '' : 's'}
727743
</span>
728744
<span class={s.seoHealthCountInfo}>
729-
{infoCount} info{infoCount === 1 ? '' : 's'} (2 pts each)
745+
{infoCount()} info{infoCount() === 1 ? '' : 's'} (2 pts each)
730746
</span>
731747
</div>
732-
<Show when={missingFieldsLine}>
733-
<div class={s.seoJsonLdHealthMissingLine}>{missingFieldsLine}</div>
748+
<Show when={missingFieldsLine()}>
749+
<div class={s.seoJsonLdHealthMissingLine}>
750+
{missingFieldsLine()}
751+
</div>
734752
</Show>
735753
</div>
736-
<For each={entries}>
754+
<For each={entries()}>
737755
{(entry, index) => <JsonLdBlock entry={entry} index={index()} />}
738756
</For>
739757
</Show>

0 commit comments

Comments
 (0)