Skip to content

Extract external CSS stylesheets from HAR for Baseline feature detection#317

Open
smsnedden wants to merge 4 commits into
cloudflare:mainfrom
smsnedden:baseline-css-extract
Open

Extract external CSS stylesheets from HAR for Baseline feature detection#317
smsnedden wants to merge 4 commits into
cloudflare:mainfrom
smsnedden:baseline-css-extract

Conversation

@smsnedden

Copy link
Copy Markdown

First slice of the --baseline CSS analysis feature. This PR adds extraction of external stylesheets from a HAR; inline <style> extraction, feature detection (css-tree), and Baseline lookup (web-features) follow in later PRs.

The extractor is intentionally not wired into the CLI or launchTest yet — it has no production caller until the detection PR consumes it, so it's exercised entirely by its unit tests. Keeping the slices separate keeps each PR small and isolates new dependencies to the PR that introduces them.

Changes

extractCSSFromHar(harData): CSSSource[] (src/baselineCssExtract.ts)

Collects external CSS a page shipped, from a parsed HAR:

  • Selects responses whose mimeType starts with text/css (case-insensitive, so Text/CSS; charset=utf-8 matches), using the response body as the CSS source.
  • Decodes base64 response bodies to UTF-8 (when HAR content.encoding === 'base64').
  • Returns sources in HAR order; entries with no response body are skipped.

Inline <style> blocks are out of scope here — they require parsing the HTML document, added in a follow-up PR with a dedicated parser.

Types (src/types.ts)

  • Add CSSSource ({ css, file }).
  • Extend HarEntry.response.content with optional text? and encoding? fields (response bodies weren't previously modeled).

Tests

Adds packages/telescope/__tests__/baselineCssExtract.test.ts — 7 pure unit tests (no browser) built from in-memory HAR fixtures:

  • External text/css extracted with the URL as file
  • text/css; charset=utf-8 still matches
  • base64-encoded CSS bodies are decoded
  • Multiple stylesheets preserve HAR order
  • Edge cases: empty HAR, entries with no body, and non-CSS resources (including HTML) ignored

Add extractCSSFromHar() which collects external text/css stylesheets shipped
by a page from a HAR, decoding base64 response bodies and recording each
source's origin file.

This is the first slice of the Baseline (--baseline) CSS analysis feature.
Inline <style> extraction (via a dedicated HTML parser) and CSS feature
detection over these sources follow in later changes.

- Add CSSSource type and extend HarEntry response content with optional
  text/encoding fields in types.ts
- Add 7 unit tests covering external extraction, base64 decoding, charset
  handling, ordering, and ignored resources
Comment thread packages/telescope/src/types.ts
Comment thread packages/telescope/src/types.ts Outdated
Comment thread packages/telescope/src/baselineCssExtract.ts
@sergeychernyshev

Copy link
Copy Markdown
Member

Looks good. Just minor changes in typing, also need to understand in general if HAR will always contain bodies or not and what controls it.

@sergeychernyshev sergeychernyshev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good - up to you if you want to do a bit more strict type checks here.

@smsnedden

Copy link
Copy Markdown
Author

Looks good - up to you if you want to do a bit more strict type checks here.

Sounds good, made a small change to line 39 for a bit more strict of a type check.

@sergeychernyshev sergeychernyshev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use a constant and type instead of repeating the string itself.

The reason for such abstraction is to easily add other options through types, for example, without rewriting everything in the future.

It is a minor optimization, but helpful.

* @param encoding - The `content.encoding` value, if present.
* @returns The decoded body as UTF-8 text.
*/
function decodeContent(text: string, encoding?: 'base64'): string {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this as a constant to types.ts and create a type and constant, something like

export const BASE64 = 'base64';
export type HARContentEncoding = typeof BASE64 | undefined;
Suggested change
function decodeContent(text: string, encoding?: 'base64'): string {
function decodeContent(text: string, encoding: HARContentEncoding): string {

*/
function decodeContent(text: string, encoding?: 'base64'): string {
if (encoding === 'base64') {
return Buffer.from(text, 'base64').toString('utf8');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return Buffer.from(text, 'base64').toString('utf8');
return Buffer.from(text, BASE64).toString('utf8');

* @returns The decoded body as UTF-8 text.
*/
function decodeContent(text: string, encoding?: 'base64'): string {
if (encoding === 'base64') {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (encoding === 'base64') {
if (encoding === BASE64) {

size: number;
mimeType: string;
text?: string;
encoding?: 'base64';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
encoding?: 'base64';
encoding: HARContentEncoding;

}

const toBase64 = (value: string) =>
Buffer.from(value, 'utf8').toString('base64');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Buffer.from(value, 'utf8').toString('base64');
Buffer.from(value, 'utf8').toString(BASE64);

it('decodes base64-encoded CSS bodies', () => {
const css = '.a { color: blue; }';
const har = makeHar([
makeEntry('https://x.test/a.css', 'text/css', toBase64(css), 'base64'),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
makeEntry('https://x.test/a.css', 'text/css', toBase64(css), 'base64'),
makeEntry('https://x.test/a.css', 'text/css', toBase64(css), BASE64),

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants