inline-critical/index.js

134 lines
3.4 KiB
JavaScript
Raw Normal View History

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';
2015-04-28 06:37:48 +02:00
var _ = require('lodash');
var fs = require('fs');
var path = require('path');
2014-12-08 23:02:01 +01:00
var UglifyJS = require("uglify-js");
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');
2014-12-08 23:15:13 +01:00
// get loadCSS
2015-06-10 18:07:48 +02:00
var loadCSS = read(path.join(__dirname, 'vendor', 'loadCSS.js'));
2014-12-08 23:15:13 +01:00
loadCSS = 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
2014-12-08 23:15:13 +01:00
* @param {string} str
* @return {string}
2014-11-25 17:21:43 +01:00
*/
function normalizePath(str) {
return process.platform === 'win32' ? slash(str) : str;
}
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) {
2014-12-08 23:02:01 +01:00
return fs.readFileSync(file, 'utf8');
}
2015-06-10 18:07:48 +02:00
module.exports = function (html, styles, options) {
2014-08-04 00:01:39 +02:00
2015-06-25 06:27:18 +02:00
if (!html) {
throw new Error('HTML missing!')
}
if (!styles) {
throw new Error('Styles missing!')
}
2015-04-30 16:56:36 +02:00
var $ = cheerio.load(String(html), {
decodeEntities: false
});
2015-06-10 18:07:48 +02:00
var links = $('link[rel="stylesheet"]').filter(function () {
2015-06-09 17:19:31 +02:00
return !$(this).parents('noscript').length;
});
2014-08-04 00:01:39 +02:00
var noscript = $('<noscript>\n</noscript>');
var o = options || {};
2014-08-04 00:01:39 +02:00
2015-06-09 07:12:09 +02:00
if (_.isString(o.ignore)) {
o.ignore = [o.ignore];
}
if (o.ignore) {
2015-06-10 18:07:48 +02:00
links = links.filter(function () {
2015-06-09 07:12:09 +02:00
var href = $(this).attr('href');
2015-06-10 18:07:48 +02:00
return _.findIndex(o.ignore, function (arg) {
2015-06-09 07:12:09 +02:00
return _.isRegExp(arg) && arg.test(href) || arg === href;
}) === -1;
});
}
2014-08-04 00:01:39 +02:00
// minify if minify option is set
if (o.minify) {
2015-02-17 00:59:53 +01:00
styles = new CleanCSS().minify(styles).styles;
2014-08-04 00:01:39 +02:00
}
// insert inline styles right before first <link rel="stylesheet" />
links.eq(0).before('<style type="text/css">\n' + styles + '\n</style>\n');
// insert noscript block right after stylesheets
links.eq(0).first().after(noscript);
2015-06-10 18:07:48 +02:00
var hrefs = links.map(function (idx, el) {
2014-10-20 16:44:53 +02:00
return $(this).attr('href');
2014-08-04 00:01:39 +02:00
}).toArray();
2015-06-09 07:12:09 +02:00
// extract styles from stylesheets if extract option is set
if (o.extract) {
if (!o.basePath) {
throw new Error('Option `basePath` is missing and required when using `extract`!');
}
2015-06-10 18:07:48 +02:00
hrefs = hrefs.map(function (href) {
var file = path.resolve(path.join(o.basePath, href));
if (!fs.existsSync(file)) {
2015-02-23 17:04:34 +01:00
return href;
}
2015-06-10 18:07:48 +02:00
var diff = normalizeNewline(cave(file, {css: styles}));
2014-10-20 16:44:53 +02:00
fs.writeFileSync(reaver.rev(file, diff), diff);
2014-11-25 17:28:56 +01:00
return normalizePath(reaver.rev(href, diff));
});
}
2014-10-20 16:44:53 +02:00
// wrap links to stylesheets in noscript block so that they will evaluated when js is turned off
links.each(function (idx) {
var el = $(this);
el.attr('href', hrefs[idx]);
noscript.append(el);
noscript.append('\n');
});
2015-02-17 00:59:53 +01:00
// build js block to load blocking stylesheets and insert it right before
2014-12-08 21:33:40 +01:00
$(noscript).before('<script>\n' +
2014-12-08 23:02:01 +01:00
'(function(u){' +
loadCSS +
'for(var i in u){loadCSS(u[i]);}' +
'}([\'' + hrefs.join('\',\'') + '\']));\n' +
2014-08-04 00:01:39 +02:00
'</script>\n');
2015-02-17 00:59:53 +01:00
var dom = parse($.html());
2015-04-29 06:23:39 +02:00
var markup = render(dom);
2015-02-17 00:59:53 +01:00
return new Buffer(markup);
2014-08-04 00:01:39 +02:00
};