mirror of
https://github.com/danog/inline-critical.git
synced 2024-11-26 20:14:41 +01:00
fix: prevent multiple loadcss includes
This commit is contained in:
parent
44c0cd38dd
commit
226bf160ea
90
index.js
90
index.js
@ -11,6 +11,9 @@
|
||||
'use strict';
|
||||
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');
|
||||
@ -25,6 +28,14 @@ const resolve = require('resolve');
|
||||
const detectIndent = require('detect-indent');
|
||||
const prettier = require('prettier');
|
||||
|
||||
const DEFAULT_OPTIONS = {
|
||||
minify: true,
|
||||
extract: false,
|
||||
polyfill: true,
|
||||
ignore: [],
|
||||
stylesheets: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Get loadcss + cssrelpreload script
|
||||
*
|
||||
@ -86,10 +97,15 @@ function prettifyCSS(styles) {
|
||||
return prettier.format(styles, {parser: 'css'});
|
||||
}
|
||||
|
||||
function extract(css, critical) {
|
||||
css = minifyCSS(css);
|
||||
critical = minifyCSS(critical);
|
||||
return normalizeNewline(postcss(discard({css: critical})).process(css).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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -119,34 +135,26 @@ const getSvgs = (str = '') => {
|
||||
* @returns {string} HTML Source with inlined critical css
|
||||
*/
|
||||
function inline(html, styles, options) {
|
||||
if (!_.isString(html)) {
|
||||
const o = {...DEFAULT_OPTIONS, ...(options || {})};
|
||||
|
||||
if (!isString(html)) {
|
||||
html = String(html);
|
||||
}
|
||||
|
||||
const o = _.assign(
|
||||
{
|
||||
minify: true,
|
||||
},
|
||||
options || {}
|
||||
);
|
||||
|
||||
const $ = cheerio.load(html, {
|
||||
decodeEntities: false,
|
||||
});
|
||||
|
||||
// Fetch styles already inlined
|
||||
// Process style tags
|
||||
const inlineStyles = $('head style')
|
||||
.map((i, el) => $(el).html())
|
||||
.get()
|
||||
.join('\n');
|
||||
|
||||
// Only missing styles
|
||||
let missing = extract(styles, inlineStyles);
|
||||
if (!o.minify) {
|
||||
missing = prettifyCSS(missing);
|
||||
}
|
||||
|
||||
// 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;
|
||||
});
|
||||
@ -158,18 +166,14 @@ function inline(html, styles, options) {
|
||||
const targetIndent = getIndent(html, target);
|
||||
const $target = $(target);
|
||||
|
||||
if (_.isString(o.ignore)) {
|
||||
o.ignore = [o.ignore];
|
||||
if (!Array.isArray(o.ignore)) {
|
||||
o.ignore = [o.ignore].filter(i => i);
|
||||
}
|
||||
|
||||
if (o.ignore) {
|
||||
links = _.filter(links, link => {
|
||||
if (o.ignore.length > 0) {
|
||||
links = filter(links, link => {
|
||||
const href = $(link).attr('href');
|
||||
return (
|
||||
_.findIndex(o.ignore, arg => {
|
||||
return (_.isRegExp(arg) && arg.test(href)) || arg === href;
|
||||
}) === -1
|
||||
);
|
||||
return !o.ignore.some(i => (isRegExp(i) && i.test(href)) || i === href);
|
||||
});
|
||||
}
|
||||
|
||||
@ -211,12 +215,7 @@ function inline(html, styles, options) {
|
||||
const file = path.resolve(path.join(o.basePath, href));
|
||||
if (fs.existsSync(file)) {
|
||||
const orig = fs.readFileSync(file);
|
||||
let diff = extract(orig, inlined);
|
||||
|
||||
if (!o.minify) {
|
||||
diff = prettifyCSS(diff);
|
||||
}
|
||||
|
||||
const diff = extract(orig, inlined, o.minify);
|
||||
const filename = reaver.rev(file, diff);
|
||||
|
||||
fs.writeFileSync(filename, diff);
|
||||
@ -233,15 +232,22 @@ function inline(html, styles, options) {
|
||||
$el.attr('onload', "this.onload=null;this.rel='stylesheet'");
|
||||
});
|
||||
|
||||
// Add loadcss + cssrelpreload polyfill
|
||||
const scriptAnchor = $('link[rel="stylesheet"], noscript')
|
||||
.filter(function() {
|
||||
return !$(this).parents('noscript').length;
|
||||
})
|
||||
.last()
|
||||
.get(0);
|
||||
// Only add loadcss if it's not already included
|
||||
const loadCssIncluded = $('script')
|
||||
.get()
|
||||
.some(tag => ($(tag).html() || '').includes('loadCSS'));
|
||||
|
||||
$(scriptAnchor).after('\n' + targetIndent + '<script>' + getScript() + '</script>');
|
||||
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();
|
||||
|
59
test/expected/loadcss-again.html
Normal file
59
test/expected/loadcss-again.html
Normal file
@ -0,0 +1,59 @@
|
||||
<!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 -->
|
||||
|
||||
<!-- 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/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||
<noscript><link rel="stylesheet" href="css/main.css"></noscript>
|
||||
<script id="loadcss">
|
||||
!function(t){"use strict";t.loadCSS||(t.loadCSS=function(){});var e=loadCSS.relpreload={};if(e.support=function(){var e;try{e=t.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),e.bindMediaToggle=function(t){function e(){t.media=a}var a=t.media||"all";t.addEventListener?t.addEventListener("load",e):t.attachEvent&&t.attachEvent("onload",e),setTimeout(function(){t.rel="stylesheet",t.media="only x"}),setTimeout(e,3e3)},e.poly=function(){if(!e.support())for(var a=t.document.getElementsByTagName("link"),n=0;n<a.length;n++){var o=a[n];"preload"!==o.rel||"style"!==o.getAttribute("as")||o.getAttribute("data-loadcss")||(o.setAttribute("data-loadcss",!0),e.bindMediaToggle(o))}},!e.support()){e.poly();var a=t.setInterval(e.poly,500);t.addEventListener?t.addEventListener("load",function(){e.poly(),t.clearInterval(a)}):t.attachEvent&&t.attachEvent("onload",function(){e.poly(),t.clearInterval(a)})}"undefined"!=typeof exports?exports.loadCSS=loadCSS:t.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]-->
|
||||
|
||||
<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>
|
||||
<p class="lead">Always a pleasure scaffolding your apps.</p>
|
||||
<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>
|
||||
<p>HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or sites.</p>
|
||||
|
||||
<h4>Bootstrap</h4>
|
||||
<p>Sleek, intuitive, and powerful mobile first front-end framework for faster and easier web development.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>♥ from the Yeoman team</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
55
test/fixtures/loadcss-again.html
vendored
Normal file
55
test/fixtures/loadcss-again.html
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
<!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 -->
|
||||
|
||||
<!-- build:css styles/main.css -->
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<script id="loadcss">
|
||||
!function(t){"use strict";t.loadCSS||(t.loadCSS=function(){});var e=loadCSS.relpreload={};if(e.support=function(){var e;try{e=t.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),e.bindMediaToggle=function(t){function e(){t.media=a}var a=t.media||"all";t.addEventListener?t.addEventListener("load",e):t.attachEvent&&t.attachEvent("onload",e),setTimeout(function(){t.rel="stylesheet",t.media="only x"}),setTimeout(e,3e3)},e.poly=function(){if(!e.support())for(var a=t.document.getElementsByTagName("link"),n=0;n<a.length;n++){var o=a[n];"preload"!==o.rel||"style"!==o.getAttribute("as")||o.getAttribute("data-loadcss")||(o.setAttribute("data-loadcss",!0),e.bindMediaToggle(o))}},!e.support()){e.poly();var a=t.setInterval(e.poly,500);t.addEventListener?t.addEventListener("load",function(){e.poly(),t.clearInterval(a)}):t.attachEvent&&t.attachEvent("onload",function(){e.poly(),t.clearInterval(a)})}"undefined"!=typeof exports?exports.loadCSS=loadCSS:t.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]-->
|
||||
|
||||
<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>
|
||||
<p class="lead">Always a pleasure scaffolding your apps.</p>
|
||||
<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>
|
||||
<p>HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or sites.</p>
|
||||
|
||||
<h4>Bootstrap</h4>
|
||||
<p>Sleek, intuitive, and powerful mobile first front-end framework for faster and easier web development.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>♥ from the Yeoman team</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -299,3 +299,13 @@ test('consider existing style tags', async () => {
|
||||
|
||||
expect(out.toString('utf-8')).toBe(expected);
|
||||
});
|
||||
|
||||
test("don't add loadcss twice", async () => {
|
||||
const html = await read('fixtures/loadcss-again.html');
|
||||
const css = await read('fixtures/critical.css');
|
||||
|
||||
const expected = await read('expected/loadcss-again.html');
|
||||
const out = inline(html, css);
|
||||
|
||||
expect(out.toString('utf-8')).toBe(expected);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user