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
96 changes: 50 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,52 +19,33 @@ npm install @projectwallace/css-code-coverage

### Prerequisites

1. You have collected browser coverage data of your CSS. There are several ways to do this:

1. in the browser devtools in [Edge](https://learn.microsoft.com/en-us/microsoft-edge/devtools-guide-chromium/coverage/)/[Chrome](https://developer.chrome.com/docs/devtools/coverage/)/chromium
1. Via the `coverage.startCSSCoverage()` API that headless browsers like [Playwright](https://playwright.dev/docs/api/class-coverage#coverage-start-css-coverage) or [Puppeteer](https://pptr.dev/api/puppeteer.coverage.startcsscoverage/) provide.

Either way you end up with one or more JSON files that contain coverage data.

```ts
// Read a single JSON or a folder full of JSON files with coverage data
// Coverage data looks like this:
// {
// url: 'https://www.projectwallace.com/style.css',
// text: 'a { color: blue; text-decoration: underline; }', etc.
// ranges: [
// { start: 0, end: 46 }
// ]
// }
import { parse_coverage } from '@projectwallace/css-code-coverage'

let files = await fs.glob('./css-coverage/**/*.json')
let coverage_data = []

for (let file of files) {
let json_content = await fs.readFile(file, 'urf-8')
coverage_data.push(...parse_coverage(json_content))
}
```

1. You provide a HTML parser that we use to 'scrape' the HTML in case the browser gives us not just plain CSS contents. Depending on where you run this analysis you can use:

1. Browser:
```ts
function parse_html(html) {
return new DOMParser().parseFromString(html, 'text/html')
}
```
1. Node (using [linkedom](https://github.com/WebReflection/linkedom) in this example):

```ts
// $ npm install linkedom
import { DOMParser } from 'linkedom'

function parse_html(html: string) {
return new DOMParser().parseFromString(html, 'text/html')
}
```
You have collected browser coverage data of your CSS. There are several ways to do this:

1. in the browser devtools in [Edge](https://learn.microsoft.com/en-us/microsoft-edge/devtools-guide-chromium/coverage/)/[Chrome](https://developer.chrome.com/docs/devtools/coverage/)/chromium
1. Via the `coverage.startCSSCoverage()` API that headless browsers like [Playwright](https://playwright.dev/docs/api/class-coverage#coverage-start-css-coverage) or [Puppeteer](https://pptr.dev/api/puppeteer.coverage.startcsscoverage/) provide.

Either way you end up with one or more JSON files that contain coverage data.

```ts
// Read a single JSON or a folder full of JSON files with coverage data
// Coverage data looks like this:
// {
// url: 'https://www.projectwallace.com/style.css',
// text: 'a { color: blue; text-decoration: underline; }', etc.
// ranges: [
// { start: 0, end: 46 }
// ]
// }
import { parse_coverage } from '@projectwallace/css-code-coverage'

let files = await fs.glob('./css-coverage/**/*.json')
let coverage_data = []

for (let file of files) {
let json_content = await fs.readFile(file, 'urf-8')
coverage_data.push(...parse_coverage(json_content))
}
```

### Bringing it together

Expand All @@ -73,3 +54,26 @@ import { calculate_coverage } from '@projectwallace/css-code-coverage'

let report = calculcate_coverage(coverage_data, parse_html)
```

See [src/index.ts](https://github.com/projectwallace/css-code-coverage/blob/main/src/index.ts) for the data that's returned.

### Optional: coverage from `<style>` blocks

Covergae generators also create coverage ranges for `<style>` blocks in HTML. If this applies to your code you should provide a HTML parser that we use to 'scrape' the HTML in case the browser gives us not just plain CSS contents. Depending on where you run this analysis you can use:

1. Browser:
```ts
function parse_html(html) {
return new DOMParser().parseFromString(html, 'text/html')
}
```
1. Node (using [linkedom](https://github.com/WebReflection/linkedom) in this example):

```ts
// $ npm install linkedom
import { DOMParser } from 'linkedom'

function parse_html(html: string) {
return new DOMParser().parseFromString(html, 'text/html')
}
```
11 changes: 11 additions & 0 deletions src/filter-entries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,14 @@ test('keeps extension-less URL with CSS text (running coverage in vite dev mode)
]
expect(filter_coverage(entries, html_parser)).toEqual(entries)
})

test('skips extension-less URL with HTML text when no parser is provided', () => {
let entries = [
{
url: 'http://example.com',
text: `<html><style>a{color:red;}</style></html>`,
ranges: [{ start: 13, end: 26 }],
},
]
expect(filter_coverage(entries)).toEqual([])
})
7 changes: 6 additions & 1 deletion src/filter-entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ function is_html(text: string): boolean {
return /<\/?(html|body|head|div|span|script|style)/i.test(text)
}

export function filter_coverage(coverage: Coverage[], parse_html: Parser): Coverage[] {
export function filter_coverage(coverage: Coverage[], parse_html?: Parser): Coverage[] {
let result = []

for (let entry of coverage) {
Expand All @@ -22,6 +22,11 @@ export function filter_coverage(coverage: Coverage[], parse_html: Parser): Cover
}

if (is_html(entry.text)) {
if (!parse_html) {
// No parser provided, cannot extract CSS from HTML, silently skip this entry
continue
}

let { css, ranges } = remap_html(parse_html, entry.text, entry.ranges)
result.push({
url: entry.url,
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function ratio(fraction: number, total: number) {
* 4. Calculate used/unused CSS bytes (fastest path, no inspection of the actual CSS needed)
* 5. Calculate line-coverage, byte-coverage per stylesheet
*/
export function calculate_coverage(coverage: Coverage[], parse_html: Parser): CoverageResult {
export function calculate_coverage(coverage: Coverage[], parse_html?: Parser): CoverageResult {
let total_files_found = coverage.length

if (!is_valid_coverage(coverage)) {
Expand Down