const cssnano = require('cssnano'); const postcss = require('postcss'); /** * Optimize cssnano plugin * * @param {Object} options */ function OptimizeCssnanoPlugin(options) { this.options = Object.assign({ sourceMap: false, cssnanoOptions: { preset: 'default', }, }, options); if (this.options.sourceMap) { this.options.sourceMap = Object.assign( {inline: false}, this.options.sourceMap || {}); } } OptimizeCssnanoPlugin.prototype.apply = function(compiler) { const self = this; compiler.hooks.emit.tapAsync('OptimizeCssnanoPlugin', function(compilation, callback) { // Search for CSS assets const assetsNames = Object.keys(compilation.assets) .filter((assetName) => { return /\.css$/i.test(assetName); }); let hasErrors = false; const promises = []; // Generate promises for each minification assetsNames.forEach((assetName) => { // Original CSS const asset = compilation.assets[assetName]; const originalCss = asset.source(); // Options for particalar cssnano call const postCssOptions = { from: assetName, to: assetName, map: false, }; const cssnanoOptions = self.options.cssnanoOptions; // Extract or remove previous map const mapName = assetName + '.map'; if (self.options.sourceMap) { // Use previous map if exist... if (compilation.assets[mapName]) { const mapObject = JSON.parse(compilation.assets[mapName].source()); // ... and not empty if (mapObject.sources.length > 0 || mapObject.mappings.length > 0) { postCssOptions.map = Object.assign({ prev: compilation.assets[mapName].source(), }, self.options.sourceMap); } else { postCssOptions.map = Object.assign({}, self.options.sourceMap); } } } else { delete compilation.assets[mapName]; } // Run minification const promise = postcss([cssnano(cssnanoOptions)]) .process(originalCss, postCssOptions) .then((result) => { if (hasErrors) { return; } // Extract CSS back to assets const processedCss = result.css; compilation.assets[assetName] = { source: function() { return processedCss; }, size: function() { return processedCss.length; }, }; // Extract map back to assets if (result.map) { const processedMap = result.map.toString(); compilation.assets[mapName] = { source: function() { return processedMap; }, size: function() { return processedMap.length; }, }; } } ).catch(function(err) { hasErrors = true; throw new Error('CSS minification error: ' + err.message + '. File: ' + assetName); } ); promises.push(promise); }); Promise.all(promises) .then(function() { callback(); }) .catch(callback); }); }; module.exports = OptimizeCssnanoPlugin;