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
9 changes: 7 additions & 2 deletions src/lib/chunkify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export type ChunkedCoverage = Omit<Coverage, 'ranges'> & {
chunks: Chunk[]
}

const WHITESPACE_ONLY_REGEX = /^\s+$/

function merge(stylesheet: ChunkedCoverage): ChunkedCoverage {
let new_chunks: Chunk[] = []
let previous_chunk: Chunk | undefined
Expand All @@ -18,7 +20,7 @@ function merge(stylesheet: ChunkedCoverage): ChunkedCoverage {
let chunk = stylesheet.chunks.at(i)!

// If the current chunk is only whitespace or empty, ignore it
if (/^\s+$/.test(stylesheet.text.slice(chunk.start_offset, chunk.end_offset))) {
if (WHITESPACE_ONLY_REGEX.test(stylesheet.text.slice(chunk.start_offset, chunk.end_offset))) {
continue
}

Expand All @@ -32,7 +34,10 @@ function merge(stylesheet: ChunkedCoverage): ChunkedCoverage {
continue
}
// If the current chunk is only whitespace or empty, add it to the previous
else if (/^\s+$/.test(stylesheet.text.slice(chunk.start_offset, chunk.end_offset)) || chunk.end_offset === chunk.start_offset) {
else if (
WHITESPACE_ONLY_REGEX.test(stylesheet.text.slice(chunk.start_offset, chunk.end_offset)) ||
chunk.end_offset === chunk.start_offset
) {
latest_chunk.end_offset = chunk.end_offset
// do not update previous_chunk
continue
Expand Down
120 changes: 38 additions & 82 deletions src/lib/decuplicate.ts
Original file line number Diff line number Diff line change
@@ -1,104 +1,60 @@
import type { Coverage, Range } from './parse-coverage.js'

// Combine multiple adjecent ranges into a single one
function concatenate(ranges: Set<Range> | Range[]): Range[] {
let result: Range[] = []
// 1. Merge and concatenate ranges
function merge_ranges(ranges: Range[]): Range[] {
if (ranges.length === 0) return []

for (let range of ranges) {
// Update the last range if this range starts at last-range-end + 1
if (result.length > 0 && (result.at(-1)!.end === range.start - 1 || result.at(-1)!.end === range.start)) {
result.at(-1)!.end = range.end
// sort by start
ranges.sort((a, b) => a.start - b.start)

let merged: Range[] = [ranges[0]!]

for (let r of ranges.slice(1)) {
let last = merged.at(-1)

// merge overlapping or adjacent
if (last && r.start <= last.end + 1) {
if (r.end > last.end) {
last.end = r.end
}
} else {
result.push(range)
merged.push({ start: r.start, end: r.end })
}
}

return result
return merged
}

function dedupe_list(ranges: Range[]): Set<Range> {
let new_ranges: Set<Range> = new Set()
// 2. Merge ranges for a single stylesheet entry into an existing grouped sheet
function merge_entry_ranges(sheet: { url: string; ranges: Range[] } | undefined, entry: Coverage): { url: string; ranges: Range[] } {
if (!sheet) {
return { url: entry.url, ranges: [...entry.ranges] }
}

outer: for (let range of ranges) {
for (let processed_range of new_ranges) {
// Case: an existing range fits within this range -> replace it
// { start: 0, end: 100 },
// { start: 0, end: 200 }
if (range.start <= processed_range.start && range.end >= processed_range.end) {
new_ranges.delete(processed_range)
new_ranges.add(range)
continue outer
}
let seen = new Set(sheet.ranges.map((r) => `${r.start}:${r.end}`))

// Case: this range fits within an existing range -> skip it
// { start: 324, end: 485 }, --> exists
// { start: 364, end: 485 }, --> skip
// { start: 404, end: 485 }, --> skip
if (range.start >= processed_range.start && range.end <= processed_range.end) {
// console.log('skip', range)
continue outer
}
// Case: ranges partially overlap
// { start: 324, end: 444 },
// { start: 364, end: 485 },
if (range.start < processed_range.end && range.start > processed_range.start && range.end > processed_range.end) {
new_ranges.delete(processed_range)
new_ranges.add({
start: processed_range.start,
end: range.end,
})
continue outer
}
for (let range of entry.ranges) {
let id = `${range.start}:${range.end}`
if (!seen.has(id)) {
seen.add(id)
sheet.ranges.push({ ...range })
}
new_ranges.add(range)
}

return new_ranges
return sheet
}

/**
* @description
* prerequisites
* - we check each stylesheet content only once (to avoid counting the same content multiple times)
* - if a duplicate stylesheet enters the room, we add it's ranges to the existing stylesheet's ranges
* - only bytes of deduplicated stylesheets are counted
*/
// 3. Main function orchestrating the grouping and range merging
export function deduplicate_entries(entries: Coverage[]): Coverage[] {
let checked_stylesheets = new Map<string, { url: string; ranges: Range[] }>()

for (let entry of entries) {
let text = entry.text
if (checked_stylesheets.has(text)) {
let sheet = checked_stylesheets.get(text)!
let ranges = sheet.ranges
// Check if the ranges are already in the checked_stylesheets map
// If not, add them
for (let range of entry.ranges) {
let found = false

for (let checked_range of ranges) {
// find exact range
if (checked_range.start === range.start && checked_range.end === range.end) {
found = true
break
}
}

if (!found) {
ranges.push(range)
}
}
} else {
checked_stylesheets.set(text, {
url: entry.url,
ranges: entry.ranges,
})
}
}
let grouped = entries.reduce<Record<string, { url: string; ranges: Range[] }>>((acc, entry) => {
let key = entry.text
acc[key] = merge_entry_ranges(acc[key], entry)
return acc
}, Object.create(null))

return Array.from(checked_stylesheets, ([text, { url, ranges }]) => ({
return Object.entries(grouped).map(([text, { url, ranges }]) => ({
text,
url,
ranges: concatenate(dedupe_list(ranges.sort((a, b) => a.start - b.start))).sort((a, b) => a.start - b.start),
ranges: merge_ranges(ranges),
}))
}
96 changes: 44 additions & 52 deletions src/lib/extend-ranges.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ test.describe('leaves ranges intact when nothing to change', () => {
// Expect the incomplete coverage reported by the browser
expect(coverage.at(0)!.ranges).toEqual([{ start: 0, end: 17 }])

let result = extend_ranges(coverage)
expect(result.at(0)!.ranges).toEqual([{ start: 0, end: 17 }])
let result = extend_ranges(coverage[0])
expect(result.ranges).toEqual([{ start: 0, end: 17 }])
})
})

Expand All @@ -37,8 +37,8 @@ test.describe('@rules', () => {
// Expect the incomplete coverage reported by the browser
expect(coverage.at(0)!.ranges).toEqual([{ start: 7, end: 42 }]) // (min-width:100px){body{color:green}

let result = extend_ranges(coverage)
expect(result.at(0)!.ranges).toEqual([{ start: 0, end: 43 }]) // @media (min-width:100px){body{color:green}}
let result = extend_ranges(coverage[0])
expect(result.ranges).toEqual([{ start: 0, end: 43 }]) // @media (min-width:100px){body{color:green}}
})

test.describe('adjecent to uncovered code', () => {
Expand All @@ -49,8 +49,8 @@ test.describe('@rules', () => {
// Expect the incomplete coverage reported by the browser
expect(coverage.at(0)!.ranges).toEqual([{ start: 10, end: 45 }]) // (min-width:100px){body{color:green}

let result = extend_ranges(coverage)
expect(result.at(0)!.ranges).toEqual([{ start: 3, end: 46 }]) // @media (min-width:100px){body{color:green}}
let result = extend_ranges(coverage[0])
expect(result.ranges).toEqual([{ start: 3, end: 46 }]) // @media (min-width:100px){body{color:green}}
})

test('@media at start', async () => {
Expand All @@ -60,8 +60,8 @@ test.describe('@rules', () => {
// Expect the incomplete coverage reported by the browser
expect(coverage.at(0)!.ranges).toEqual([{ start: 7, end: 42 }]) // (min-width:100px){body{color:green}

let result = extend_ranges(coverage)
expect(result.at(0)!.ranges).toEqual([{ start: 0, end: 43 }]) // @media (min-width:100px){body{color:green}}
let result = extend_ranges(coverage[0])
expect(result.ranges).toEqual([{ start: 0, end: 43 }]) // @media (min-width:100px){body{color:green}}
})
})

Expand All @@ -71,57 +71,49 @@ test.describe('@rules', () => {
let coverage = (await generate_coverage(html, { link_css: css })) as Coverage[]

// Expect the incomplete coverage reported by the browser
expect(coverage).toEqual([
{
url: 'http://localhost/style.css',
text: css,
ranges: [
{ start: 0, end: 12 }, // p{color:red}
{ start: 19, end: 54 }, // (min-width:100px){body{color:green}
],
},
])

let result = extend_ranges(coverage)
expect(result).toEqual([
{
url: 'http://localhost/style.css',
text: css,
ranges: [
{ start: 0, end: 12 }, // p{color:red}
{ start: 12, end: 55 }, // @media (min-width:100px){body{color:green}}
],
},
])
expect(coverage[0]).toEqual({
url: 'http://localhost/style.css',
text: css,
ranges: [
{ start: 0, end: 12 }, // p{color:red}
{ start: 19, end: 54 }, // (min-width:100px){body{color:green}
],
})

let result = extend_ranges(coverage[0])
expect(result).toEqual({
url: 'http://localhost/style.css',
text: css,
ranges: [
{ start: 0, end: 12 }, // p{color:red}
{ start: 12, end: 55 }, // @media (min-width:100px){body{color:green}}
],
})
})

test('@media at start', async () => {
let css = `@media (min-width:100px){body{color:green}}p{color:red}`
let coverage = (await generate_coverage(html, { link_css: css })) as Coverage[]

// Expect the incomplete coverage reported by the browser
expect(coverage).toEqual([
{
url: 'http://localhost/style.css',
text: css,
ranges: [
{ start: 7, end: 42 }, // (min-width:100px){body{color:green}
{ start: 43, end: 55 }, // p{color:red}
],
},
])

let result = extend_ranges(coverage)
expect(result).toEqual([
{
url: 'http://localhost/style.css',
text: css,
ranges: [
{ start: 0, end: 43 }, // @media (min-width:100px){body{color:green}}
{ start: 43, end: 55 }, // p{color:red}
],
},
])
expect(coverage[0]).toEqual({
url: 'http://localhost/style.css',
text: css,
ranges: [
{ start: 7, end: 42 }, // (min-width:100px){body{color:green}
{ start: 43, end: 55 }, // p{color:red}
],
})

let result = extend_ranges(coverage[0])
expect(result).toEqual({
url: 'http://localhost/style.css',
text: css,
ranges: [
{ start: 0, end: 43 }, // @media (min-width:100px){body{color:green}}
{ start: 43, end: 55 }, // p{color:red}
],
})
})
})
})
Loading