extract-css-core
Advanced tools
Comparing version
{ | ||
"name": "extract-css-core", | ||
"description": "Extract all CSS from a given url, both server side and client side rendered.", | ||
"version": "2.0.0", | ||
"version": "2.1.0", | ||
"homepage": "https://www.projectwallace.com/oss", | ||
@@ -30,3 +30,4 @@ "repository": "https://github.com/bartveneman/extract-css-core", | ||
"create-test-server": "^3.0.1", | ||
"eslint": "^6.7.2" | ||
"eslint": "^6.7.2", | ||
"sirv": "^0.4.2" | ||
}, | ||
@@ -33,0 +34,0 @@ "dependencies": { |
@@ -15,2 +15,18 @@ <div align="center"> | ||
## Usage | ||
```js | ||
const extractCss = require('extract-css-core') | ||
const css = await extractCss('http://www.projectwallace.com') | ||
``` | ||
## Installation | ||
```sh | ||
npm install extract-css-core | ||
# or | ||
yarn add extract-css-core | ||
``` | ||
## Problem, solution and shortcomings | ||
@@ -23,4 +39,4 @@ | ||
look at a server-generated piece of HTML and get all the `<link>` and `<style>` | ||
tags from it. This works fine for 100% server rendered pages, and pages with | ||
CSS-in-JS styling. | ||
tags from it. This works fine for 100% server rendered pages 👍, but not for pages with | ||
CSS-in-JS styling and inline styles 👎. | ||
@@ -33,20 +49,4 @@ ### Solution | ||
is the power behind finding most of the CSS. Additionally, the | ||
`document.styleSheets` API is used to get CSS-inJS styling. | ||
`document.styleSheets` API is used to get CSS-in-JS styling. Lastly, a plain old `document.querySelectorAll('[style]')` finds all inline styling. | ||
## Installation | ||
```sh | ||
npm install extract-css-core | ||
# or | ||
yarn add extract-css-core | ||
``` | ||
## Usage | ||
```js | ||
const extractCss = require('extract-css-core') | ||
const css = await extractCss('http://www.projectwallace.com') | ||
``` | ||
## API | ||
@@ -63,3 +63,3 @@ | ||
Default: `null` | ||
Default: `{}` | ||
@@ -70,3 +70,3 @@ #### waitUntil | ||
Default: `networkidle2` | ||
Default: `networkidle0` | ||
@@ -80,2 +80,2 @@ Can be any value as provided by the | ||
- [get-css](https://github.com/cssstats/cssstats/tree/master/packages/get-css) - | ||
The original get-css | ||
The original get-css from CSSStats |
@@ -0,1 +1,2 @@ | ||
/* global document */ | ||
const puppeteer = require('puppeteer') | ||
@@ -10,3 +11,8 @@ | ||
module.exports = async (url, {waitUntil = 'networkidle2'} = {}) => { | ||
/** | ||
* @param {string} url URL to get CSS from | ||
* @param {string} waitUntil https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#pagegotourl-options | ||
* @returns {string} All CSS that was found | ||
*/ | ||
module.exports = async (url, {waitUntil = 'networkidle0'} = {}) => { | ||
// Setup a browser instance | ||
@@ -17,4 +23,2 @@ const browser = await puppeteer.launch() | ||
const page = await browser.newPage() | ||
// Start CSS coverage. This is the meat and bones of this module | ||
await page.coverage.startCSSCoverage() | ||
@@ -37,24 +41,51 @@ const response = await page.goto(url, {waitUntil}) | ||
// Coverage contains a lot of <style> and <link> CSS, | ||
// but not all... | ||
// If the response is a CSS file, return that file | ||
// instead of running our complicated setup | ||
const headers = response.headers() | ||
if (headers['content-type'].includes('text/css')) { | ||
return Promise.resolve(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(() => { | ||
/* global document */ | ||
return [...document.styleSheets] | ||
// Only take the stylesheets without href (BUT WHY) | ||
.filter(stylesheet => stylesheet.href === null) | ||
.map(stylesheet => | ||
[...stylesheet.cssRules] | ||
.map(cssStyleRule => cssStyleRule.cssText) | ||
.join('') | ||
) | ||
.join('') | ||
.map(stylesheet => { | ||
return { | ||
type: stylesheet.ownerNode.tagName.toLowerCase(), | ||
href: stylesheet.href || document.location.href, | ||
css: [...stylesheet.cssRules].map(({cssText}) => cssText).join('\n') | ||
} | ||
}) | ||
}) | ||
await browser.close() | ||
// Get all inline styles: <element style=""> | ||
// This creates a new CSSRule for every inline style | ||
// attribute it encounters. | ||
// | ||
// Example: | ||
// | ||
// HTML: | ||
// <h1 style="color: red;">Text</h1> | ||
// | ||
// CSSRule: | ||
// [x-extract-css-inline-style] { color: red; } | ||
// | ||
const inlineCssRules = await page.evaluate(() => { | ||
return [...document.querySelectorAll('[style]')] | ||
.map(element => element.getAttribute('style')) | ||
// Filter out empty style="" attributes | ||
.filter(Boolean) | ||
}) | ||
const inlineCss = inlineCssRules | ||
.map(rule => `[x-extract-css-inline-style] { ${rule} }`) | ||
.map(css => ({type: 'inline', href: url, css})) | ||
// Turn the coverage Array into a single string of CSS | ||
const coverageCss = coverage | ||
const links = coverage | ||
// Filter out the <style> tags that were found in the coverage | ||
@@ -64,8 +95,18 @@ // report since we've conducted our own search for them. | ||
// we requested is an indication that this was a <style> tag | ||
.filter(styles => styles.url !== url) | ||
// The `text` property contains the actual CSS | ||
.map(({text}) => text) | ||
.join('') | ||
.filter(entry => entry.url !== url) | ||
.map(entry => ({ | ||
href: entry.url, | ||
css: entry.text, | ||
type: 'link-or-import' | ||
})) | ||
return Promise.resolve(styleSheetsApiCss + coverageCss) | ||
await browser.close() | ||
const css = links | ||
.concat(styleSheetsApiCss) | ||
.concat(inlineCss) | ||
.map(({css}) => css) | ||
.join('\n') | ||
return Promise.resolve(css) | ||
} |
8007
19.56%93
69.09%5
25%