- Notifications
You must be signed in to change notification settings - Fork33
Extract the minimal CSS used in a set of URLs with puppeteer
License
peterbe/minimalcss
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
A Node library to extract the minimal CSS used in a set of URLs with puppeteer.Used to find what minimal CSS is needed to render on first load, even withdocument.onload executed.
This minimal CSS is also known ascritical path CSSand ultimately a web performance technique to make web pages load fasterat initial load.
You supply a list of URLs that it opens (one at a time) and for each pageit downloads all external CSS files (e.g.<link rel="stylesheet" href="bootstrap.min.css">) and uses the DOM anddocument.querySelector to investigate which selectors, in the CSS, areactually in the DOM. That minimal payload of CSS is all you need to loadthe URLs styled without having to make it block on CSS.
Under the hood it relies on the excellentpuppeteer library which usesthe Headless Chome Node API. This means it runs (at the time of writing)Chrome 62 and this library is maintained by the Google Chrome team.
The CSS to analyze (and hopefully minimize) is downloaded automatically justlike a browser opens and downloads CSS as mentioned in the DOM as<link>tags.
The CSS is parsed byCSSTree and theminification and compression is done withCSSO.An AST of each CSS payload is sent into the Headless Chrome page evaluationtogether with a callback that compares with the DOM and then each minimal CSSpayload is concatenated into one big string which then CSSO compresses intoone "merged" and minified CSS payload.
Install:
yarn add minimalcss --dev
You can install it globally if you like:
yarn global add minimalcss
npm install [--save-dev|--global] minimalcssNow you can run it:
./node_modules/.bin/minimalcss https://example.com/ https://example.com/aboutus> minimal.min.cssminimalcss isn't the first library to perform this task. What's unique andspecial aboutminimalcss is that it uses the Chrome Headless browser.
penthouse -usespuppeteer (since version 1.0) andCSSTree.Supports only 1 URL at a time and can't you have to first save the CSS filesit should process.
critical - uses
penthouse(see above) with its "flaws" meaning you can only do 1 URL (or HTML string)and you have to prepare the CSS files too.UnCSS - usesjsdomto render and execute JavaScript. Supports supplying multiple URLs but stillrequires to manually supply the CSS files to process.
mincss - Python project that useslxml.html to analyze the HTML statically(by doing a
GETof the URL as if done by a server). I.e.it can't load the HTML as a real browser would and thus does not support aDOM with possible JavaScript mutations on load.It can optionally usePhantomJSto extract the HTML.
You don't need to specify where the CSS is. It gets downloaded and parsedautomatically.
It usespuppeteer andCSSTree which are both high qualityprojects that are solid and well tested.
The CSS selectors downloaded is compared to the DOM beforeand afterJavaScript code has changed the DOM. That means you can extract thecritical CSS needed to display properly before the JavaScript has kicked in.
Ability to analyze the remaining CSS selectors to see which keyframeanimations that they use and use this to delete keyframe definitionsthat are no longer needed.
You can specify aviewport,which might cause the page to render slightly different. It does notcreate the minimal CSSonly on DOM that is visible though.
If the CSS contains
@font-face { ... }rules whose name is neverused in any remaining CSS selector, the wholefont-faceblock is removed.
Let's make this a thriving community project!
Help needed with features, tooling, and much testing in real web performanceoptimization work.
constminimalcss=require('minimalcss');
Just prints out the current version.
Returns a promise. The promise returns an object containing, amongstother things, the minified minimal CSS as a string.For example:
minimalcss.minimize({url:'https://example.com/'}).then(result=>{console.log('OUTPUT',result.finalCss.length,result.finalCss);}).catch(error=>{console.error(`Failed the minimize CSS:${error}`);});
Thatresult object that is returned by theminimize function contains:
finalCss- the minified minimal CSS as a string.stylesheetContents- an object of stylesheet URLs as keys and theircontent as text.
Optionally, you can supply a list of URLs like this:
minimalcss.minimize({urls:['https://example.com/page1','https://example.com/page2']})...
andminimalcss will try to merge the minimal critical CSS across all pages.But we aware that this can be "dangerous" because of the inherit order of CSS.
Callingminimalcss.run(options) takes an object whose only mandatorykey isurls orurl. Other optional options are:
debug- all console logging during page rendering are included in thestdout. Also, any malformed selector that cause errors indocument.querySelectorwill be raised as new errors.skippable- function which takesrequestas an argument and returns boolean. If it returns true then given requestwill be aborted (skipped). Can be used to block requests to Google Analyticsetc.loadimages- If set totrue, images will actually load.withoutjavascript- If set tofalseit willskip loading the page firstwithout JavaScript. By defaultminimalcsswill evaluate the DOM as plain ascan be, and then with JavaScript enabledand waiting for network activityto be idle.disableJavaScript- By default JavaScript is enabled. If set totrueit will ignorewithoutjavascriptoption and loading the page only one time without JavaScript.browser- Instance of aBrowser, which will be used instead of launching another one.userAgent- specific user agent to use (string)viewport- viewport object as specified inpage.setViewportpuppeteerArgs- Args sent to puppeteer when launching.Listof strings for headless Chrome.cssoOptions- CSSO compress functionoptionstimeout- Maximum navigation time in milliseconds, defaults to 30 seconds, pass 0 to disable timeout.ignoreCSSErrors- By default, any CSS parsing error throws an error inminimalcss. If you know it's safe to ignore (for example, third-party CSS resources), set this totrue.ignoreJSErrors- By default, any JavaScript error encountered by puppeteerignoreRequestErrors- When CSS files return 404 or another request errorminimalcsswill ignore this instead of throwing an error.will be thrown byminimalcss. If you know it's safe to ignore errors (for example, onthird-party webpages), set this totrue.styletags- If set totrue, on-page<style>tags are parsed along with external stylesheets. By default, only external stylesheets are parsed.enableServiceWorkers- By default all Service Workers are disabled. This option enables them as is.whitelist- Array of css selectors that should be left in final CSS. RegExp patterns are supported (e.g.['sidebar', icon-.*, .*-error]).
Suppose you have this in your HTML:
<linkhref="https://fonts.googleapis.com/css?family=Lato"rel="stylesheet">
then,minimalcss will consider this an external CSS stylesheet, load itand include it in the minimal CSS.
The problem is that Google Fonts will respond to that URL dynamically basedon the user agent. In other words a different CSS payload depending on who'sasking. So, the user agent whenminimalcss runs will be whateverpuppeteer uses and it might not be the best CSS for other user agents.So to avoid this predicament use theskippable option. On the command lineyou can do that like this:
./node_modules/.bin/minimalcss --skip fonts.googleapis.com https://example.com
With the API, you can do it like this:
minimalcss.minimize({url:'https://example.com',skippable:request=>{return!!request.url().match('fonts.googleapis.com');}}).then(result=>{ ...});
minimalcss can accept multiple URLs when figuring out the minimal CSS forall those URLs, combined. Butbe careful, this can be dangerous. Ifyou have one URL with this HTML:
<head><linkrel="stylesheet"href="base.css"><linkrel="stylesheet"href="specific.css"></head>
and another URL with...:
<head><linkrel="stylesheet"href="base.css"></head>
When combining these, it will optimize the CSS in this order:
base.cssspecific.cssbase.css
But ifspecific.css was meant to override something inbase.css in thefirst URL, that might get undone whenbase.css becomes the last CSSto include.
See this issue for another good example why runningminimalcss across multiple URLs.
Whenminimalcss evaluates each CSS selector to decide whether to keep itor not, some selectors might not be parseable. Possibly, the CSS employshacks for specific browsers thatcheerio doesn't support. Orthere might be CSS selectors that no browser or tool can understand(e.g a typo by the CSS author). If there's a problem parsing a CSS selector,the default is to swallow the exception and let the CSS selector stay.
Also by default, all these warnings are hidden. To see them use the--debugflag (ordebug API option). Then the CSS selector syntax errors areprinted onstderr.
minimalcss will remove any@font-face rules whose name is not mentionedin any of the CSS selectors. But be aware that you might have a@font-face { font-family: MyName; } in some/static/foo.css but separatelyyou might have an inline style sheet that looks like this:
<styletype="text/css">div.something {font-family: MyName; }</style>
In this case the@font-face { font-family: MyName; } would be removed eventhough it's mentioned from somewhere else.
If your document usesBlob to create injectable stylesheets into the DOM,minimalcss willnot be able to optimize that. It will be not beincluded in the final CSS.
First thing to get started, once you've cloned the repo is to install allthe dependencies:
yarn
Testing is done withjest. At thebeginning of every test, a static file server is started onlocalhostand apuppeteer browser instance is created for every test.
To run the tests:
yarn jest
Best way to get into writing tests is to look at existing tests and copy.
All code is expected to conform withPrettier accordingto the the.prettierrc file in the root of the project.
To check that all your code conforms, run:
yarn lintcheck
This blog postdemonstrates technique to useminimalcss when you don't yet have a server.Using thehttp-server package you can start a server right before you runand shut down as soon as you're done.
Copyright (c) 2017-2020Peter Bengtsson.See theLICENSE file for license rights and limitations (MIT).
About
Extract the minimal CSS used in a set of URLs with puppeteer
Resources
License
Contributing
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors15
Uh oh!
There was an error while loading.Please reload this page.