Skip to content

Commit c447f40

Browse files
authored
Add support for scraping CSS-in-JS based styles πŸ‘©β€πŸŽ€
2 parents 5576b77 + 0359379 commit c447f40

File tree

7 files changed

+7012
-42
lines changed

7 files changed

+7012
-42
lines changed

β€Ž.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
node_modules
2-
package-lock.json

β€Žpackage-lock.json

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

β€Žsrc/index.js

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,18 @@ module.exports = async (
4848
// but not all...
4949
const coverage = await page.coverage.stopCSSCoverage()
5050

51-
// Fetch all <style> tags from the page, because the coverage
52-
// API may have missed some JS-generated <style> tags.
53-
// Some of them *were* already caught by the coverage API,
54-
// but they will be removed later on to prevent duplicates.
55-
const styleTagsCss = (await page.$$eval('style', styles => {
56-
// Get the text inside each <style> tag and trim() the
57-
// results to prevent all the inside-html indentation
58-
// clogging up the results and making it look
59-
// bigger than it actually is
60-
return styles.map(style => style.innerHTML.trim())
61-
})).join('')
51+
// Get all CSS generated with the CSSStyleSheet API
52+
// See: https://developer.mozilla.org/en-US/docs/Web/API/CSSRule/cssText
53+
const styleSheetsApiCss = await page.evaluate(() => {
54+
/* global document */
55+
return [...document.styleSheets]
56+
.map(stylesheet =>
57+
[...stylesheet.cssRules]
58+
.map(cssStyleRule => cssStyleRule.cssText)
59+
.join('')
60+
)
61+
.join('')
62+
})
6263

6364
await browser.close()
6465

@@ -73,5 +74,5 @@ module.exports = async (
7374
.map(({text}) => text)
7475
.join('')
7576

76-
return Promise.resolve(coverageCss + styleTagsCss)
77+
return Promise.resolve(styleSheetsApiCss + coverageCss)
7778
}

β€Žtest/index.js

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ const puppeteerCore = require('puppeteer-core')
88
const extractCss = require('..')
99

1010
let server
11-
const expected = readFileSync(resolve(__dirname, 'fixture.css'), 'utf8')
11+
const fixture = readFileSync(resolve(__dirname, 'fixture.css'), 'utf8')
1212

1313
test.before(async () => {
1414
server = await createTestServer()
1515

1616
server.get('/fixture.css', (req, res) => {
17-
res.send(expected)
17+
res.send(fixture)
1818
})
1919
})
2020

@@ -27,11 +27,16 @@ test('it fetches css from a page with CSS in a server generated <link> inside th
2727
server.get(url, (req, res) => {
2828
res.send(`
2929
<!doctype html>
30-
<link rel="stylesheet" href="fixture.css" />
30+
<html>
31+
<head>
32+
<link rel="stylesheet" href="fixture.css" />
33+
</head>
34+
</html>
3135
`)
3236
})
3337

3438
const actual = await extractCss(server.url + url)
39+
const expected = fixture
3540

3641
t.is(actual, expected)
3742
})
@@ -41,13 +46,14 @@ test('it fetches css from a page with CSS in server generated <style> inside the
4146
server.get(url, (req, res) => {
4247
res.send(`
4348
<!doctype html>
44-
<style>${expected.trim()}</style>
49+
<style>${fixture}</style>
4550
`)
4651
})
4752

4853
const actual = await extractCss(server.url + url)
54+
const expected = 'body { color: teal; }'
4955

50-
t.is(actual, expected.trim())
56+
t.is(actual, expected)
5157
})
5258

5359
test('it finds JS generated <link /> CSS', async t => {
@@ -62,6 +68,7 @@ test('it finds JS generated <link /> CSS', async t => {
6268
})
6369

6470
const actual = await extractCss(server.url + path)
71+
const expected = fixture
6572

6673
t.is(actual, expected)
6774
})
@@ -77,7 +84,26 @@ test('it finds JS generated <style /> CSS', async t => {
7784
})
7885

7986
const actual = await extractCss(server.url + url, {waitUntil: 'load'})
80-
const expected = `body { color: teal; }`
87+
const expected = 'body { color: teal; }'
88+
89+
t.is(actual, expected)
90+
})
91+
92+
test('it finds css-in-js, like Styled Components', async t => {
93+
const url = '/css-in-js'
94+
const cssInJsExampleHtml = readFileSync(
95+
resolve(__dirname, 'css-in-js.html'),
96+
'utf8'
97+
)
98+
server.get(url, (req, res) => {
99+
res.send(cssInJsExampleHtml)
100+
})
101+
102+
const actual = await extractCss(server.url + url, {waitUntil: 'load'})
103+
// Color is RGB instead of Hex, because of serialization:
104+
// https://www.w3.org/TR/cssom-1/#serializing-css-values
105+
const expected =
106+
'html { color: rgb(255, 0, 0); }.hJHBhT { color: blue; font-family: sans-serif; font-size: 3em; }'
81107

82108
t.is(actual, expected)
83109
})
@@ -94,9 +120,11 @@ test('it combines server generated <link> and <style> tags with client side crea
94120

95121
const actual = await extractCss(server.url + path)
96122

123+
t.true(actual.includes('content: "js-style";'))
124+
t.true(actual.includes('content: "server-style";'))
125+
t.true(actual.includes(`body {`))
126+
t.true(actual.includes(`color: teal;`))
97127
t.snapshot(actual)
98-
t.true(actual.includes('counter-increment: 2;'))
99-
t.true(actual.includes('counter-increment: 3;'))
100128
})
101129

102130
test('it rejects if the url has an HTTP error status', async t => {
@@ -116,20 +144,17 @@ test('it accepts a browser override for usage with other browsers', async t => {
116144
res.send(`
117145
<!doctype html>
118146
<style>
119-
body::before {
120-
content: ${req.headers['user-agent']};
121-
}
147+
body::before { content: "${req.headers['user-agent']}"; }
122148
</style>
123149
`)
124150
})
125151
const customBrowser = await puppeteerCore.launch({
126152
executablePath: chromium.path,
127-
args: ["--user-agent='Extract CSS Core'"]
153+
args: ['--user-agent=Extract CSS Core']
128154
})
129155
const actual = await extractCss(server.url + path, {customBrowser})
130156

131-
t.snapshot(actual)
132-
t.true(actual.includes("content: 'Extract CSS Core';"))
157+
t.is(actual, 'body::before { content: "Extract CSS Core"; }')
133158
})
134159

135160
test('it rejects on an invalid customBrowser option', async t => {

β€Žtest/kitchen-sink.html

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,18 @@
99

1010
<!-- Server generated style -->
1111
<style>
12-
.server-style {
13-
counter-increment: 2;
12+
.server-style::after {
13+
content: 'server-style';
1414
}
1515
</style>
1616

1717
<h1>Title</h1>
18+
<div class="server-style">server-style:</div>
1819

1920
<script>
2021
// Client generated style
2122
var style = document.createElement('style')
22-
style.textContent = '.js-style { counter-increment: 3; }'
23+
style.textContent = '.js-style::after { content: "js-style"; }'
2324
document.body.appendChild(style)
2425

2526
// Client generated link

β€Žtest/snapshots/index.js.md

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,14 @@ The actual snapshot is saved in `index.js.snap`.
44

55
Generated by [AVA](https://ava.li).
66

7-
## it accepts a browser override for usage with other browsers
8-
9-
> Snapshot 1
10-
11-
`body::before {␊
12-
content: 'Extract CSS Core';␊
13-
}`
14-
157
## it combines server generated <link> and <style> tags with client side created <link> and <style> tags
168

179
> Snapshot 1
1810
19-
`body {␊
11+
`.server-style::after { content: "server-style"; }.js-style::after { content: "js-style"; }body {␊
2012
color: teal;␊
2113
}␊
2214
body {␊
2315
color: teal;␊
2416
}␊
25-
.server-style {␊
26-
counter-increment: 2;␊
27-
}.js-style { counter-increment: 3; }`
17+
`

β€Žtest/snapshots/index.js.snap

-66 Bytes
Binary file not shown.

0 commit comments

Comments
Β (0)