Skip to content
Open
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
6 changes: 6 additions & 0 deletions src/app.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
declare class Highlight extends Set<Range | StaticRange> {}

interface Scheduler {
yield: () => Promise<void>
}

declare var scheduler: Scheduler

interface Window {
CSS: {
highlights: Map<string, Highlight>
Expand Down
15 changes: 8 additions & 7 deletions src/hooks.client.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { isSupported } from '@oddbird/popover-polyfill/fn'
import '$lib/polyfills/request-idle-callback'
import '$lib/polyfills/scheduler-yield'

export const init = async () => {
// Polyfill popover if necessary
if (!isSupported()) {
console.warn('Popover API not supported in this browser, applying polyfill')
await import("@oddbird/popover-polyfill/fn")
.then(({ apply }) => apply())
}
}
// Polyfill popover if necessary
if (!isSupported()) {
console.warn('Popover API not supported in this browser, applying polyfill')
await import('@oddbird/popover-polyfill/fn').then(({ apply }) => apply())
}
}
1 change: 1 addition & 0 deletions src/lib/components/DevTools.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@
border-style: solid;
border-color: transparent;
font-size: var(--size-sm);
/* TODO: contain layout/style? */

@media (min-width: 33rem) {
gap: var(--space-2);
Expand Down
4 changes: 3 additions & 1 deletion src/lib/components/ItemUsage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
use:highlight_css={{
css,
node_type: css_state.selected_item_node_type,
enabled: css_state.selected_item_node_type !== undefined
// disabled for performance reasons
enabled: false
}}
>
<!-- Technically this should contain a <code> with the specimen, but the amount of DOM nodes is too damn high -->
Expand Down Expand Up @@ -115,6 +116,7 @@

tbody tr {
cursor: pointer;
content-visibility: auto;

&:nth-child(even) {
background-color: var(--uneven-tr-bg);
Expand Down
1 change: 0 additions & 1 deletion src/lib/components/Pre.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
let supports_highlights = $state(false)
let lines: Highlight | undefined
// body element is used to scroll to the highlighted location
// svelte-ignore non_reactive_update
let body: HTMLElement | undefined
// code_node is used to highlight the code (highlighting only works on TextNodes)
// svelte-ignore non_reactive_update
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/css-form/Form.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
cleaned_url.searchParams.delete('url')
cleaned_url.searchParams.delete('prettify')
cleaned_url.hash = ''
await goto(cleaned_url, { replaceState: true })
await goto(cleaned_url, { replaceState: true, noScroll: true })

status = 'idle'

Expand All @@ -125,7 +125,7 @@
submit_type: 'file',
prettify
})
css_state.set_origins(origins)
await css_state.set_origins(origins)
css_state.url = undefined
}

Expand Down
115 changes: 73 additions & 42 deletions src/lib/components/stats/Analysis.svelte
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
<script lang="ts">
import { type Snippet } from 'svelte'
import { type Snippet, onDestroy } from 'svelte'
import Report from './Report.svelte'
import Nav from './Nav.svelte'
import Devtools from '$components/DevTools.svelte'
import type { CSSOrigin } from '$lib/css-origins'
import type { CssAnalysis } from '$lib/analyze-css'
import NetworkPanel from '$components/NetworkPanel.svelte'
import ItemUsage from '$components/ItemUsage.svelte'
import JsonPanel from '$components/devtools/JsonPanel.svelte'
import CssPanel from '$components/devtools/CssPanel.svelte'
import { analyzer_tabs, type TabId } from '$components/devtools/tabs'
import { get_css_state } from '$lib/css-state.svelte'
import { analyze } from '@projectwallace/css-analyzer'
import { PersistedState } from 'runed'
import AnalyzerWorker from '$lib/css-analyzer.worker?worker'
import { Deferred } from '$lib/deferred.svelte'

interface Props {
prettify_css_before_analyze?: boolean
Expand All @@ -24,12 +23,25 @@

let { nav, children, devtools }: Props = $props()
let css_state = get_css_state()
let analysis = $derived(
analyze(css_state.css, {
useLocations: true
})
)
let analysis: CssAnalysis | undefined = $state(undefined)
let nav_visible = new PersistedState<boolean>('analyzer-nav-visible', true)
let deferred = new Deferred()

const worker = new AnalyzerWorker()
worker.onmessage = (event: MessageEvent<{ css: string; analysis: CssAnalysis }>) => {
css_state.css = event.data.css
analysis = event.data.analysis
}

$effect(() => {
if (css_state.raw_css) {
worker.postMessage({ css: css_state.raw_css, prettify: css_state.should_prettify })
}
})

onDestroy(() => {
worker.terminate()
})

function on_keydown(event: KeyboardEvent) {
if ((event.metaKey || event.ctrlKey) && event.key === '\\') {
Expand All @@ -40,47 +52,56 @@

<svelte:window onkeydown={on_keydown} />

<div data-testid="css-report" class="analysis" data-nav-visible={nav_visible.current}>
<div class="nav print:hidden scroll-container">
{#if nav}
{@render nav()}
{:else}
<Nav />
{#if analysis}
<div data-testid="css-report" class="analysis" data-nav-visible={nav_visible.current}>
<div class="nav print:hidden scroll-container">
{#if nav}
{@render nav()}
{:else}
<Nav />
{/if}
</div>
<div class="report" data-testid="report">
{#if children}
{@render children({ analysis, css: css_state.css })}
{:else}
<Report result={analysis} />
{/if}
</div>
{#if deferred.ready}
<div class="devtools print:hidden">
{#if devtools}
{@render devtools({ analysis, css: css_state.css })}
{:else}
<Devtools tabs={analyzer_tabs}>
{#snippet children({ tab_id }: { tab_id: TabId })}
{#if tab_id === 'network'}
{#await import('$components/NetworkPanel.svelte') then { default: NetworkPanel }}
<NetworkPanel />
{/await}
{:else if tab_id === 'inspector'}
{#await import('$components/ItemUsage.svelte') then { default: ItemUsage }}
<ItemUsage />
{/await}
{:else if tab_id === 'report'}
<JsonPanel json={analysis} />
{:else if tab_id === 'css'}
<CssPanel css={css_state.css} />
{/if}
{/snippet}
</Devtools>
{/if}
</div>
{/if}
</div>
<div class="report" data-testid="report">
{#if children}
{@render children({ analysis, css: css_state.css })}
{:else}
<Report result={analysis} />
{/if}
</div>
<div class="devtools print:hidden">
{#if devtools}
{@render devtools({ analysis, css: css_state.css })}
{:else}
<Devtools tabs={analyzer_tabs}>
{#snippet children({ tab_id }: { tab_id: TabId })}
{#if tab_id === 'network'}
<NetworkPanel />
{:else if tab_id === 'inspector'}
<ItemUsage />
{:else if tab_id === 'report'}
<JsonPanel json={analysis} />
{:else if tab_id === 'css'}
<CssPanel css={css_state.css} />
{/if}
{/snippet}
</Devtools>
{/if}
</div>
</div>
{/if}

<style>
.analysis {
display: grid;
grid-template-areas: 'nav' 'report' 'devtools';
position: relative;
contain: layout style;
}

.nav {
Expand All @@ -90,6 +111,13 @@
.report {
grid-area: report;
padding: var(--space-2);
contain: layout style paint;
}

.report :global(section) {
content-visibility: auto;
contain-intrinsic-size: auto 500px;
contain: layout style;
}

.devtools {
Expand All @@ -104,6 +132,9 @@
.analysis {
grid-template-rows: 1fr auto;
column-gap: var(--space-8);
/* Set explicit min-height to avoid layout shift */
min-height: 100vh;
min-height: 100dvh;

&[data-nav-visible='true'] {
grid-template-columns: minmax(0, 1fr) clamp(12rem, 20%, 18rem);
Expand Down
8 changes: 5 additions & 3 deletions src/lib/components/stats/AtRule.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
id,
warnings = []
}: Props = $props()

function sort_alphabetical(a: [string, CssLocation[]], b: [string, CssLocation[]]) {
return string_sort(a[0], b[0])
}
</script>

<Panel {id}>
Expand All @@ -56,9 +60,7 @@
extra_sort_options={[
{
label: 'Sort A-Z',
fn: (a: [string, CssLocation[]], b: [string, CssLocation[]]) => {
return string_sort(a[0], b[0])
},
fn: sort_alphabetical,
id: 'alphabetical'
}
]}
Expand Down
15 changes: 10 additions & 5 deletions src/lib/components/stats/Report.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import Declarations from '$components/stats/Declarations.svelte'
import Values from '$components/stats/Values.svelte'
import type { CssAnalysis } from '$lib/analyze-css'
import { Deferred } from '$lib/deferred.svelte'

interface Props {
result?: CssAnalysis
Expand All @@ -15,16 +16,20 @@
let { result = Object.create(null) }: Props = $props()

let { rules, selectors, declarations, atrules, properties, values } = $derived(result)

let deferred = new Deferred()
</script>

<div class="group">
<Stylesheet {result} />
<Rulesets {rules} {atrules} {selectors} {declarations} />
<Selectors {selectors} />
<AtRules {atrules} />
<Declarations {declarations} />
<Properties {properties} />
<Values {values} />
{#if deferred.ready}
<Selectors {selectors} />
<AtRules {atrules} />
<Declarations {declarations} />
<Properties {properties} />
<Values {values} />
{/if}
</div>

<style>
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/stats/Selectors.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@
universal selectors and combinators.
</p>

{#if complexity && complexity.max}
{#if complexity?.max}
<figure>
<ScatterPlot max={complexity.max} items={complexity.items} />
<figcaption>
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/use-css-highlight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ const TIME_TO_WAIT = 50
function get_highlight_level(css_size: number, user_agent: string): number {
let level = HIGHLIGHT_LEVEL_FULL

if (css_size > 2_000_000) {
if (css_size > 500_000) {
level = HIGHLIGHT_LEVEL_MINIMAL
} else if (css_size > 1_000_000) {
} else if (css_size > 250_000) {
level = HIGHLIGHT_LEVEL_PARTIAL
} else {
level = HIGHLIGHT_LEVEL_FULL
Expand Down
14 changes: 14 additions & 0 deletions src/lib/css-analyzer.worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { analyze } from '@projectwallace/css-analyzer'
import { format } from '@projectwallace/format-css'

type WorkerInput = {
css: string
prettify: boolean
}

self.onmessage = (event: MessageEvent<WorkerInput>) => {
const { css: raw_css, prettify } = event.data
const css = prettify ? format(raw_css) : raw_css
const analysis = analyze(css, { useLocations: true })
self.postMessage({ css, analysis })
}
Loading
Loading