postcss-basecss
Version:
PostCSS plugin which extracts basic CSS rules for inlining them in your index.html, similar to critical CSS
194 lines (162 loc) • 5.59 kB
JavaScript
var postcss = require('postcss');
var parseCSS = require('css');
var fs = require('fs');
var _ = require('lodash');
var jsdom = require('jsdom-nogyp');
var cssmin = require('cssmin');
// init Basecss
var Basecss = function (options) {
// our default options
this.options = _.extend({
cssFile: '',
htmlFile: '',
selectors: [
// '[^x]small',
// 'h1'
// 'large'
],
propertiesExclude: [
'animation[\-a-z]*', 'transition[\-a-z]*',
'cursor', 'nav[\-a-z]*', 'resize',
'image[\-a-z]*'
],
includeFontFace: true
}, options);
return this;
};
// get specific data from our css data
Basecss.prototype.getData = function (name) {
return _.reject(this.cssData, function (n) {
return n.type !== name;
});
};
// get specific rules by selector
Basecss.prototype.fetchRulesBySelectors = function (selectorArray) {
selectorArray = selectorArray || this.options.selectors;
this.data = _.filter(this.getData('rule'), function (rule) {
// filter from every rule in the css data
var selectors = rule.selectors;
// loop over every selector in this specific rule
for (var i in selectors) {
// loop over our selectors from the options
for (var sel in selectorArray) {
// do they match?
if (selectors[i].search(selectorArray[sel]) !== -1) {
return true;
}
}
}
});
// should we include @font-face rules? we better do...
if (this.options.includeFontFace) {
this.data = this.getData('font-face').concat(this.data);
}
return this;
};
Basecss.prototype.getRulesBySelectors = function (selectorArray) {
this.fetchRulesBySelector(selectorArray);
return this.data;
};
// return our data as perfect CSS-String
Basecss.prototype.toString = function (data) {
return parseCSS.stringify(
{ stylesheet: { rules: data ? data : this.data } }
);
};
// write or build css to the html file - inline!
Basecss.prototype.writeToHtmlFile = function () {
// callback function this=self workaround
var self = this;
var file;
try {
file = fs.readFileSync(this.options.htmlFile);
} catch(err) {
console.log(
'File "' + this.options.htmlFile + '" doesn\'t exist!'
);
return false;
}
// we need jsdom to nicely traverse through our html code
jsdom.env(
file.toString('utf-8'), [],
function (err, window) {
if(err) throw err;
var csstag = window.document
.querySelector('style[data-id="base-css"]');
var head = window.document.querySelector('head');
// do we already have a Basecss style element?
if (!csstag) {
// if not, create one and set our data-id
csstag = window.document.createElement('style');
csstag.setAttribute('data-id', 'base-css');
}
// we need the type attribute!
csstag.setAttribute('type', 'text/css');
// append our build css code
csstag.innerHTML = '\n' + cssmin(self.toString()) + '\n';
// append our element to the head area
head.insertBefore(
csstag,
head.querySelector('link[rel="stylesheet"]')
);
// write the html file back to the file system
fs.writeFileSync(
self.options.htmlFile,
window.document.innerHTML
);
// yay!
console.log(
'Successfully updated "' + self.options.htmlFile + '"!'
);
}
);
};
Basecss.prototype.filterRulesByProperties = function (propertyArray) {
propertyArray = propertyArray || this.options.propertiesExclude;
var rule;
var properties;
var search;
for (var r in this.data) {
rule = this.data[r];
// filter from every rule in the css data
properties = rule.declarations;
// loop over every property in this specific rule
for (var i in properties) {
// loop over our properties from the options
for (var prop in propertyArray) {
// do they match?
search = properties[i]
.property
.search(propertyArray[prop]);
if (search !== -1) {
// remove this property!
properties.splice(i, 1);
}
}
}
}
return this;
};
// shorthand function for the normal way
Basecss.prototype.run = function () {
// read the css file and parse it
this.cssData = parseCSS.parse(
fs.readFileSync(this.options.cssFile).toString('utf-8'),
{ source: this.options.cssFile }
).stylesheet.rules;
this.fetchRulesBySelectors().filterRulesByProperties().writeToHtmlFile();
return this;
};
Basecss.prototype.process = function (str, options) {
if (options) _.extend(this.options, options);
// read the css file and parse it
this.cssData = parseCSS.parse(str.toString('utf-8')).stylesheet.rules;
this.fetchRulesBySelectors().filterRulesByProperties().writeToHtmlFile();
return this;
};
module.exports = postcss.plugin('postcss-basecss', function (opts) {
opts = opts || {};
return function (css) {
new Basecss(opts).process(css);
};
});