2014-08-04 00:01:39 +02:00
|
|
|
/**
|
|
|
|
* Module to inline styles while loading the existing stylesheets async
|
|
|
|
*
|
|
|
|
* @author Ben Zörb @bezoerb https://github.com/bezoerb
|
|
|
|
* @copyright Copyright (c) 2014 Ben Zörb
|
|
|
|
*
|
|
|
|
* Licensed under the MIT license.
|
|
|
|
* http://bezoerb.mit-license.org/
|
|
|
|
* All rights reserved.
|
|
|
|
*/
|
|
|
|
'use strict';
|
2014-10-19 03:12:33 +02:00
|
|
|
var fs = require('fs');
|
|
|
|
var path = require('path');
|
2016-04-14 17:26:33 +02:00
|
|
|
var _ = require('lodash');
|
2015-11-13 22:59:10 +01:00
|
|
|
var UglifyJS = require('uglify-js');
|
2014-10-19 03:12:33 +02:00
|
|
|
var cave = require('cave');
|
2014-10-20 16:44:53 +02:00
|
|
|
var reaver = require('reaver');
|
2014-08-04 00:01:39 +02:00
|
|
|
var cheerio = require('cheerio');
|
2015-02-17 00:59:53 +01:00
|
|
|
var render = require('dom-serializer');
|
|
|
|
var parse = require('cheerio/lib/parse');
|
2014-08-04 00:01:39 +02:00
|
|
|
var CleanCSS = require('clean-css');
|
2014-11-25 17:21:43 +01:00
|
|
|
var slash = require('slash');
|
2014-11-25 23:15:48 +01:00
|
|
|
var normalizeNewline = require('normalize-newline');
|
2015-11-13 22:29:05 +01:00
|
|
|
var resolve = require('resolve');
|
2016-04-17 00:50:40 +02:00
|
|
|
var detectIndent = require('detect-indent');
|
2016-04-14 17:26:33 +02:00
|
|
|
|
2016-04-14 22:36:45 +02:00
|
|
|
/**
|
|
|
|
* Get loadcss + cssrelpreload script
|
|
|
|
*
|
|
|
|
* @returns {string}
|
|
|
|
*/
|
|
|
|
function getScript() {
|
|
|
|
var loadCssMain = resolve.sync('fg-loadcss');
|
|
|
|
var loadCssBase = path.dirname(loadCssMain);
|
2016-04-14 17:26:33 +02:00
|
|
|
|
2016-04-14 22:36:45 +02:00
|
|
|
var loadCSS = read(loadCssMain) + read(path.join(loadCssBase, 'cssrelpreload.js'));
|
|
|
|
return UglifyJS.minify(loadCSS, {fromString: true}).code;
|
|
|
|
}
|
2014-12-08 23:02:01 +01:00
|
|
|
|
2014-11-25 17:21:43 +01:00
|
|
|
/**
|
|
|
|
* Fixup slashes in file paths for windows
|
2016-04-14 22:36:45 +02:00
|
|
|
*
|
2014-12-08 23:15:13 +01:00
|
|
|
* @param {string} str
|
|
|
|
* @return {string}
|
2014-11-25 17:21:43 +01:00
|
|
|
*/
|
|
|
|
function normalizePath(str) {
|
2015-11-13 22:59:10 +01:00
|
|
|
return process.platform === 'win32' ? slash(str) : str;
|
2014-11-25 17:21:43 +01:00
|
|
|
}
|
2014-08-04 00:01:39 +02:00
|
|
|
|
2014-12-08 23:15:13 +01:00
|
|
|
/**
|
|
|
|
* Read file *
|
|
|
|
* @param {string} file
|
|
|
|
* @returns {string}
|
|
|
|
*/
|
2015-06-10 18:07:48 +02:00
|
|
|
function read(file) {
|
2015-11-13 22:59:10 +01:00
|
|
|
return fs.readFileSync(file, 'utf8');
|
2014-12-08 23:02:01 +01:00
|
|
|
}
|
|
|
|
|
2016-04-17 00:50:40 +02:00
|
|
|
/**
|
|
|
|
* Get the indentation of the link tags
|
|
|
|
* @param html
|
|
|
|
* @param $el
|
|
|
|
*/
|
|
|
|
function getIndent(html, $el) {
|
2016-04-18 16:36:33 +02:00
|
|
|
var regName = new RegExp(_.escapeRegExp(_.get($el, 'name')));
|
|
|
|
var regHref = new RegExp(_.escapeRegExp(_.get($el, 'attribs.href')));
|
|
|
|
var regRel = new RegExp(_.escapeRegExp(_.get($el, 'attribs.rel')));
|
2016-04-17 00:50:40 +02:00
|
|
|
var lines = _.filter(html.split(/[\r\n]+/), function (line) {
|
|
|
|
return regName.test(line) && regHref.test(line) && regRel.test(line);
|
|
|
|
});
|
|
|
|
return detectIndent(lines.join('\n')).indent;
|
|
|
|
}
|
|
|
|
|
2015-06-10 18:07:48 +02:00
|
|
|
module.exports = function (html, styles, options) {
|
2016-04-18 06:35:08 +02:00
|
|
|
if (!_.isString(html)) {
|
|
|
|
html = String(html);
|
|
|
|
}
|
2016-04-17 23:46:58 +02:00
|
|
|
var $ = cheerio.load(html, {
|
2015-11-13 22:59:10 +01:00
|
|
|
decodeEntities: false
|
2015-06-09 07:12:09 +02:00
|
|
|
});
|
2015-07-10 21:55:28 +02:00
|
|
|
|
2016-04-17 00:50:40 +02:00
|
|
|
var allLinks = $('link[rel="stylesheet"], link[rel="preload"][as="style"]').filter(function () {
|
2015-11-13 22:59:10 +01:00
|
|
|
return !$(this).parents('noscript').length;
|
|
|
|
});
|
2015-06-09 07:12:09 +02:00
|
|
|
|
2016-04-17 00:50:40 +02:00
|
|
|
var links = allLinks.filter('[rel="stylesheet"]');
|
|
|
|
|
|
|
|
var o = _.assign({
|
2016-04-17 00:58:04 +02:00
|
|
|
minify: true
|
2016-04-17 00:50:40 +02:00
|
|
|
}, options || {});
|
|
|
|
|
|
|
|
var target = o.selector || allLinks.get(0) || $('script').get(0);
|
|
|
|
var indent = detectIndent(html).indent;
|
|
|
|
var targetIndent = getIndent(html, target);
|
2015-11-13 22:59:10 +01:00
|
|
|
var $target = $(target);
|
2015-07-10 21:55:28 +02:00
|
|
|
|
2015-11-13 22:59:10 +01:00
|
|
|
if (_.isString(o.ignore)) {
|
|
|
|
o.ignore = [o.ignore];
|
|
|
|
}
|
2015-07-10 21:55:28 +02:00
|
|
|
|
2015-11-13 22:59:10 +01:00
|
|
|
if (o.ignore) {
|
2016-04-17 00:50:40 +02:00
|
|
|
links = _.filter(links, function (link) {
|
2016-04-14 22:36:45 +02:00
|
|
|
var href = $(link).attr('href');
|
|
|
|
return _.findIndex(options.ignore, function (arg) {
|
|
|
|
return _.isRegExp(arg) && arg.test(href) || arg === href;
|
|
|
|
}) === -1;
|
|
|
|
});
|
2015-07-10 21:55:28 +02:00
|
|
|
}
|
2014-10-19 03:12:33 +02:00
|
|
|
|
2015-11-13 22:59:10 +01:00
|
|
|
// minify if minify option is set
|
|
|
|
if (o.minify) {
|
|
|
|
styles = new CleanCSS().minify(styles).styles;
|
|
|
|
}
|
2014-10-20 16:44:53 +02:00
|
|
|
|
2015-11-13 22:59:10 +01:00
|
|
|
// insert inline styles right before first <link rel="stylesheet" />
|
2016-04-17 00:50:40 +02:00
|
|
|
$target.before([
|
|
|
|
'<style type="text/css">',
|
2016-04-18 07:24:12 +02:00
|
|
|
indent + styles.replace(/(\r\n|\r|\n)/g, '$1' + targetIndent + indent).replace(/^[\s\t]+$/g, ''),
|
2016-04-17 00:50:40 +02:00
|
|
|
'</style>', ''
|
2016-04-18 07:24:12 +02:00
|
|
|
].join('\n' + targetIndent).replace(/(\r\n|\r|\n)[\s\t]+(\r\n|\r|\n)/g, '$1$2'));
|
2015-11-13 22:59:10 +01:00
|
|
|
|
|
|
|
if (links.length) {
|
2016-04-17 00:50:40 +02:00
|
|
|
// modify links and ad clones to noscript block
|
|
|
|
$(links).each(function (idx, el) {
|
|
|
|
if (o.extract && !o.basePath) {
|
2015-11-13 22:59:10 +01:00
|
|
|
throw new Error('Option `basePath` is missing and required when using `extract`!');
|
|
|
|
}
|
2016-04-17 00:50:40 +02:00
|
|
|
|
|
|
|
var $el = $(el);
|
|
|
|
|
|
|
|
if (o.extract) {
|
|
|
|
var href = $el.attr('href');
|
2015-11-13 22:59:10 +01:00
|
|
|
var file = path.resolve(path.join(o.basePath, href));
|
2016-04-17 00:50:40 +02:00
|
|
|
if (fs.existsSync(file)) {
|
|
|
|
var diff = normalizeNewline(cave(file, {css: styles}));
|
|
|
|
fs.writeFileSync(reaver.rev(file, diff), diff);
|
|
|
|
$el.attr('href', normalizePath(reaver.rev(href, diff)));
|
2015-11-13 22:59:10 +01:00
|
|
|
}
|
2016-04-17 00:50:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// add each fallback right behind the current style to keep source order when ignoring stylesheets
|
|
|
|
$el.after('\n' + getIndent(html, el) + '<noscript>' + render(this) + '</noscript>');
|
|
|
|
|
|
|
|
// add preload atttibutes to actual link element
|
|
|
|
$el.attr('rel', 'preload');
|
|
|
|
$el.attr('as', 'style');
|
|
|
|
$el.attr('onload', 'this.rel=\'stylesheet\'');
|
2015-11-13 22:59:10 +01:00
|
|
|
});
|
|
|
|
|
2016-04-17 00:50:40 +02:00
|
|
|
// add loadcss + cssrelpreload polyfill
|
|
|
|
var scriptAnchor = $('link[rel="stylesheet"], noscript').filter(function () {
|
|
|
|
return !$(this).parents('noscript').length;
|
|
|
|
}).last().get(0);
|
|
|
|
|
|
|
|
$(scriptAnchor).after('\n' + targetIndent + '<script>' + getScript() + '</script>');
|
2015-11-13 22:59:10 +01:00
|
|
|
}
|
2015-02-17 00:59:53 +01:00
|
|
|
|
2015-11-13 22:59:10 +01:00
|
|
|
var dom = parse($.html());
|
|
|
|
var markup = render(dom);
|
2015-02-17 00:59:53 +01:00
|
|
|
|
2015-11-13 22:59:10 +01:00
|
|
|
return new Buffer(markup);
|
2014-08-04 00:01:39 +02:00
|
|
|
};
|