Switched to jsdom (#240)

This commit is contained in:
Ben Zörb 2018-12-30 23:41:29 +01:00 committed by GitHub
parent da6b0fc1b8
commit e37904080a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 3272 additions and 3013 deletions

8
cli.js
View File

@ -3,6 +3,7 @@
const os = require('os');
const fs = require('fs');
const meow = require('meow');
const chalk = require('chalk');
const indentString = require('indent-string');
const stdin = require('get-stdin');
const css = require('css');
@ -42,6 +43,7 @@ const cli = meow(help, {
minify: {
type: 'boolean',
alias: 'm',
default: true,
},
extract: {
type: 'boolean',
@ -101,10 +103,10 @@ cli.flags = _.reduce(
);
function processError(err) {
process.stderr.write(indentString('Error: ' + (err.message || err), 4));
process.stderr.write(chalk.red(indentString('Error: ' + (err.message || err), 2)));
process.stderr.write(os.EOL);
process.stderr.write(indentString(help, 4));
// Process.exit(1);
process.stderr.write(indentString(help, 2));
process.exit(1);
}
function read(file) {

269
index.js
View File

@ -13,42 +13,20 @@ const fs = require('fs');
const path = require('path');
const isString = require('lodash/isString');
const isRegExp = require('lodash/isRegExp');
const filter = require('lodash/filter');
const _ = require('lodash');
const UglifyJS = require('uglify-js');
const reaver = require('reaver');
const postcss = require('postcss');
const discard = require('postcss-discard');
const cheerio = require('cheerio');
const render = require('dom-serializer');
const CleanCSS = require('clean-css');
const slash = require('slash');
const normalizeNewline = require('normalize-newline');
const resolve = require('resolve');
const detectIndent = require('detect-indent');
const prettier = require('prettier');
const Dom = require('./src/dom');
const {prettifyCss, extractCss} = require('./src/css');
const DEFAULT_OPTIONS = {
minify: true,
extract: false,
polyfill: true,
ignore: [],
stylesheets: [],
replaceStylesheets: [],
};
/**
* Get loadcss + cssrelpreload script
*
* @returns {string} Minified loadcss script
*/
function getScript() {
const loadCssMain = resolve.sync('fg-loadcss');
const loadCssBase = path.dirname(loadCssMain);
const loadCSS = read(path.join(loadCssBase, 'cssrelpreload.js'));
return UglifyJS.minify(loadCSS).code;
}
/**
* Fixup slashes in file paths for windows
*
@ -59,74 +37,6 @@ function normalizePath(str) {
return process.platform === 'win32' ? slash(str) : str;
}
/**
* Read file *
* @param {string} file Filepath
* @returns {string} Content
*/
function read(file) {
return fs.readFileSync(file, 'utf8');
}
/**
* Get the indentation of the link tags
* @param {string} html Html source
* @param {Cheerio} $el Cheerio object
* @returns {string} Indetation
*/
function getIndent(html, $el) {
const regName = new RegExp(_.escapeRegExp(_.get($el, 'name')));
const regHref = new RegExp(_.escapeRegExp(_.get($el, 'attribs.href')));
const regRel = new RegExp(_.escapeRegExp(_.get($el, 'attribs.rel')));
const lines = _.filter(html.split(/[\r\n]+/), line => {
return regName.test(line) && regHref.test(line) && regRel.test(line);
});
return detectIndent(lines.join('\n')).indent;
}
/**
* Minify CSS
* @param {string} styles CSS
* @returns {string} Minified css string
*/
function minifyCSS(styles) {
return new CleanCSS().minify(styles).styles; // eslint-disable-line prefer-destructuring
}
function prettifyCSS(styles) {
return prettier.format(styles, {parser: 'css'});
}
function extract(css, critical, minify = true) {
const minCss = minifyCSS(css);
const minCritical = minifyCSS(critical);
const diff = normalizeNewline(postcss(discard({css: minCritical})).process(minCss).css);
if (minify) {
return diff;
}
return prettifyCSS(diff);
}
/**
* Helper to prevent cheerio from messing with svg contrnt.
* Should be merged afe´ter https://github.com/fb55/htmlparser2/pull/259
* @param {string} str HTML String
* @returns {array} SVG Strings found in HTML
*/
const getSvgs = (str = '') => {
const indices = [];
let start = str.indexOf('<svg', 0);
let end = str.indexOf('</svg>', start) + 6;
while (start >= 0) {
indices.push({start, end});
start = str.indexOf('<svg', end);
end = str.indexOf('</svg>', end) + 6;
}
return indices.map(({start, end}) => str.substring(start, end));
};
/**
* Main function ;)
* @param {string} html HTML String
@ -141,122 +51,115 @@ function inline(html, styles, options) {
html = String(html);
}
const $ = cheerio.load(html, {
decodeEntities: false,
});
// Process style tags
const inlineStyles = $('head style')
.map((i, el) => $(el).html())
.get()
.join('\n');
// Only inline the missing styles
const missing = extract(styles, inlineStyles, o.minify);
const inlined = `${inlineStyles}\n${missing}`;
const allLinks = $('link[rel="stylesheet"], link[rel="preload"][as="style"]').filter(function() {
return !$(this).parents('noscript').length;
});
let links = allLinks.filter('[rel="stylesheet"]');
const target = o.selector || allLinks.get(0) || $('head script').get(0);
const {indent} = detectIndent(html);
const targetIndent = getIndent(html, target);
const $target = $(target);
if (!Array.isArray(o.ignore)) {
o.ignore = [o.ignore].filter(i => i);
}
if (o.ignore.length > 0) {
links = filter(links, link => {
const href = $(link).attr('href');
return !o.ignore.some(i => (isRegExp(i) && i.test(href)) || i === href);
});
}
const document = new Dom(html, o);
if (missing) {
const elements = [
'<style>',
indent +
missing
.replace(/(\r\n|\r|\n)/g, '$1' + targetIndent + indent)
.replace(/^[\s\t]+$/g, '')
.trim(),
'</style>',
'',
]
.join('\n' + targetIndent)
.replace(/(\r\n|\r|\n)[\s\t]+(\r\n|\r|\n)/g, '$1$2');
const inlineStyles = document.getInlineStyles();
const extarnalStyles = document.getExternalStyles();
const missingStyles = extractCss(styles, ...inlineStyles);
if ($target.length > 0) {
// Insert inline styles right before first <link rel="stylesheet" /> or other target
$target.before(elements);
const links = extarnalStyles.filter(link => {
// Only take stylesheets
const stylesheet = link.getAttribute('rel') === 'stylesheet';
// Filter ignored links
const href = link.getAttribute('href');
return stylesheet && !o.ignore.some(i => (isRegExp(i) && i.test(href)) || i === href);
});
const targetSelectors = [
o.selector,
':not(noscript) > link[rel="stylesheet"]',
':not(noscript) > link[rel="preload"][as="style"]',
'head script',
];
const target = document.querySelector(targetSelectors);
const inlined = `${inlineStyles}\n${missingStyles}`;
if (missingStyles) {
if (o.minify) {
document.addInlineStyles(missingStyles, target);
} else {
// Just append to the head
$('head').append(elements);
document.addInlineStyles(prettifyCss(missingStyles, document.indent), target);
}
}
if (links.length > 0) {
// Modify links and ad clones to noscript block
$(links).each(function(idx, el) {
if (o.extract && !o.basePath) {
throw new Error('Option `basePath` is missing and required when using `extract`!');
if (o.replaceStylesheets.length > 0 && links.length > 0) {
// Detect links to be removed
const [ref] = links;
const removable = [...document.querySelectorAll('link[rel="stylesheet"], link[rel="preload"][as="style"]')].filter(
link => {
// Filter ignored links
const href = link.getAttribute('href');
return !o.ignore.some(i => (isRegExp(i) && i.test(href)) || i === href);
}
);
const $el = $(el);
const elIndent = getIndent(html, el);
// Add link tags before old links
// eslint-disable-next-line array-callback-return
o.replaceStylesheets.map(href => {
const link = document.createElement('link');
link.setAttribute('rel', 'stylesheet');
link.setAttribute('href', href);
const noscript = document.createElement('noscript');
noscript.append(link.cloneNode());
link.setAttribute('rel', 'preload');
link.setAttribute('as', 'style');
link.setAttribute('onload', "this.onload=null;this.rel='stylesheet'");
document.insertBefore(link, ref);
document.insertBefore(noscript, ref);
});
// Remove old links
// eslint-disable-next-line array-callback-return
removable.map(link => {
if (link.parentElement.tagName === 'NOSCRIPT') {
document.remove(link.parentElement);
} else {
document.remove(link);
}
});
} else {
// Modify links and add clones to noscript block
// eslint-disable-next-line array-callback-return
links.map(link => {
if (o.extract) {
const href = $el.attr('href');
const file = path.resolve(path.join(o.basePath, href));
const href = link.getAttribute('href');
const file = path.resolve(path.join(o.basePath || process.cwd, href));
if (fs.existsSync(file)) {
const orig = fs.readFileSync(file);
const diff = extract(orig, inlined, o.minify);
const diff = extractCss(orig, inlined, o.minify);
const filename = reaver.rev(file, diff);
fs.writeFileSync(filename, diff);
$el.attr('href', normalizePath(reaver.rev(href, diff)));
link.setAttribute('href', normalizePath(reaver.rev(href, diff)));
} else if (!/\/\//.test(href)) {
throw new Error(`Error: file "${href}" not found in "${o.basePath || process.cwd}". Specify base path.`);
}
}
// Add each fallback right behind the current style to keep source order when ignoring stylesheets
$el.after('\n' + elIndent + '<noscript>' + render(this) + '</noscript>');
const noscript = document.createElement('noscript');
noscript.append(link.cloneNode());
document.insertAfter(noscript, link);
// Add preload atttibutes to actual link element
$el.attr('rel', 'preload');
$el.attr('as', 'style');
$el.attr('onload', "this.onload=null;this.rel='stylesheet'");
link.setAttribute('rel', 'preload');
link.setAttribute('as', 'style');
link.setAttribute('onload', "this.onload=null;this.rel='stylesheet'");
});
// Only add loadcss if it's not already included
const loadCssIncluded = $('script')
.get()
.some(tag => ($(tag).html() || '').includes('loadCSS'));
if (!loadCssIncluded && o.polyfill) {
// Add loadcss + cssrelpreload polyfill
const scriptAnchor = $('link[rel="stylesheet"], noscript')
.filter(function() {
return !$(this).parents('noscript').length;
})
.last()
.get(0);
$(scriptAnchor).after('\n' + targetIndent + '<script>' + getScript() + '</script>');
}
}
const output = $.html();
// Add loadcss if it's not already loaded
if (o.polyfill) {
document.maybeAddLoadcss();
}
// Quickfix until https://github.com/fb55/htmlparser2/pull/259 is merged/fixed
const svgs = getSvgs(html);
const quickfixed = getSvgs(output).reduce((str, code, index) => str.replace(code, svgs[index] || code), output);
return Buffer.from(quickfixed);
return Buffer.from(document.serialize());
}
module.exports = inline;

3572
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,7 @@
"url": "https://github.com/bezoerb/inline-critical/issues"
},
"dependencies": {
"cheerio": "0.22.0",
"chalk": "^2.4.1",
"clean-css": "^4.2.1",
"css": "^2.2.4",
"detect-indent": "5.0.0",
@ -37,6 +37,7 @@
"fs-extra": "^7.0.1",
"get-stdin": "^6.0.0",
"indent-string": "^3.2.0",
"jsdom": "^13.1.0",
"lodash": "^4.17.11",
"meow": "^5.0.0",
"normalize-newline": "3.0.0",

50
src/css.js Normal file
View File

@ -0,0 +1,50 @@
const prettier = require('prettier');
const CleanCSS = require('clean-css');
const postcss = require('postcss');
const discard = require('postcss-discard');
const normalizeNewline = require('normalize-newline');
/**
* Minify CSS
* @param {string} styles CSS
* @returns {string} Minified css string
*/
function minifyCss(styles) {
return new CleanCSS().minify(styles).styles; // eslint-disable-line prefer-destructuring
}
/**
* Prettify CSS
* @param {string} styles CSS
* @param {object} indent Result object returned by detect-indent
* @returns {string} Prettified css string
*/
function prettifyCss(styles, indent = {}) {
return prettier.format(styles, {
parser: 'css',
useTabs: indent.type === 'tab',
tabWidth: indent.amount || 2,
});
}
/**
* Remove styles
* @param {string} styles CSS
* @param {array<string>} css CSS
* @returns {string} css string not containing any of the styles defined in css array
*/
function extractCss(styles, ...css) {
const _styles = normalizeNewline(minifyCss(styles || ''));
const _css = normalizeNewline(minifyCss(css.join('\n')));
if (_css.trim() !== '') {
return postcss(discard({css: _css})).process(_styles).css;
}
return _styles;
}
module.exports = {
minifyCss,
prettifyCss,
extractCss,
};

206
src/dom.js Normal file
View File

@ -0,0 +1,206 @@
const fs = require('fs');
const path = require('path');
const {JSDOM} = require('jsdom');
const resolve = require('resolve');
const detectIndent = require('detect-indent');
const flatten = require('lodash/flatten');
const UglifyJS = require('uglify-js');
/**
* Get loadcss + cssrelpreload script
*
* @returns {string} Minified loadcss script
*/
function getScript() {
const loadCssMain = resolve.sync('fg-loadcss');
const loadCssBase = path.dirname(loadCssMain);
const loadCSS = fs.readFileSync(path.join(loadCssBase, 'cssrelpreload.js'), 'utf8');
return UglifyJS.minify(loadCSS).code.trim();
}
/**
* Get all subsctings of of the passed tags
* Does not work with self closing tags
* @param {string} html Html string
* @param {string} tag Tagname
* @returns {array<string>} Array with aöö substrings
*/
const getPartials = (html = '', tag = 'svg') => {
const indices = [];
let start = html.indexOf(`<${tag}`, 0);
let end = html.indexOf(`</${tag}>`, start) + `</${tag}>`.length;
while (start >= 0) {
indices.push({start, end});
start = html.indexOf(`<${tag}`, end);
end = html.indexOf(`</${tag}>`, end) + `</${tag}>`.length;
}
return indices.map(({start, end}) => html.substring(start, end));
};
/**
* Replace all partials defined by tagname in source with the corresponding
* partials found in dest
* @param {string} source Source HTML String
* @param {string} dest Dest HTML String
* @param {string} tag Tagname (svg or math)
* @returns {array} SVG Strings found in HTML
*/
const replacePartials = (source, dest, tag) => {
if (!Array.isArray(tag)) {
tag = [tag];
}
return tag.reduce((result, tag) => {
// Only replace head so we don't mess with the orignal markup
const newTags = getPartials(dest, tag);
const oldTags = getPartials(result, tag);
return oldTags.reduce((str, code, index) => str.replace(code, newTags[index] || code), result);
}, source);
};
class Dom {
constructor(html, {minify = true} = {}) {
const jsdom = new JSDOM(html);
const {window} = jsdom;
const {document} = window;
document.$jsdom = jsdom;
this.minify = minify;
this.html = html;
this.document = document;
this.window = window;
this.jsdom = jsdom;
this.indent = detectIndent(html);
this.headIndent = detectIndent(this.document.querySelector('head').innerHTML);
}
serialize() {
const html = this.jsdom.serialize();
// Only replace head so we don't mess with the orignal markup
// See https://github.com/fb55/htmlparser2/pull/259 (htmlparser2)
// See https://runkit.com/582b0e9ebe07a80014bf1e82/58400d2db3ef0f0013bae090 (parse5)
// The current parsers have problems with foreign context elements like svg & math
return replacePartials(this.html, html, 'head');
}
createStyleNode(css, referenceIndent = this.headIndent.indent) {
if (this.minify) {
const styles = this.document.createElement('style');
styles.append(this.document.createTextNode(css));
return styles;
}
const textIndent = String(referenceIndent + this.indent.indent);
const text = css
.trim()
.split(/[\r\n]+/)
.join(`\n${textIndent}`);
const styles = this.document.createElement('style');
styles.append(this.document.createTextNode('\n' + textIndent + text + '\n' + referenceIndent));
return styles;
}
createElement(tag) {
return this.document.createElement(tag);
}
getInlineStyles() {
return [...this.document.querySelectorAll('head style')].map(node => node.textContent);
}
getExternalStyles() {
return [...this.document.querySelectorAll('link[rel="stylesheet"], link[rel="preload"][as="style"]')].filter(
link => link.parentElement.tagName !== 'NOSCRIPT'
);
}
querySelector(...selector) {
const s = flatten(selector)
.filter(s => s)
.join(',');
return this.document.querySelector(s);
}
querySelectorAll(...selector) {
const s = flatten(selector)
.filter(s => s)
.join(',');
return this.document.querySelectorAll(s);
}
addInlineStyles(css, target) {
if (target) {
this.insertStylesBefore(css, target);
} else {
this.appendStyles(css, this.querySelector('head'));
}
}
insertStylesBefore(css, referenceNode) {
const styles = this.createStyleNode(css);
referenceNode.before(styles);
styles.after(this.document.createTextNode('\n' + this.headIndent.indent));
}
appendStyles(css, referenceNode) {
const styles = this.createStyleNode(css);
referenceNode.append(styles);
styles.before(this.document.createTextNode(this.headIndent.indent));
styles.after(this.document.createTextNode('\n' + this.headIndent.indent));
}
insertBefore(node, referenceNode) {
referenceNode.before(node);
node.after(this.document.createTextNode('\n' + this.headIndent.indent));
}
insertAfter(node, referenceNode) {
referenceNode.after(node);
referenceNode.after(this.document.createTextNode('\n' + this.headIndent.indent));
}
remove(node) {
while (
node.previousSibling &&
node.previousSibling.nodeName === '#text' &&
node.previousSibling.textContent.trim() === ''
) {
node.previousSibling.remove();
}
node.remove();
}
maybeAddLoadcss() {
// Only add loadcss if it's not already included
const loadCssIncluded = [...this.document.querySelectorAll('script')].some(tag =>
(tag.textContent || '').includes('loadCSS')
);
if (loadCssIncluded) {
return;
}
// Add loadcss + cssrelpreload polyfill
const nodes = [
...this.document.querySelectorAll('head link[rel="stylesheet"],head link[rel="preload"],head noscript'),
].filter(link => link.parentElement.tagName !== 'NOSCRIPT');
const scriptAnchor = nodes.pop();
const script = this.document.createElement('script');
script.append(this.document.createTextNode(getScript()));
if (scriptAnchor) {
scriptAnchor.after(script);
scriptAnchor.after(this.document.createTextNode('\n' + this.headIndent.indent));
}
}
}
module.exports = Dom;

View File

@ -277,10 +277,10 @@
float: right !important;
}
</style>
<link rel="preload" href="/css/cartoon.2808011f.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/cartoon.2808011f.css"></noscript>
<link rel="preload" href="/bower_components/bootstrap/dist/css/bootstrap.10a09ad4.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/bower_components/bootstrap/dist/css/bootstrap.10a09ad4.css"></noscript>
<link rel="preload" href="/css/cartoon.08b3295a.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/cartoon.08b3295a.css"></noscript>
<link rel="preload" href="/bower_components/bootstrap/dist/css/bootstrap.c8dce395.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/bower_components/bootstrap/dist/css/bootstrap.c8dce395.css"></noscript>
<script>!function(n){"use strict";n.loadCSS||(n.loadCSS=function(){});var o=loadCSS.relpreload={};if(o.support=function(){var e;try{e=n.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),o.bindMediaToggle=function(t){var e=t.media||"all";function a(){t.addEventListener?t.removeEventListener("load",a):t.attachEvent&&t.detachEvent("onload",a),t.setAttribute("onload",null),t.media=e}t.addEventListener?t.addEventListener("load",a):t.attachEvent&&t.attachEvent("onload",a),setTimeout(function(){t.rel="stylesheet",t.media="only x"}),setTimeout(a,3e3)},o.poly=function(){if(!o.support())for(var t=n.document.getElementsByTagName("link"),e=0;e<t.length;e++){var a=t[e];"preload"!==a.rel||"style"!==a.getAttribute("as")||a.getAttribute("data-loadcss")||(a.setAttribute("data-loadcss",!0),o.bindMediaToggle(a))}},!o.support()){o.poly();var t=n.setInterval(o.poly,500);n.addEventListener?n.addEventListener("load",function(){o.poly(),n.clearInterval(t)}):n.attachEvent&&n.attachEvent("onload",function(){o.poly(),n.clearInterval(t)})}"undefined"!=typeof exports?exports.loadCSS=loadCSS:n.loadCSS=loadCSS}("undefined"!=typeof global?global:this);</script>
<!-- endbuild -->

View File

@ -1,64 +1,63 @@
<!doctype html>
<!DOCTYPE html>
<html class="no-js">
<head>
<meta charset="utf-8">
<title>critical css test</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
<head>
<meta charset="utf-8">
<title>critical css test</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
<!-- build:css styles/main.css -->
<style>
body{padding-top:20px;padding-bottom:20px}.header{padding-left:15px;padding-right:15px}.header{border-bottom:1px solid #e5e5e5}.header h3{margin-top:0;margin-bottom:0;line-height:40px;padding-bottom:19px}.jumbotron{text-align:center;border-bottom:1px solid #e5e5e5}.jumbotron .btn{font-size:21px;padding:14px 24px}@media screen and (min-width:768px){.container{max-width:730px}.header{padding-left:0;padding-right:0}.header{margin-bottom:30px}.jumbotron{border-bottom:0}}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a{background:0 0}h1{margin:.67em 0;font-size:2em}@media print{*{color:#000!important;text-shadow:none!important;background:0 0!important;box-shadow:none!important}a{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}a[href^="#"]:after{content:""}h3,p{orphans:3;widows:3}h3{page-break-after:avoid}}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:transparent}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}a{color:#428bca;text-decoration:none}h1,h3{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1,h3{margin-top:20px;margin-bottom:10px}h1{font-size:36px}h3{font-size:24px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:200;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.text-muted{color:#999}ul{margin-top:0;margin-bottom:10px}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-lg{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a{color:#fff;background-color:#428bca}.jumbotron{padding:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.container .jumbotron{border-radius:6px}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1{font-size:63px}}.container:after,.container:before,.nav:after,.nav:before{display:table;content:" "}.container:after,.nav:after{clear:both}.pull-right{float:right!important}
</style>
<link rel="preload" href="css/cartoon.08b3295a.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="css/cartoon.08b3295a.css"></noscript>
<link rel="preload" href="bower_components/bootstrap/dist/css/bootstrap.c8dce395.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.c8dce395.css"></noscript>
<script>!function(n){"use strict";n.loadCSS||(n.loadCSS=function(){});var o=loadCSS.relpreload={};if(o.support=function(){var e;try{e=n.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),o.bindMediaToggle=function(t){var e=t.media||"all";function a(){t.addEventListener?t.removeEventListener("load",a):t.attachEvent&&t.detachEvent("onload",a),t.setAttribute("onload",null),t.media=e}t.addEventListener?t.addEventListener("load",a):t.attachEvent&&t.attachEvent("onload",a),setTimeout(function(){t.rel="stylesheet",t.media="only x"}),setTimeout(a,3e3)},o.poly=function(){if(!o.support())for(var t=n.document.getElementsByTagName("link"),e=0;e<t.length;e++){var a=t[e];"preload"!==a.rel||"style"!==a.getAttribute("as")||a.getAttribute("data-loadcss")||(a.setAttribute("data-loadcss",!0),o.bindMediaToggle(a))}},!o.support()){o.poly();var t=n.setInterval(o.poly,500);n.addEventListener?n.addEventListener("load",function(){o.poly(),n.clearInterval(t)}):n.attachEvent&&n.attachEvent("onload",function(){o.poly(),n.clearInterval(t)})}"undefined"!=typeof exports?exports.loadCSS=loadCSS:n.loadCSS=loadCSS}("undefined"!=typeof global?global:this);</script>
<!-- endbuild -->
<!-- build:css styles/main.css -->
<style>body{padding-top:20px;padding-bottom:20px}.header{padding-left:15px;padding-right:15px}.header{border-bottom:1px solid #e5e5e5}.header h3{margin-top:0;margin-bottom:0;line-height:40px;padding-bottom:19px}.jumbotron{text-align:center;border-bottom:1px solid #e5e5e5}.jumbotron .btn{font-size:21px;padding:14px 24px}@media screen and (min-width:768px){.container{max-width:730px}.header{padding-left:0;padding-right:0}.header{margin-bottom:30px}.jumbotron{border-bottom:0}}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a{background:0 0}h1{margin:.67em 0;font-size:2em}@media print{*{color:#000!important;text-shadow:none!important;background:0 0!important;box-shadow:none!important}a{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}a[href^="#"]:after{content:""}h3,p{orphans:3;widows:3}h3{page-break-after:avoid}}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:transparent}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}a{color:#428bca;text-decoration:none}h1,h3{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1,h3{margin-top:20px;margin-bottom:10px}h1{font-size:36px}h3{font-size:24px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:200;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.text-muted{color:#999}ul{margin-top:0;margin-bottom:10px}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-lg{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a{color:#fff;background-color:#428bca}.jumbotron{padding:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.container .jumbotron{border-radius:6px}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1{font-size:63px}}.container:after,.container:before,.nav:after,.nav:before{display:table;content:" "}.container:after,.nav:after{clear:both}.pull-right{float:right!important}</style>
<link rel="preload" href="css/cartoon.08b3295a.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="css/cartoon.08b3295a.css"></noscript>
<link rel="preload" href="bower_components/bootstrap/dist/css/bootstrap.c8dce395.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.c8dce395.css"></noscript>
<script>!function(n){"use strict";n.loadCSS||(n.loadCSS=function(){});var o=loadCSS.relpreload={};if(o.support=function(){var e;try{e=n.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),o.bindMediaToggle=function(t){var e=t.media||"all";function a(){t.addEventListener?t.removeEventListener("load",a):t.attachEvent&&t.detachEvent("onload",a),t.setAttribute("onload",null),t.media=e}t.addEventListener?t.addEventListener("load",a):t.attachEvent&&t.attachEvent("onload",a),setTimeout(function(){t.rel="stylesheet",t.media="only x"}),setTimeout(a,3e3)},o.poly=function(){if(!o.support())for(var t=n.document.getElementsByTagName("link"),e=0;e<t.length;e++){var a=t[e];"preload"!==a.rel||"style"!==a.getAttribute("as")||a.getAttribute("data-loadcss")||(a.setAttribute("data-loadcss",!0),o.bindMediaToggle(a))}},!o.support()){o.poly();var t=n.setInterval(o.poly,500);n.addEventListener?n.addEventListener("load",function(){o.poly(),n.clearInterval(t)}):n.attachEvent&&n.attachEvent("onload",function(){o.poly(),n.clearInterval(t)})}"undefined"!=typeof exports?exports.loadCSS=loadCSS:n.loadCSS=loadCSS}("undefined"!=typeof global?global:this);</script>
<!-- endbuild -->
</head>
<body>
<!--[if lt IE 10]>
<p class="browsehappy">
You are using an <strong>outdated</strong> browser. Please
<a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.
</p>
<![endif]-->
</head>
<body>
<!--[if lt IE 10]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade
your browser</a> to improve your experience.</p>
<![endif]-->
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li class="active"><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Contact</a></li>
</ul>
<h3 class="text-muted">critical css test</h3>
</div>
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li class="active"><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Contact</a></li>
</ul>
<h3 class="text-muted">critical css test</h3>
</div>
<div class="jumbotron">
<h1>'Allo, 'Allo!</h1>
<div class="jumbotron">
<h1>'Allo, 'Allo!</h1>
<p class="lead">Always a pleasure scaffolding your apps.</p>
<p class="lead">Always a pleasure scaffolding your apps.</p>
<p><a class="btn btn-lg btn-success" href="#">Splendid!</a></p>
</div>
<p><a class="btn btn-lg btn-success" href="#">Splendid!</a></p>
</div>
<div class="row marketing">
<div class="col-lg-6">
<h4>HTML5 Boilerplate</h4>
<div class="row marketing">
<div class="col-lg-6">
<h4>HTML5 Boilerplate</h4>
<p>
HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or
sites.
</p>
<p>HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or
sites.</p>
<h4>Bootstrap</h4>
<h4>Bootstrap</h4>
<p>Sleek, intuitive, and powerful mobile first front-end framework for faster and easier web development.</p>
</div>
</div>
<p>Sleek, intuitive, and powerful mobile first front-end framework for faster and easier web development.</p>
<div class="footer"><p>♥ from the Yeoman team</p></div>
</div>
</div>
<div class="footer">
<p>♥ from the Yeoman team</p>
</div>
</div>
</body>
</body>
</html>

View File

@ -1,330 +1,331 @@
<!doctype html>
<!DOCTYPE html>
<html class="no-js">
<head>
<meta charset="utf-8">
<title>critical css test</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
<head>
<meta charset="utf-8">
<title>critical css test</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
<!-- build:css styles/main.css -->
<style>
body {
padding-top: 20px;
padding-bottom: 20px;
}
.header {
padding-left: 15px;
padding-right: 15px;
}
.header {
border-bottom: 1px solid #e5e5e5;
}
.header h3 {
margin-top: 0;
margin-bottom: 0;
line-height: 40px;
padding-bottom: 19px;
}
.jumbotron {
text-align: center;
border-bottom: 1px solid #e5e5e5;
}
.jumbotron .btn {
font-size: 21px;
padding: 14px 24px;
}
@media screen and (min-width: 768px) {
.container {
max-width: 730px;
<!-- build:css styles/main.css -->
<style>
body {
padding-top: 20px;
padding-bottom: 20px;
}
.header {
padding-left: 0;
padding-right: 0;
padding-left: 15px;
padding-right: 15px;
}
.header {
margin-bottom: 30px;
border-bottom: 1px solid #e5e5e5;
}
.header h3 {
margin-top: 0;
margin-bottom: 0;
line-height: 40px;
padding-bottom: 19px;
}
.jumbotron {
border-bottom: 0;
text-align: center;
border-bottom: 1px solid #e5e5e5;
}
}
html {
font-family: sans-serif;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
body {
margin: 0;
}
a {
background: 0 0;
}
h1 {
margin: 0.67em 0;
font-size: 2em;
}
@media print {
* {
color: #000 !important;
text-shadow: none !important;
background: 0 0 !important;
box-shadow: none !important;
.jumbotron .btn {
font-size: 21px;
padding: 14px 24px;
}
@media screen and (min-width: 768px) {
.container {
max-width: 730px;
}
.header {
padding-left: 0;
padding-right: 0;
}
.header {
margin-bottom: 30px;
}
.jumbotron {
border-bottom: 0;
}
}
html {
font-family: sans-serif;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
body {
margin: 0;
}
a {
text-decoration: underline;
background: 0 0;
}
a[href]:after {
content: " (" attr(href) ")";
h1 {
margin: 0.67em 0;
font-size: 2em;
}
a[href^="#"]:after {
content: "";
@media print {
* {
color: #000 !important;
text-shadow: none !important;
background: 0 0 !important;
box-shadow: none !important;
}
a {
text-decoration: underline;
}
a[href]:after {
content: " (" attr(href) ")";
}
a[href^="#"]:after {
content: "";
}
h3,
p {
orphans: 3;
widows: 3;
}
h3 {
page-break-after: avoid;
}
}
h3,
p {
orphans: 3;
widows: 3;
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
:after,
:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
html {
font-size: 62.5%;
-webkit-tap-highlight-color: transparent;
}
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.42857143;
color: #333;
background-color: #fff;
}
a {
color: #428bca;
text-decoration: none;
}
h1,
h3 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
}
h1,
h3 {
margin-top: 20px;
margin-bottom: 10px;
}
h1 {
font-size: 36px;
}
h3 {
page-break-after: avoid;
font-size: 24px;
}
p {
margin: 0 0 10px;
}
}
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
:after,
:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
html {
font-size: 62.5%;
-webkit-tap-highlight-color: transparent;
}
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.42857143;
color: #333;
background-color: #fff;
}
a {
color: #428bca;
text-decoration: none;
}
h1,
h3 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
}
h1,
h3 {
margin-top: 20px;
margin-bottom: 10px;
}
h1 {
font-size: 36px;
}
h3 {
font-size: 24px;
}
p {
margin: 0 0 10px;
}
.lead {
margin-bottom: 20px;
font-size: 16px;
font-weight: 200;
line-height: 1.4;
}
@media (min-width: 768px) {
.lead {
font-size: 21px;
margin-bottom: 20px;
font-size: 16px;
font-weight: 200;
line-height: 1.4;
}
@media (min-width: 768px) {
.lead {
font-size: 21px;
}
}
.text-muted {
color: #999;
}
ul {
margin-top: 0;
margin-bottom: 10px;
}
}
.text-muted {
color: #999;
}
ul {
margin-top: 0;
margin-bottom: 10px;
}
.container {
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
}
@media (min-width: 768px) {
.container {
width: 750px;
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
}
}
@media (min-width: 992px) {
.container {
width: 970px;
@media (min-width: 768px) {
.container {
width: 750px;
}
}
}
@media (min-width: 1200px) {
.container {
width: 1170px;
@media (min-width: 992px) {
.container {
width: 970px;
}
}
@media (min-width: 1200px) {
.container {
width: 1170px;
}
}
.btn {
display: inline-block;
padding: 6px 12px;
margin-bottom: 0;
font-size: 14px;
font-weight: 400;
line-height: 1.42857143;
text-align: center;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-image: none;
border: 1px solid transparent;
border-radius: 4px;
}
.btn-success {
color: #fff;
background-color: #5cb85c;
border-color: #4cae4c;
}
.btn-lg {
padding: 10px 16px;
font-size: 18px;
line-height: 1.33;
border-radius: 6px;
}
.nav {
padding-left: 0;
margin-bottom: 0;
list-style: none;
}
.nav > li {
position: relative;
display: block;
}
.nav > li > a {
position: relative;
display: block;
padding: 10px 15px;
}
.nav-pills > li {
float: left;
}
.nav-pills > li > a {
border-radius: 4px;
}
.nav-pills > li + li {
margin-left: 2px;
}
.nav-pills > li.active > a {
color: #fff;
background-color: #428bca;
}
}
.btn {
display: inline-block;
padding: 6px 12px;
margin-bottom: 0;
font-size: 14px;
font-weight: 400;
line-height: 1.42857143;
text-align: center;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-image: none;
border: 1px solid transparent;
border-radius: 4px;
}
.btn-success {
color: #fff;
background-color: #5cb85c;
border-color: #4cae4c;
}
.btn-lg {
padding: 10px 16px;
font-size: 18px;
line-height: 1.33;
border-radius: 6px;
}
.nav {
padding-left: 0;
margin-bottom: 0;
list-style: none;
}
.nav > li {
position: relative;
display: block;
}
.nav > li > a {
position: relative;
display: block;
padding: 10px 15px;
}
.nav-pills > li {
float: left;
}
.nav-pills > li > a {
border-radius: 4px;
}
.nav-pills > li + li {
margin-left: 2px;
}
.nav-pills > li.active > a {
color: #fff;
background-color: #428bca;
}
.jumbotron {
padding: 30px;
margin-bottom: 30px;
color: inherit;
background-color: #eee;
}
.jumbotron h1 {
color: inherit;
}
.jumbotron p {
margin-bottom: 15px;
font-size: 21px;
font-weight: 200;
}
.container .jumbotron {
border-radius: 6px;
}
@media screen and (min-width: 768px) {
.jumbotron {
padding-top: 48px;
padding-bottom: 48px;
}
.container .jumbotron {
padding-right: 60px;
padding-left: 60px;
padding: 30px;
margin-bottom: 30px;
color: inherit;
background-color: #eee;
}
.jumbotron h1 {
font-size: 63px;
color: inherit;
}
}
.container:after,
.container:before,
.nav:after,
.nav:before {
display: table;
content: " ";
}
.container:after,
.nav:after {
clear: both;
}
.pull-right {
float: right !important;
}
</style>
<link rel="preload" href="css/cartoon.2808011f.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="css/cartoon.2808011f.css"></noscript>
<link rel="preload" href="bower_components/bootstrap/dist/css/bootstrap.10a09ad4.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.10a09ad4.css"></noscript>
<script>!function(n){"use strict";n.loadCSS||(n.loadCSS=function(){});var o=loadCSS.relpreload={};if(o.support=function(){var e;try{e=n.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),o.bindMediaToggle=function(t){var e=t.media||"all";function a(){t.addEventListener?t.removeEventListener("load",a):t.attachEvent&&t.detachEvent("onload",a),t.setAttribute("onload",null),t.media=e}t.addEventListener?t.addEventListener("load",a):t.attachEvent&&t.attachEvent("onload",a),setTimeout(function(){t.rel="stylesheet",t.media="only x"}),setTimeout(a,3e3)},o.poly=function(){if(!o.support())for(var t=n.document.getElementsByTagName("link"),e=0;e<t.length;e++){var a=t[e];"preload"!==a.rel||"style"!==a.getAttribute("as")||a.getAttribute("data-loadcss")||(a.setAttribute("data-loadcss",!0),o.bindMediaToggle(a))}},!o.support()){o.poly();var t=n.setInterval(o.poly,500);n.addEventListener?n.addEventListener("load",function(){o.poly(),n.clearInterval(t)}):n.attachEvent&&n.attachEvent("onload",function(){o.poly(),n.clearInterval(t)})}"undefined"!=typeof exports?exports.loadCSS=loadCSS:n.loadCSS=loadCSS}("undefined"!=typeof global?global:this);</script>
<!-- endbuild -->
.jumbotron p {
margin-bottom: 15px;
font-size: 21px;
font-weight: 200;
}
.container .jumbotron {
border-radius: 6px;
}
@media screen and (min-width: 768px) {
.jumbotron {
padding-top: 48px;
padding-bottom: 48px;
}
.container .jumbotron {
padding-right: 60px;
padding-left: 60px;
}
.jumbotron h1 {
font-size: 63px;
}
}
.container:after,
.container:before,
.nav:after,
.nav:before {
display: table;
content: " ";
}
.container:after,
.nav:after {
clear: both;
}
.pull-right {
float: right !important;
}
</style>
<link rel="preload" href="css/cartoon.08b3295a.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="css/cartoon.08b3295a.css"></noscript>
<link rel="preload" href="bower_components/bootstrap/dist/css/bootstrap.c8dce395.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.c8dce395.css"></noscript>
<script>!function(n){"use strict";n.loadCSS||(n.loadCSS=function(){});var o=loadCSS.relpreload={};if(o.support=function(){var e;try{e=n.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),o.bindMediaToggle=function(t){var e=t.media||"all";function a(){t.addEventListener?t.removeEventListener("load",a):t.attachEvent&&t.detachEvent("onload",a),t.setAttribute("onload",null),t.media=e}t.addEventListener?t.addEventListener("load",a):t.attachEvent&&t.attachEvent("onload",a),setTimeout(function(){t.rel="stylesheet",t.media="only x"}),setTimeout(a,3e3)},o.poly=function(){if(!o.support())for(var t=n.document.getElementsByTagName("link"),e=0;e<t.length;e++){var a=t[e];"preload"!==a.rel||"style"!==a.getAttribute("as")||a.getAttribute("data-loadcss")||(a.setAttribute("data-loadcss",!0),o.bindMediaToggle(a))}},!o.support()){o.poly();var t=n.setInterval(o.poly,500);n.addEventListener?n.addEventListener("load",function(){o.poly(),n.clearInterval(t)}):n.attachEvent&&n.attachEvent("onload",function(){o.poly(),n.clearInterval(t)})}"undefined"!=typeof exports?exports.loadCSS=loadCSS:n.loadCSS=loadCSS}("undefined"!=typeof global?global:this);</script>
<!-- endbuild -->
</head>
<body>
<!--[if lt IE 10]>
<p class="browsehappy">
You are using an <strong>outdated</strong> browser. Please
<a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.
</p>
<![endif]-->
</head>
<body>
<!--[if lt IE 10]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade
your browser</a> to improve your experience.</p>
<![endif]-->
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li class="active"><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Contact</a></li>
</ul>
<h3 class="text-muted">critical css test</h3>
</div>
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li class="active"><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Contact</a></li>
</ul>
<h3 class="text-muted">critical css test</h3>
</div>
<div class="jumbotron">
<h1>'Allo, 'Allo!</h1>
<div class="jumbotron">
<h1>'Allo, 'Allo!</h1>
<p class="lead">Always a pleasure scaffolding your apps.</p>
<p class="lead">Always a pleasure scaffolding your apps.</p>
<p><a class="btn btn-lg btn-success" href="#">Splendid!</a></p>
</div>
<p><a class="btn btn-lg btn-success" href="#">Splendid!</a></p>
</div>
<div class="row marketing">
<div class="col-lg-6">
<h4>HTML5 Boilerplate</h4>
<div class="row marketing">
<div class="col-lg-6">
<h4>HTML5 Boilerplate</h4>
<p>
HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or
sites.
</p>
<p>HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or
sites.</p>
<h4>Bootstrap</h4>
<h4>Bootstrap</h4>
<p>Sleek, intuitive, and powerful mobile first front-end framework for faster and easier web development.</p>
</div>
</div>
<p>Sleek, intuitive, and powerful mobile first front-end framework for faster and easier web development.</p>
<div class="footer"><p>♥ from the Yeoman team</p></div>
</div>
</div>
<div class="footer">
<p>♥ from the Yeoman team</p>
</div>
</div>
</body>
</body>
</html>

View File

@ -279,8 +279,8 @@
</style>
<link rel="preload" href="css/main.d41d8cd9.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="css/main.d41d8cd9.css"></noscript>
<link rel="preload" href="bower_components/bootstrap/dist/css/bootstrap.10a09ad4.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.10a09ad4.css"></noscript>
<link rel="preload" href="bower_components/bootstrap/dist/css/bootstrap.c8dce395.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.c8dce395.css"></noscript>
<link href="http://fonts.googleapis.com/css?family=Open+Sans" rel="preload" type="text/css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link href="http://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet" type="text/css"></noscript>
<script>!function(n){"use strict";n.loadCSS||(n.loadCSS=function(){});var o=loadCSS.relpreload={};if(o.support=function(){var e;try{e=n.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),o.bindMediaToggle=function(t){var e=t.media||"all";function a(){t.addEventListener?t.removeEventListener("load",a):t.attachEvent&&t.detachEvent("onload",a),t.setAttribute("onload",null),t.media=e}t.addEventListener?t.addEventListener("load",a):t.attachEvent&&t.attachEvent("onload",a),setTimeout(function(){t.rel="stylesheet",t.media="only x"}),setTimeout(a,3e3)},o.poly=function(){if(!o.support())for(var t=n.document.getElementsByTagName("link"),e=0;e<t.length;e++){var a=t[e];"preload"!==a.rel||"style"!==a.getAttribute("as")||a.getAttribute("data-loadcss")||(a.setAttribute("data-loadcss",!0),o.bindMediaToggle(a))}},!o.support()){o.poly();var t=n.setInterval(o.poly,500);n.addEventListener?n.addEventListener("load",function(){o.poly(),n.clearInterval(t)}):n.attachEvent&&n.attachEvent("onload",function(){o.poly(),n.clearInterval(t)})}"undefined"!=typeof exports?exports.loadCSS=loadCSS:n.loadCSS=loadCSS}("undefined"!=typeof global?global:this);</script>

View File

@ -16,7 +16,7 @@
<!-- endbuild -->
<noscript>
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />
</noscript>
<script>!function(n){"use strict";n.loadCSS||(n.loadCSS=function(){});var o=loadCSS.relpreload={};if(o.support=function(){var e;try{e=n.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),o.bindMediaToggle=function(t){var e=t.media||"all";function a(){t.addEventListener?t.removeEventListener("load",a):t.attachEvent&&t.detachEvent("onload",a),t.setAttribute("onload",null),t.media=e}t.addEventListener?t.addEventListener("load",a):t.attachEvent&&t.attachEvent("onload",a),setTimeout(function(){t.rel="stylesheet",t.media="only x"}),setTimeout(a,3e3)},o.poly=function(){if(!o.support())for(var t=n.document.getElementsByTagName("link"),e=0;e<t.length;e++){var a=t[e];"preload"!==a.rel||"style"!==a.getAttribute("as")||a.getAttribute("data-loadcss")||(a.setAttribute("data-loadcss",!0),o.bindMediaToggle(a))}},!o.support()){o.poly();var t=n.setInterval(o.poly,500);n.addEventListener?n.addEventListener("load",function(){o.poly(),n.clearInterval(t)}):n.attachEvent&&n.attachEvent("onload",function(){o.poly(),n.clearInterval(t)})}"undefined"!=typeof exports?exports.loadCSS=loadCSS:n.loadCSS=loadCSS}("undefined"!=typeof global?global:this);</script>
</head>

View File

@ -1,58 +1,59 @@
<!doctype html>
<!DOCTYPE html>
<html class="no-js">
<head>
<meta charset="utf-8">
<title>critical css test</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
<head>
<meta charset="utf-8" />
<title>critical css test</title>
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
<!-- build:css styles/main.css -->
<link rel="stylesheet" href="css/cartoon.css">
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css"/>
<!-- endbuild -->
<!-- build:css styles/main.css -->
<link rel="stylesheet" href="css/cartoon.css" />
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />
<!-- endbuild -->
</head>
<body>
<!--[if lt IE 10]>
<p class="browsehappy">
You are using an <strong>outdated</strong> browser. Please
<a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.
</p>
<![endif]-->
</head>
<body>
<!--[if lt IE 10]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade
your browser</a> to improve your experience.</p>
<![endif]-->
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li class="active"><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Contact</a></li>
</ul>
<h3 class="text-muted">critical css test</h3>
</div>
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li class="active"><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Contact</a></li>
</ul>
<h3 class="text-muted">critical css test</h3>
</div>
<div class="jumbotron">
<h1>'Allo, 'Allo!</h1>
<div class="jumbotron">
<h1>'Allo, 'Allo!</h1>
<p class="lead">Always a pleasure scaffolding your apps.</p>
<p class="lead">Always a pleasure scaffolding your apps.</p>
<p><a class="btn btn-lg btn-success" href="#">Splendid!</a></p>
</div>
<p><a class="btn btn-lg btn-success" href="#">Splendid!</a></p>
</div>
<div class="row marketing">
<div class="col-lg-6">
<h4>HTML5 Boilerplate</h4>
<div class="row marketing">
<div class="col-lg-6">
<h4>HTML5 Boilerplate</h4>
<p>
HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or
sites.
</p>
<p>HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or
sites.</p>
<h4>Bootstrap</h4>
<h4>Bootstrap</h4>
<p>Sleek, intuitive, and powerful mobile first front-end framework for faster and easier web development.</p>
</div>
</div>
<p>Sleek, intuitive, and powerful mobile first front-end framework for faster and easier web development.</p>
<div class="footer"><p>♥ from the Yeoman team</p></div>
</div>
</div>
<div class="footer">
<p>♥ from the Yeoman team</p>
</div>
</div>
</body>
</body>
</html>

View File

@ -1,24 +1,55 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>asd</title>
</head>
<body>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewbox="0 0 563 87.9" enable-background="new 0 0 563 87.9" xml:space="preserve" width="167px" height="26px" fill="#fff">
<head lang="en">
<meta charset="UTF-8">
<title>asd</title>
</head>
<body>
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewbox="0 0 563 87.9"
enable-background="new 0 0 563 87.9"
xml:space="preserve"
width="167px"
height="26px"
fill="#fff"
>
<polygon />
<polygon />
<polygon fill="#DD7B27" points="55.92,7 91.96,61.76 20.24,61.76 "/>
<polygon fill="#DD7B27" points="55.92,7 91.96,61.76 20.24,61.76 " />
<polygon />
<g id="layer1" transform="translate(-192.905,-516.02064)">
<path
style="fill:#000000"
d="M 197.67968,534.31563 C 197.40468,534.31208 196.21788,532.53719 195.04234,530.37143 L 192.905,526.43368 L 193.45901,525.87968 C 193.76371,525.57497 194.58269,525.32567 195.27896,525.32567 L 196.5449,525.32567 L 197.18129,527.33076 L 197.81768,529.33584 L 202.88215,523.79451 C 205.66761,520.74678 208.88522,517.75085 210.03239,517.13691 L 212.11815,516.02064 L 207.90871,520.80282 C 205.59351,523.43302 202.45735,527.55085 200.93947,529.95355 C 199.42159,532.35625 197.95468,534.31919 197.67968,534.31563 z "
id="path2223" />
</g>
</svg>
<svg>
<symbol viewbox="0 0 24 24" id="shape-clock"> <title>clock</title> <path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z" /> <path d="M0 0h24v24H0z" fill="none" /> <path d="M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z" /> </symbol>
</svg>
</body>
<path
style="fill:#000000"
d="M 197.67968,534.31563 C 197.40468,534.31208 196.21788,532.53719 195.04234,530.37143 L 192.905,526.43368 L 193.45901,525.87968 C 193.76371,525.57497 194.58269,525.32567 195.27896,525.32567 L 196.5449,525.32567 L 197.18129,527.33076 L 197.81768,529.33584 L 202.88215,523.79451 C 205.66761,520.74678 208.88522,517.75085 210.03239,517.13691 L 212.11815,516.02064 L 207.90871,520.80282 C 205.59351,523.43302 202.45735,527.55085 200.93947,529.95355 C 199.42159,532.35625 197.95468,534.31919 197.67968,534.31563 z "
id="path2223"
/>
</g>
</svg>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<symbol viewbox="0 0 24 24" id="shape-clock">
<title>clock</title>
<path
d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
/>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z" />
</symbol>
</svg>
<math>
(<array align="c"> <item>
&ldet;<array align="cc">
<item>x<sub>11</sub>
<item>x<sub>12</sub>
<item>x<sub>21</sub>
<item>x<sub>22</sub>
</array><rd>&rdet;
<item> y <item> z
</array>)
</math>
</body>
</html>

View File

@ -1,30 +1,11 @@
/* eslint-env jest */
const path = require('path');
const reaver = require('reaver');
const postcss = require('postcss');
const discard = require('postcss-discard');
const normalizeNewline = require('normalize-newline');
const CleanCSS = require('clean-css');
const prettier = require('prettier');
const {extractCss} = require('../src/css.js');
const inline = require('..');
const {read, checkAndDelete, strip} = require('./helper');
function minifyCSS(styles) {
return new CleanCSS().minify(styles).styles; // eslint-disable-line prefer-destructuring
}
const extract = (css, critical, minify = false) => {
css = minifyCSS(css);
critical = minifyCSS(critical);
const result = normalizeNewline(postcss(discard({css: critical})).process(css).css);
if (minify) {
return result;
}
return prettier.format(result, {parser: 'css'});
};
jest.setTimeout(20000);
test('Inline css', async () => {
@ -65,7 +46,7 @@ test('Inline and minify css', async () => {
expect(strip(out.toString('utf-8'))).toBe(strip(expected));
});
test('should inline and extract css', async () => {
test('Inline and extract css', async () => {
const html = await read('fixtures/cartoon.html');
const css = await read('fixtures/critical.css');
const expected = await read('expected/cartoon-expected.html');
@ -76,8 +57,8 @@ test('should inline and extract css', async () => {
]);
const reved = [
reaver.rev('fixtures/css/cartoon.css', extract(styles[0], css)),
reaver.rev('fixtures/bower_components/bootstrap/dist/css/bootstrap.css', extract(styles[1], css)),
reaver.rev('fixtures/css/cartoon.css', extractCss(styles[0], css)),
reaver.rev('fixtures/bower_components/bootstrap/dist/css/bootstrap.css', extractCss(styles[1], css)),
];
const out = inline(html, css, {
@ -93,7 +74,7 @@ test('should inline and extract css', async () => {
expect(strip(out.toString('utf8'))).toBe(strip(expected));
});
test('should extract and minify css', async () => {
test('Extract and minify css', async () => {
const html = await read('fixtures/cartoon.html');
const css = await read('fixtures/critical.css');
const expected = await read('expected/cartoon-expected-minified.html');
@ -104,8 +85,8 @@ test('should extract and minify css', async () => {
]);
const reved = [
reaver.rev('fixtures/css/cartoon.css', extract(styles[0], css, true)),
reaver.rev('fixtures/bower_components/bootstrap/dist/css/bootstrap.css', extract(styles[1], css, true)),
reaver.rev('fixtures/css/cartoon.css', extractCss(styles[0], css)),
reaver.rev('fixtures/bower_components/bootstrap/dist/css/bootstrap.css', extractCss(styles[1], css)),
];
const out = inline(html, css, {
@ -120,7 +101,7 @@ test('should extract and minify css', async () => {
expect(strip(out.toString('utf8'))).toBe(strip(expected));
});
test('should inline and extract css correctly with absolute paths', async () => {
test('Inline and extract css correctly with absolute paths', async () => {
const html = await read('fixtures/cartoon-absolute.html');
const css = await read('fixtures/critical.css');
const expected = await read('expected/cartoon-absolute-expected.html');
@ -131,8 +112,8 @@ test('should inline and extract css correctly with absolute paths', async () =>
]);
const reved = [
reaver.rev('fixtures/css/cartoon.css', extract(styles[0], css)),
reaver.rev('fixtures/bower_components/bootstrap/dist/css/bootstrap.css', extract(styles[1], css)),
reaver.rev('fixtures/css/cartoon.css', extractCss(styles[0], css)),
reaver.rev('fixtures/bower_components/bootstrap/dist/css/bootstrap.css', extractCss(styles[1], css)),
];
const out = inline(html, css, {
@ -148,14 +129,14 @@ test('should inline and extract css correctly with absolute paths', async () =>
expect(strip(out.toString('utf8'))).toBe(strip(expected));
});
test('should not strip of svg closing tags', async () => {
test('Does not strip of svg closing tags', async () => {
const html = await read('fixtures/entities.html');
const out = inline(html, '', {minify: false});
expect(strip(out.toString('utf-8'))).toBe(strip(html));
});
test('should not strip of svg closing tags test 2', async () => {
test('Does not strip svg closing tags test 2', async () => {
const html = await read('fixtures/svg.html');
const expected = await read('expected/test-svg.html');
const css = 'html{font-size:16;}';
@ -164,7 +145,7 @@ test('should not strip of svg closing tags test 2', async () => {
expect(strip(out.toString('utf-8'))).toBe(strip(expected));
});
test('should not keep external urls', async () => {
test('Also preload external urls', async () => {
function strip2(string) {
return string.replace(/\s+/gm, '');
}
@ -176,7 +157,7 @@ test('should not keep external urls', async () => {
expect(strip2(out.toString('utf-8'))).toBe(strip2(expected));
});
test('should not extract on external urls', async () => {
test("Don't try to extract for external urls", async () => {
const html = await read('fixtures/external.html');
const css = await read('fixtures/critical.css');
const expected = await read('expected/external-extract-expected.html');
@ -187,8 +168,8 @@ test('should not extract on external urls', async () => {
]);
const reved = [
reaver.rev('fixtures/css/main.css', extract(styles[0], css)),
reaver.rev('fixtures/bower_components/bootstrap/dist/css/bootstrap.css', extract(styles[1], css)),
reaver.rev('fixtures/css/main.css', extractCss(styles[0], css)),
reaver.rev('fixtures/bower_components/bootstrap/dist/css/bootstrap.css', extractCss(styles[1], css)),
];
const out = inline(html, css, {
@ -203,13 +184,13 @@ test('should not extract on external urls', async () => {
expect(strip(out.toString('utf8'))).toBe(strip(expected));
});
test('should keep self closing svg elements', async () => {
test('Keep self closing svg elements', async () => {
const html = await read('fixtures/entities2.html');
const out = inline(html, '', {minify: false});
const out = inline(html, '');
expect(strip(out.toString('utf-8'))).toBe(strip(html));
});
test('should respect ignore option with string array', async () => {
test('Respect ignore option with string array', async () => {
function strip2(string) {
return string.replace(/\s+/gm, '');
}
@ -225,7 +206,7 @@ test('should respect ignore option with string array', async () => {
expect(strip2(out.toString('utf-8'))).toBe(strip2(expected));
});
test('should respect single ignore option with string', async () => {
test('Respect single ignore option with string', async () => {
function strip2(string) {
return string.replace(/\s+/gm, '');
}
@ -241,7 +222,7 @@ test('should respect single ignore option with string', async () => {
expect(strip2(out.toString('utf-8'))).toBe(strip2(expected));
});
test('should respect ignore option with RegExp array', async () => {
test('Respect ignore option with RegExp array', async () => {
function strip2(string) {
return string.replace(/\s+/gm, '');
}
@ -257,7 +238,7 @@ test('should respect ignore option with RegExp array', async () => {
expect(strip2(out.toString('utf-8'))).toBe(strip2(expected));
});
test('should respect selector option', async () => {
test('Respect selector option', async () => {
const html = await read('fixtures/index.html');
const css = await read('fixtures/critical.css');
@ -270,7 +251,7 @@ test('should respect selector option', async () => {
expect(strip(out.toString('utf-8'))).toBe(strip(expected));
});
test('should ignore stylesheets wrapped in noscript', async () => {
test('Ignore stylesheets wrapped in noscript', async () => {
const html = await read('fixtures/index-noscript.html');
const css = await read('fixtures/critical.css');
@ -280,7 +261,7 @@ test('should ignore stylesheets wrapped in noscript', async () => {
expect(strip(out.toString('utf-8'))).toBe(strip(expected));
});
test("should skip loadcss if it's already present and used for all existing link tags", async () => {
test("Skip loadcss if it's already present and used for all existing link tags", async () => {
const html = await read('fixtures/loadcss.html');
const css = await read('fixtures/critical.css');
@ -290,7 +271,7 @@ test("should skip loadcss if it's already present and used for all existing link
expect(strip(out.toString('utf-8'))).toBe(strip(expected));
});
test('consider existing style tags', async () => {
test('Consider existing style tags', async () => {
const html = await read('fixtures/index-inlined.html');
const css = await read('fixtures/critical.css');
@ -300,7 +281,7 @@ test('consider existing style tags', async () => {
expect(strip(out.toString('utf-8'))).toBe(strip(expected));
});
test("don't add loadcss twice", async () => {
test("Don't add loadcss twice", async () => {
const html = await read('fixtures/loadcss-again.html');
const css = await read('fixtures/critical.css');
@ -309,3 +290,16 @@ test("don't add loadcss twice", async () => {
expect(strip(out.toString('utf-8'))).toBe(strip(expected));
});
test('Replace stylesheets', async () => {
const html = await read('fixtures/cartoon.html');
const css = await read('fixtures/critical.css');
const out = inline(html, css, {
replaceStylesheets: ['replace/all.css'],
});
expect(out.toString('utf8')).not.toMatch('css/cartoon.css');
expect(out.toString('utf8')).not.toMatch('css/bootstrap.css');
expect(out.toString('utf8')).toMatch('href="replace/all.css"');
});

1237
yarn.lock

File diff suppressed because it is too large Load Diff