Instantly share code, notes, and snippets.

Embed
What would you like to do?
Critical css generator for easy debugging
const penthouse = require('penthouse');
const async = require('async');
const fs = require('fs-extra');
const _ = require('lodash');
const less = require('less');
const CleanCss = require('clean-css');
const chalk = require('chalk');
const checkmark = '';
const CDN = 'https://redacted.com/assets/img/';
const replaceFontPath = (css) => css.replace(/\.\.\/fonts\/([^')]+?)([')])/g, '{{#lambdas.staticUrl}}/fonts/$1{{/lambdas.staticUrl}}$2');
module.exports = function(grunt, context) {
grunt.registerMultiTask('critical', 'Generate critical css for SEO pages', function(page) {
let target = this.target;
// If this is the dist task and the dist task wasn't specifically targeted
if (process.argv[2] === 'critical' && target === 'dist') {
// Don't actually run anything. Just log some instructions.
console.log();
console.log(chalk.gray('Grunt critical was run without a target. Skipping the dist target.'));
console.log(chalk.gray('If you really meant to run the dist target, target it specifically'));
console.log(chalk.gray('by running ' + chalk.red('grunt critical:dist') + '.'));
return;
} else {
// Tell grunt this is a async task.
const done = this.async();
const options = this.options({ compress: false, replaceImagePaths: false });
let pages = options.pages;
if (page) {
pages = _.pick(pages, page.split(','));
}
// Some initial logging just so the user knows something is happening.
console.log();
console.log(chalk.gray('Generating critical css for'), chalk.green(_.keys(pages).length), chalk.gray('pages.'));
console.log();
// eachOf is for iterating the key/value pairs in an object.
async.eachOfSeries(pages, function(route, page, nextPage) {
penthouse({
url: `http://localhost:8000${ route }`,
css: `${ options.target }/css/app.css`,
width: 1200,
height: 2100,
forceInclude: [/^\.col-(xs|sm|md|lg)(-offset)?-\d{1,2}/, '.modal'],
timeout: 120000
}, function(err, criticalCss) {
if (err) {
nextPage(err);
} else {
var wrapped = `body.critical {
${ criticalCss }
}`;
less.render(wrapped, function(err, raw) {
if (err) {
nextPage(err);
} else {
// Just in case something is amiss with the generated css, try/catch this.
try {
var css = replaceFontPath(raw.css);
if (options.replaceImagePaths) {
css = css.replace(/\/assets\/img\//g, CDN);
}
css = css.replace(/body\.critical body/g, 'body.critical').replace(/body\.critical html/g, 'html');
if (options.compress) {
css = new CleanCss({ compress: options.compress }).minify(css).styles;
}
let tag = `<style id="critical-css">${css}</style>`;
fs.outputFile(context.paths.root + '/server/views/partials/critical/' + _.kebabCase(page) + '.html', tag, { encoding: 'utf8' }, function(err) {
console.log(' ', chalk.green(checkmark), ' ', `${chalk.cyan(page)}:`, `http://localhost:8000${ route }`);
nextPage(err);
});
} catch (e) {
console.log(e);
nextPage(e);
}
}
});
}
});
}, function(err) {
if (err) {
console.log(err);
}
done();
});
}
});
return {
options: {
pages: {
redacted: '/redacted'
}
},
dev: {
options: {
target: '<%= paths.generated %>'
}
},
dist: {
options: {
target: '<%= paths.generated %>',
compress: true,
replaceImagePaths: true
}
}
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment