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
6 changes: 5 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ export default [
globals: {
node: true,
es6: true,
URL: "readonly"
URL: "readonly",
fetch: "readonly",
AbortController: "readonly",
setTimeout: "readonly",
clearTimeout: "readonly"
},
parserOptions: {
ecmaVersion: 'latest',
Expand Down
57 changes: 55 additions & 2 deletions lib/harAnalyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export class HarAnalyzer {
this.version = this.package.version;
}

transform2SimplifiedData(harData, url) {
async transform2SimplifiedData(harData, url) {
const data = {
'url': url,
'rules': this.config.rules,
Expand All @@ -37,12 +37,25 @@ export class HarAnalyzer {

let reqIndex = 1;

// CSS resources whose response body is missing from the HAR (served from
// cache, 304 Not Modified, or recorded with an empty body). We refetch
// these below — otherwise the custom properties they define are absent
// from the linted CSS and every var() referencing them elsewhere is
// falsely reported as "Unknown custom property" (no-unknown-custom-properties).
const missingCssUrls = new Set();

for (const entry of harData.entries) {
const req = entry.request;
const res = entry.response;
const reqUrl = req.url;

const mimeType = (res && res.content && res.content.mimeType) || '';
const looksLikeCss = mimeType.includes('css') || /\.css(\?|#|$)/i.test(reqUrl);

if (!res.content || !res.content.text || !res.content.mimeType || !res.content.size || res.content.size <= 0 || !res.status) {
if (looksLikeCss && /^https?:\/\//i.test(reqUrl)) {
missingCssUrls.add(reqUrl);
}
continue;
}

Expand All @@ -62,6 +75,46 @@ export class HarAnalyzer {
reqIndex++;
}

// Refetch the bodies of CSS files that were absent from the HAR, skipping
// any we already captured. Failures (network error, timeout, non-OK) are
// ignored so a single unreachable file never fails the whole CSS test.
const refetchUrls = [...missingCssUrls].filter(
(cssUrl) => !data['style-files'].some((o) => o.url === cssUrl)
);
const refetched = await Promise.all(
refetchUrls.map(async (cssUrl) => {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
const response = await fetch(cssUrl, { signal: controller.signal });
clearTimeout(timeout);
if (!response.ok) {
return null;
}
const text = await response.text();
if (!text) {
return null;
}
return { url: cssUrl, content: text };
} catch {
return null;
}
})
);
for (const item of refetched) {
if (!item) {
continue;
}
const obj = {
'url': item.url,
'content': item.content,
'index': reqIndex
};
data['all-styles'].push(obj);
data['style-files'].push(obj);
reqIndex++;
}

// Extract <style> elements from HTML content
for (const htmlObj of data.htmls) {
const dom = new JSDOM(htmlObj.content);
Expand Down Expand Up @@ -229,7 +282,7 @@ export class HarAnalyzer {
this.groups[group] = {};
}

const analyzedData = this.transform2SimplifiedData(harData, url);
const analyzedData = await this.transform2SimplifiedData(harData, url);
if (!('analyzedData' in this.groups[group])) {
this.groups[group]['analyzedData'] = []
}
Expand Down