suitcss-preprocessor
Version:
A CSS preprocessor for working with SUIT CSS
197 lines (167 loc) • 4.84 kB
JavaScript
;
const assign = require('object-assign-deep');
const autoprefixer = require('autoprefixer');
const bemLinter = require('postcss-bem-linter');
const cssnano = require('cssnano');
const difference = require('lodash.difference');
const encapsulationPlugins = require('./encapsulation');
const fs = require('fs');
const isEmpty = require('lodash.isempty');
const isPromise = require('is-promise');
const pify = require('pify');
const postcssEasyImport = require('postcss-easy-import');
const reporter = require('postcss-reporter');
const stylelint = require('stylelint');
const stylelintConfigSuit = require('stylelint-config-suitcss');
let postcss = require('postcss'); // eslint-disable-line prefer-const
module.exports = preprocessor;
/**
* Default configuration
* and options to PostCSS plugins
*/
const defaults = {
debug: identity,
lint: true,
minify: false,
encapsulate: false,
use: [
'postcss-custom-properties',
'postcss-calc',
'postcss-color-function',
'postcss-custom-media',
'postcss-apply'
],
autoprefixer: {
browsers: `> 1%, last 2 versions, safari > 6, ie > 9,
ios > 6, android > 4.3, samsung > 3, chromeandroid > 50`
},
'postcss-easy-import': {
load: getFileContent,
onImport: noop
},
'postcss-reporter': {
clearMessages: true
},
// http://cssnano.co/optimisations/
cssnano: {
calc: false,
autoprefixer: false,
mergeRules: false,
safe: true
}
};
/**
* Process CSS
*
* @param {String} css
* @param {Object} options
* @param {String} filename
* @returns {Promise}
*/
function preprocessor(css, options, filename) {
options = mergeOptions(options);
let plugins = [
postcssEasyImport(options['postcss-easy-import'])
];
plugins = plugins.concat(
options.use.map(p => {
const plugin = require(p);
const settings = options[p];
return settings ? plugin(settings) : plugin;
})
);
if (options.encapsulate) {
plugins = plugins.concat([
encapsulationPlugins.resetGeneric,
encapsulationPlugins.resetInherited
]);
}
// autoprefixer and postcss-reporter
// should always be the last plugin
plugins = plugins.concat([
autoprefixer(options.autoprefixer),
reporter(options['postcss-reporter'])
]);
const processor = postcss(options.debug(plugins));
if (options.minify) {
processor.use(cssnano(options.cssnano));
}
return lintFile(css, options, filename)
.then(result => processor.process(result.css, options.postcss));
}
/**
* Merge options with defaults and set root
*
* @param {Object} options
* @returns {Object} Merged options object
*/
function mergeOptions(options) {
options = options || {};
const mergedOpts = assign({}, defaults, options);
const easyImportOpts = mergedOpts['postcss-easy-import'];
const origLoad = easyImportOpts.load;
const origOnImport = easyImportOpts.onImport;
if (mergedOpts.root) {
easyImportOpts.root = mergedOpts.root;
}
easyImportOpts.load = filename => {
const transformedCss = origLoad(filename);
return lintFile(transformedCss, mergedOpts, filename)
.then(result => result.css);
};
easyImportOpts.onImport = importedFiles => {
updateWatchTaskFiles(importedFiles);
origOnImport(importedFiles);
};
// Allow additional plugins to be merged with the defaults
// but remove any duplicates so that it respects the new order
if (!isEmpty(options.use)) {
const plugins = difference(mergedOpts.use, options.use);
// Remove core plugins. Can't reorder them
const userPlugins = difference(options.use, [
'postcss-easy-import',
'autoprefixer',
'postcss-reporter'
]);
mergedOpts.use = plugins.concat(userPlugins);
}
return mergedOpts;
}
/**
* Lint component with postcss-bem-linter and stylelint
*
* @param {String} css
* @param {Object} options
* @param {String} filename
* @returns {Promise} Used by postcss-import transform
*/
function lintFile(css, options, filename) {
const processor = postcss();
if (options.lint) {
processor
.use(stylelint(options.stylelint || stylelintConfigSuit))
.use(bemLinter(options['postcss-bem-linter']));
}
// Merge filename alongside any other `postcss` options
assign(options, {
postcss: {from: filename}
});
processor
.use(reporter(options['postcss-reporter']));
if (isPromise(css)) {
return css.then(css => processor.process(css, options.postcss)); // eslint-disable-line no-shadow
}
return processor.process(css, options.postcss);
}
function noop() {}
function getFileContent(filename) {
return pify(fs.readFile)(filename, 'utf8');
}
function identity(x) {
return x;
}
function updateWatchTaskFiles(files) {
if (typeof global.watchCSS === 'function') {
global.watchCSS(files);
}
}