Skip to content

Commit 73d9f0a

Browse files
authored
Simplify (#8)
* move to calculate_stylesheet_coverage * WIP: simplify steps like prettification etc. * rm css-tree * split up in smaller files * some tests, remove ranges from output
1 parent 2b65190 commit 73d9f0a

19 files changed

+401
-501
lines changed

package-lock.json

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@
5252
},
5353
"dependencies": {
5454
"@projectwallace/format-css": "^2.1.1",
55-
"css-tree": "^3.1.0",
5655
"valibot": "^1.1.0"
5756
}
5857
}

src/chunkify.test.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { test, expect } from '@playwright/test'
2+
import { chunkify_stylesheet, type ChunkedCoverage } from './chunkify'
3+
4+
test('creates chunks with outer chunks covered', () => {
5+
let coverage = {
6+
text: 'a { color: red; } b { color: green; } c { color: blue; }',
7+
ranges: [
8+
{ start: 0, end: 17 },
9+
{ start: 38, end: 56 },
10+
],
11+
url: 'https://example.com',
12+
}
13+
let result = chunkify_stylesheet(coverage)
14+
expect(result).toEqual({
15+
...coverage,
16+
chunks: [
17+
{
18+
start_offset: 0,
19+
end_offset: 17,
20+
is_covered: true,
21+
},
22+
{
23+
start_offset: 17,
24+
end_offset: 38,
25+
is_covered: false,
26+
},
27+
{
28+
start_offset: 38,
29+
end_offset: 56,
30+
is_covered: true,
31+
},
32+
],
33+
} satisfies ChunkedCoverage)
34+
})
35+
36+
test('creates chunks with only middle chunk covered', () => {
37+
let coverage = {
38+
text: 'a { color: red; } b { color: green; } c { color: blue; }',
39+
ranges: [{ start: 17, end: 38 }],
40+
url: 'https://example.com',
41+
}
42+
let result = chunkify_stylesheet(coverage)
43+
expect(result).toEqual({
44+
...coverage,
45+
chunks: [
46+
{
47+
start_offset: 0,
48+
end_offset: 17,
49+
is_covered: false,
50+
},
51+
{
52+
start_offset: 17,
53+
end_offset: 38,
54+
is_covered: true,
55+
},
56+
{
57+
start_offset: 38,
58+
end_offset: 56,
59+
is_covered: false,
60+
},
61+
],
62+
} satisfies ChunkedCoverage)
63+
})
64+
65+
test('creates a single chunk when all is covered', () => {
66+
let coverage = {
67+
text: 'a { color: red; } b { color: green; } c { color: blue; }',
68+
ranges: [{ start: 0, end: 56 }],
69+
url: 'https://example.com',
70+
}
71+
let result = chunkify_stylesheet(coverage)
72+
expect(result).toEqual({
73+
...coverage,
74+
chunks: [
75+
{
76+
start_offset: 0,
77+
end_offset: 56,
78+
is_covered: true,
79+
},
80+
],
81+
} satisfies ChunkedCoverage)
82+
})
83+
84+
test('creates a single chunk when none is covered', () => {
85+
let coverage = {
86+
text: 'a { color: red; } b { color: green; } c { color: blue; }',
87+
ranges: [],
88+
url: 'https://example.com',
89+
}
90+
let result = chunkify_stylesheet(coverage)
91+
expect(result).toEqual({
92+
...coverage,
93+
chunks: [
94+
{
95+
start_offset: 0,
96+
end_offset: 56,
97+
is_covered: false,
98+
},
99+
],
100+
} satisfies ChunkedCoverage)
101+
})

src/chunkify.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { Coverage } from './parse-coverage'
2+
3+
export type ChunkedCoverage = Coverage & {
4+
chunks: {
5+
start_offset: number
6+
end_offset: number
7+
is_covered: boolean
8+
}[]
9+
}
10+
11+
// TODO: get rid of empty chunks, merge first/last with adjecent covered block
12+
export function chunkify_stylesheet(stylesheet: Coverage): ChunkedCoverage {
13+
let chunks = []
14+
let offset = 0
15+
16+
for (let range of stylesheet.ranges) {
17+
// Create non-covered chunk
18+
if (offset !== range.start) {
19+
chunks.push({
20+
start_offset: offset,
21+
end_offset: range.start,
22+
is_covered: false,
23+
})
24+
offset = range.start
25+
}
26+
27+
chunks.push({
28+
start_offset: range.start,
29+
end_offset: range.end,
30+
is_covered: true,
31+
})
32+
offset = range.end
33+
}
34+
35+
// fill up last chunk if necessary:
36+
if (offset !== stylesheet.text.length) {
37+
chunks.push({
38+
start_offset: offset,
39+
end_offset: stylesheet.text.length,
40+
is_covered: false,
41+
})
42+
}
43+
44+
return {
45+
...stylesheet,
46+
chunks,
47+
}
48+
}

src/css-tree.d.ts

Lines changed: 0 additions & 61 deletions
This file was deleted.

src/decuplicate.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import type { Coverage, Range } from './parse-coverage.ts'
66
* - if a duplicate stylesheet enters the room, we add it's ranges to the existing stylesheet's ranges
77
* - only bytes of deduplicated stylesheets are counted
88
*/
9-
export function deduplicate_entries(entries: Coverage[]): Map<NonNullable<Coverage['text']>, Pick<Coverage, 'ranges' | 'url'>> {
9+
export function deduplicate_entries(entries: Coverage[]): Coverage[] {
1010
let checked_stylesheets = new Map<string, { url: string; ranges: Range[] }>()
1111

1212
for (let entry of entries) {
13-
let text = entry.text || ''
13+
let text = entry.text
1414
if (checked_stylesheets.has(text)) {
1515
let sheet = checked_stylesheets.get(text)!
1616
let ranges = sheet.ranges
@@ -36,5 +36,5 @@ export function deduplicate_entries(entries: Coverage[]): Map<NonNullable<Covera
3636
}
3737
}
3838

39-
return checked_stylesheets
39+
return Array.from(checked_stylesheets, ([text, { url, ranges }]) => ({ text, url, ranges }))
4040
}

src/deduplicate.test.ts

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@ test('handles a single entry', () => {
77
ranges: [{ start: 0, end: 4 }],
88
url: 'example.com',
99
}
10-
expect(deduplicate_entries([entry])).toEqual(new Map([[entry.text, { url: entry.url, ranges: entry.ranges }]]))
10+
expect(deduplicate_entries([entry])).toEqual([entry])
1111
})
1212

13-
test('deduplicats a simple duplicate entry', () => {
13+
test('deduplicates a simple duplicate entry', () => {
1414
let entry = {
1515
text: 'a {}',
1616
ranges: [{ start: 0, end: 4 }],
1717
url: 'example.com',
1818
}
19-
expect(deduplicate_entries([entry, entry])).toEqual(new Map([[entry.text, { url: entry.url, ranges: entry.ranges }]]))
19+
expect(deduplicate_entries([entry, entry])).toEqual([entry])
2020
})
2121

2222
test('merges two identical texts with different URLs and identical ranges', () => {
@@ -33,7 +33,7 @@ test('merges two identical texts with different URLs and identical ranges', () =
3333
},
3434
]
3535
let first = entries.at(0)!
36-
expect(deduplicate_entries(entries)).toEqual(new Map([[first.text, { url: first.url, ranges: first.ranges }]]))
36+
expect(deduplicate_entries(entries)).toEqual([{ text: first.text, url: first.url, ranges: first.ranges }])
3737
})
3838

3939
test('merges different ranges on identical CSS, different URLs', () => {
@@ -50,9 +50,7 @@ test('merges different ranges on identical CSS, different URLs', () => {
5050
},
5151
]
5252
let first = entries.at(0)!
53-
expect(deduplicate_entries(entries)).toEqual(
54-
new Map([[first.text, { url: first.url, ranges: [first.ranges[0], entries[1]!.ranges[0]] }]]),
55-
)
53+
expect(deduplicate_entries(entries)).toEqual([{ text: first.text, url: first.url, ranges: [first.ranges[0], entries[1]!.ranges[0]] }])
5654
})
5755

5856
test('merges different ranges on identical CSS, identical URLs', () => {
@@ -68,9 +66,9 @@ test('merges different ranges on identical CSS, identical URLs', () => {
6866
url: 'example.com',
6967
},
7068
]
71-
expect(deduplicate_entries(entries)).toEqual(
72-
new Map([[entries[0]!.text, { url: entries[0]!.url, ranges: [entries[0]!.ranges[0], entries[1]!.ranges[0]] }]]),
73-
)
69+
expect(deduplicate_entries(entries)).toEqual([
70+
{ text: entries[0]!.text, url: entries[0]!.url, ranges: [entries[0]!.ranges[0], entries[1]!.ranges[0]] },
71+
])
7472
})
7573

7674
test('does not merge different CSS with different URLs and identical ranges', () => {
@@ -86,12 +84,10 @@ test('does not merge different CSS with different URLs and identical ranges', ()
8684
url: 'example.com/b',
8785
},
8886
]
89-
expect(deduplicate_entries(entries)).toEqual(
90-
new Map([
91-
[entries[0]!.text, { url: entries[0]!.url, ranges: entries[0]!.ranges }],
92-
[entries[1]!.text, { url: entries[1]!.url, ranges: entries[1]!.ranges }],
93-
]),
94-
)
87+
expect(deduplicate_entries(entries)).toEqual([
88+
{ text: entries[0]!.text, url: entries[0]!.url, ranges: entries[0]!.ranges },
89+
{ text: entries[1]!.text, url: entries[1]!.url, ranges: entries[1]!.ranges },
90+
])
9591
})
9692

9793
test('does not merge different CSS with same URLs and identical ranges', () => {
@@ -107,10 +103,8 @@ test('does not merge different CSS with same URLs and identical ranges', () => {
107103
url: 'example.com',
108104
},
109105
]
110-
expect(deduplicate_entries(entries)).toEqual(
111-
new Map([
112-
[entries[0]!.text, { url: entries[0]!.url, ranges: entries[0]!.ranges }],
113-
[entries[1]!.text, { url: entries[1]!.url, ranges: entries[1]!.ranges }],
114-
]),
115-
)
106+
expect(deduplicate_entries(entries)).toEqual([
107+
{ text: entries[0]!.text, url: entries[0]!.url, ranges: entries[0]!.ranges },
108+
{ text: entries[1]!.text, url: entries[1]!.url, ranges: entries[1]!.ranges },
109+
])
116110
})

src/extend-ranges.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { test, expect } from '@playwright/test'
2+
import { extend_ranges } from './extend-ranges'
3+
4+
// TODO

src/extend-ranges.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import type { Coverage } from './parse-coverage'
2+
3+
/**
4+
* WARNING: mutates the ranges array
5+
*/
6+
export function extend_ranges(coverage: Coverage[]): Coverage[] {
7+
// Adjust ranges to include @-rule name (only preludes included)
8+
// Note: Cannot reliably include closing } because it may not be the end of the range
9+
const LONGEST_ATRULE_NAME = '@-webkit-font-feature-values'.length
10+
11+
for (let stylesheet of coverage) {
12+
for (let range of stylesheet.ranges) {
13+
// Heuristic: atrule names are no longer than LONGEST_ATRULE_NAME
14+
for (let i = 1; i >= -LONGEST_ATRULE_NAME; i--) {
15+
let char_position = range.start + i
16+
if (stylesheet.text.charAt(char_position) === '@') {
17+
range.start = char_position
18+
break
19+
}
20+
}
21+
}
22+
}
23+
24+
return coverage
25+
}

src/filter-entries.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ export function filter_coverage(coverage: Coverage[], parse_html?: Parser): Cove
1111
let result = []
1212

1313
for (let entry of coverage) {
14-
if (!entry.text) continue
1514
let extension = ext(entry.url).toLowerCase()
1615
if (extension === 'js') continue
1716

0 commit comments

Comments
 (0)