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
63 changes: 35 additions & 28 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ module.exports = async (url, {
// Set an explicit UserAgent, because the default UserAgent string includes something like
// `HeadlessChrome/88.0.4298.0` and some websites/CDN's block that with a HTTP 403
await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:85.0) Gecko/20100101 Firefox/85.0')
await page.coverage.startCSSCoverage()

url = normalizeUrl(url, {stripWWW: false})

let response
Expand Down Expand Up @@ -70,23 +70,44 @@ module.exports = async (url, {
return response.text()
}

const coverage = await page.coverage.stopCSSCoverage()

// Get all CSS generated with the CSSStyleSheet API
// This is primarily for CSS-in-JS solutions
// See: https://developer.mozilla.org/en-US/docs/Web/API/CSSRule/cssText
const styleSheetsApiCss = await page.evaluate(() => {
return [...document.styleSheets]
// Only take the stylesheets without href, because those with href are
// <link> tags, and we already tackled those with the Coverage API
.filter(stylesheet => stylesheet.href === null)
.map(stylesheet => {
return {
type: stylesheet.ownerNode.tagName.toLowerCase(),
href: stylesheet.href || document.location.href,
css: [...stylesheet.cssRules].map(({cssText}) => cssText).join('\n')
function getCssFromStyleSheet(stylesheet) {
var items = []
var styleType = stylesheet.ownerNode ?
stylesheet.ownerNode.tagName.toLowerCase() :
'import'

var sheetCss = ''

for (var rule of stylesheet.cssRules) {
// eslint-disable-next-line no-undef
if (rule instanceof CSSImportRule) {
var imported = getCssFromStyleSheet(rule.styleSheet)
items = items.concat(imported)
}
})

sheetCss += rule.cssText

items.push({
type: styleType,
href: stylesheet.href || document.location.href,
css: sheetCss
})
}

return items
}

let styles = []

for (const stylesheet of document.styleSheets) {
styles = styles.concat(getCssFromStyleSheet(stylesheet))
}

return styles
})

// Get all inline styles: <element style="">
Expand Down Expand Up @@ -116,21 +137,7 @@ module.exports = async (url, {

await browser.close()

const links = coverage
// Filter out the <style> tags that were found in the coverage
// report since we've conducted our own search for them.
// A coverage CSS item with the same url as the url of the page
// we requested is an indication that this was a <style> tag
.filter(entry => entry.url !== url)
.map(entry => ({
href: entry.url,
css: entry.text,
type: 'link-or-import'
}))

const css = links
.concat(styleSheetsApiCss)
.concat(inlineStyles === 'exclude' ? [] : inlineCss)
const css = styleSheetsApiCss.concat(inlineCss)

// Return the complete structure ...
if (origins === 'include') {
Expand Down
20 changes: 12 additions & 8 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,25 @@ test('it finds css in a <link> tag - HTML', async t => {

t.true(actual.includes('.link-in-html { }'))
t.true(actual.includes('@import url("import-in-css.css")'))
t.true(actual.includes('.css-imported-with-css {}'))
t.true(actual.includes('.css-imported-with-css { }'))
t.snapshot(actual)
})

test('it finds css in a <link> tag - JS', async t => {
const actual = await extractCss(server.url + '/link-tag-js.html')

t.true(actual.includes('.link-tag-created-with-js {}'))
t.true(actual.includes('.link-tag-created-with-js'))
t.true(actual.includes('@import url("import-in-css.css")'))
t.true(actual.includes('.css-imported-with-css {}'))
t.true(actual.includes('.css-imported-with-css { }'))
t.snapshot(actual)
})

test('it finds css in a <style> tag - HTML', async t => {
const actual = await extractCss(server.url + '/style-tag-html.html')

t.true(actual.includes('.fixture { color: red; }'))
t.true(actual.includes('@import url("import-in-css.css")'))
t.true(actual.includes('.css-imported-with-css {}'))
t.true(actual.includes('.css-imported-with-css { }'))
t.snapshot(actual)
})

Expand All @@ -49,14 +51,15 @@ test('it reports CSS in a <style> tag in HTML only once', async t => {
const lastOccurence = actual.lastIndexOf('.fixture')

t.is(firstOccurence, lastOccurence)
t.snapshot(actual)
})

test('it finds css in a <style> tag - JS', async t => {
const actual = await extractCss(server.url + '/style-tag-js.html')

t.true(actual.includes('.fixture { color: red; }'))
t.true(actual.includes('@import url("import-in-js.css")'))
t.true(actual.includes('.css-imported-with-js {}'))
t.true(actual.includes('.css-imported-with-js { }'))
t.snapshot(actual)
})

Expand All @@ -72,11 +75,12 @@ test('it finds CSS implemented in a mixed methods (inline, links, style tags)',
const actual = await extractCss(server.url + '/kitchen-sink.html')

t.true(actual.includes('@import url("import-in-css.css")'))
t.true(actual.includes('.css-imported-with-css {}'))
t.true(actual.includes('.css-imported-with-css { }'))
t.true(actual.includes('[x-extract-css-inline-style]'))
t.true(actual.includes('[x-extract-css-inline-style] { background-image: url(\'background-image-inline-style-attribute-in-html\'); }'))
t.true(actual.includes('[x-extract-css-inline-style] { background-image: url("background-image-inline-style-js-cssText"); }'))
t.true(actual.includes('[x-extract-css-inline-style] { background-image: url("background-image-inline-style-js-with-prop"); }'))
t.snapshot(actual)
})

test('it finds inline styles - HTML', async t => {
Expand Down Expand Up @@ -111,13 +115,13 @@ test('it returns an array of entries when the `origins` option equals `include`'
})

t.true(Array.isArray(actual), 'Result should be an array when { origins: `include` }')
t.is(actual.length, 10)
t.is(actual.length, 12)

function isString(item) {
return typeof item === 'string'
}

t.true(actual.every(item => isString(item.type) && ['link-or-import', 'style', 'inline'].includes(item.type)))
t.true(actual.every(item => isString(item.type) && ['link', 'import', 'style', 'inline'].includes(item.type)))
t.true(actual.every(item => isString(item.href)))
t.true(actual.every(item => item.href.startsWith('http://localhost:') && /\.(html|css)$/.test(item.href)))
t.true(actual.every(item => isString(item.css)))
Expand Down
49 changes: 45 additions & 4 deletions test/snapshots/index.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,69 @@ The actual snapshot is saved in `index.js.snap`.

Generated by [AVA](https://avajs.dev).

## it finds css in a <link> tag - HTML

> Snapshot 1

`.css-imported-with-css { }␊
@import url("import-in-css.css");␊
@import url("import-in-css.css");.link-in-html { }`

## it finds css in a <link> tag - JS

> Snapshot 1

`.css-imported-with-css { }␊
@import url("import-in-css.css");␊
@import url("import-in-css.css");.link-tag-created-with-js { }`

## it finds css in a <style> tag - HTML

> Snapshot 1

`.css-imported-with-css {}␊
`.css-imported-with-css { }␊
@import url("import-in-css.css");␊
.fixture { color: red; }`
@import url("import-in-css.css");.fixture { color: red; }`

## it reports CSS in a <style> tag in HTML only once

> Snapshot 1

`.css-imported-with-css { }␊
@import url("import-in-css.css");␊
@import url("import-in-css.css");.fixture { color: red; }`

## it finds css in a <style> tag - JS

> Snapshot 1

`.css-imported-with-js {}␊
`.css-imported-with-js { }␊
@import url("import-in-js.css");␊
.fixture { color: red; }`
@import url("import-in-js.css");.fixture { color: red; }`

## it finds css-in-js

> Snapshot 1

'.bcMPWx { color: blue; }'

## it finds CSS implemented in a mixed methods (inline, links, style tags)

> Snapshot 1

`.css-imported-with-css { }␊
@import url("import-in-css.css");␊
@import url("import-in-css.css");.link-in-html { }␊
.style-tag-in-html { color: green; }␊
.js-insertRule { color: red; }␊
.css-imported-with-css { }␊
@import url("import-in-css.css");␊
@import url("import-in-css.css");.link-tag-created-with-js { }␊
.style-tag-js { }␊
[x-extract-css-inline-style] { background-image: url('background-image-inline-style-attribute-in-html'); }␊
[x-extract-css-inline-style] { background-image: url("background-image-inline-style-js-cssText"); }␊
[x-extract-css-inline-style] { background-image: url("background-image-inline-style-js-with-prop"); }`

## it finds inline styles - HTML

> Snapshot 1
Expand Down
Binary file modified test/snapshots/index.js.snap
Binary file not shown.