index.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. const cssnano = require('cssnano');
  2. const postcss = require('postcss');
  3. /**
  4. * Optimize cssnano plugin
  5. *
  6. * @param {Object} options
  7. */
  8. function OptimizeCssnanoPlugin(options) {
  9. this.options = Object.assign({
  10. sourceMap: false,
  11. cssnanoOptions: {
  12. preset: 'default',
  13. },
  14. }, options);
  15. if (this.options.sourceMap) {
  16. this.options.sourceMap = Object.assign(
  17. {inline: false},
  18. this.options.sourceMap || {});
  19. }
  20. }
  21. OptimizeCssnanoPlugin.prototype.apply = function(compiler) {
  22. const self = this;
  23. compiler.hooks.emit.tapAsync('OptimizeCssnanoPlugin',
  24. function(compilation, callback) {
  25. // Search for CSS assets
  26. const assetsNames = Object.keys(compilation.assets)
  27. .filter((assetName) => {
  28. return /\.css$/i.test(assetName);
  29. });
  30. let hasErrors = false;
  31. const promises = [];
  32. // Generate promises for each minification
  33. assetsNames.forEach((assetName) => {
  34. // Original CSS
  35. const asset = compilation.assets[assetName];
  36. const originalCss = asset.source();
  37. // Options for particalar cssnano call
  38. const postCssOptions = {
  39. from: assetName,
  40. to: assetName,
  41. map: false,
  42. };
  43. const cssnanoOptions = self.options.cssnanoOptions;
  44. // Extract or remove previous map
  45. const mapName = assetName + '.map';
  46. if (self.options.sourceMap) {
  47. // Use previous map if exist...
  48. if (compilation.assets[mapName]) {
  49. const mapObject = JSON.parse(compilation.assets[mapName].source());
  50. // ... and not empty
  51. if (mapObject.sources.length > 0 || mapObject.mappings.length > 0) {
  52. postCssOptions.map = Object.assign({
  53. prev: compilation.assets[mapName].source(),
  54. }, self.options.sourceMap);
  55. } else {
  56. postCssOptions.map = Object.assign({}, self.options.sourceMap);
  57. }
  58. }
  59. } else {
  60. delete compilation.assets[mapName];
  61. }
  62. // Run minification
  63. const promise = postcss([cssnano(cssnanoOptions)])
  64. .process(originalCss, postCssOptions)
  65. .then((result) => {
  66. if (hasErrors) {
  67. return;
  68. }
  69. // Extract CSS back to assets
  70. const processedCss = result.css;
  71. compilation.assets[assetName] = {
  72. source: function() {
  73. return processedCss;
  74. },
  75. size: function() {
  76. return processedCss.length;
  77. },
  78. };
  79. // Extract map back to assets
  80. if (result.map) {
  81. const processedMap = result.map.toString();
  82. compilation.assets[mapName] = {
  83. source: function() {
  84. return processedMap;
  85. },
  86. size: function() {
  87. return processedMap.length;
  88. },
  89. };
  90. }
  91. }
  92. ).catch(function(err) {
  93. hasErrors = true;
  94. throw new Error('CSS minification error: ' + err.message +
  95. '. File: ' + assetName);
  96. }
  97. );
  98. promises.push(promise);
  99. });
  100. Promise.all(promises)
  101. .then(function() {
  102. callback();
  103. })
  104. .catch(callback);
  105. });
  106. };
  107. module.exports = OptimizeCssnanoPlugin;