fileMinifier.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. var debug = require('debug')('ylt:fileMinifier');
  2. var Q = require('q');
  3. var UglifyJS = require('uglify-js');
  4. var CleanCSS = require('clean-css');
  5. var Minimize = require('minimize');
  6. var FileMinifier = function() {
  7. function minifyFile(entry) {
  8. var deferred = Q.defer();
  9. if (!entry.weightCheck || !entry.weightCheck.body) {
  10. // No valid file available
  11. deferred.resolve(entry);
  12. return deferred.promise;
  13. }
  14. var fileSize = entry.weightCheck.uncompressedSize;
  15. debug('Let\'s try to optimize %s', entry.url);
  16. debug('Current file size is %d', fileSize);
  17. if (entry.isJS) {
  18. debug('File is a JS');
  19. // Starting softly with a lossless compression
  20. return minifyJs(entry.weightCheck.body)
  21. .then(function(newFile) {
  22. if (!newFile) {
  23. debug('Optimization didn\'t work');
  24. return entry;
  25. }
  26. var newFileSize = newFile.length;
  27. debug('JS minification complete for %s', entry.url);
  28. if (gainIsEnough(fileSize, newFileSize)) {
  29. entry.weightCheck.minified = newFileSize;
  30. entry.weightCheck.isMinified = false;
  31. debug('Filesize is %d bytes smaller (-%d%)', fileSize - newFileSize, Math.round((fileSize - newFileSize) * 100 / fileSize));
  32. }
  33. return entry;
  34. })
  35. .fail(function(err) {
  36. return entry;
  37. });
  38. } else if (entry.isCSS) {
  39. debug('File is a CSS');
  40. // Starting softly with a lossless compression
  41. return minifyCss(entry.weightCheck.body)
  42. .then(function(newFile) {
  43. if (!newFile) {
  44. debug('Optimization didn\'t work');
  45. return entry;
  46. }
  47. var newFileSize = newFile.length;
  48. debug('CSS minification complete for %s', entry.url);
  49. if (gainIsEnough(fileSize, newFileSize)) {
  50. entry.weightCheck.minified = newFileSize;
  51. entry.weightCheck.isMinified = false;
  52. debug('Filesize is %d bytes smaller (-%d%)', fileSize - newFileSize, Math.round((fileSize - newFileSize) * 100 / fileSize));
  53. }
  54. return entry;
  55. })
  56. .fail(function(err) {
  57. return entry;
  58. });
  59. } else if (entry.isHTML) {
  60. debug('File is an HTML');
  61. // Starting softly with a lossless compression
  62. return minifyHtml(entry.weightCheck.body)
  63. .then(function(newFile) {
  64. if (!newFile) {
  65. debug('Optimization didn\'t work');
  66. return entry;
  67. }
  68. var newFileSize = newFile.length;
  69. debug('HTML minification complete for %s', entry.url);
  70. if (gainIsEnough(fileSize, newFileSize)) {
  71. entry.weightCheck.minified = newFileSize;
  72. entry.weightCheck.isMinified = false;
  73. debug('Filesize is %d bytes smaller (-%d%)', fileSize - newFileSize, Math.round((fileSize - newFileSize) * 100 / fileSize));
  74. }
  75. return entry;
  76. })
  77. .fail(function(err) {
  78. return entry;
  79. });
  80. } else {
  81. debug('File type %s is not an (optimizable) image', entry.contentType);
  82. deferred.resolve(entry);
  83. }
  84. return deferred.promise;
  85. }
  86. // The gain is estimated of enough value if it's over 2KB or over 20%,
  87. // but it's ignored if is below 400 bytes
  88. function gainIsEnough(oldWeight, newWeight) {
  89. var gain = oldWeight - newWeight;
  90. var ratio = gain / oldWeight;
  91. return (gain > 2096 || (ratio > 0.2 && gain > 400));
  92. }
  93. // Uglify
  94. function minifyJs(body) {
  95. var deferred = Q.defer();
  96. try {
  97. var result = UglifyJS.minify(body, {fromString: true});
  98. deferred.resolve(result.code);
  99. } catch(err) {
  100. deferred.reject(err);
  101. }
  102. return deferred.promise;
  103. }
  104. // Clear-css
  105. function minifyCss(body) {
  106. var deferred = Q.defer();
  107. try {
  108. var result = new CleanCSS({compatibility: 'ie8'}).minify(body);
  109. deferred.resolve(result.styles);
  110. } catch(err) {
  111. deferred.reject(err);
  112. }
  113. return deferred.promise;
  114. }
  115. // HTMLMinifier
  116. function minifyHtml(body) {
  117. var deferred = Q.defer();
  118. var minimize = new Minimize({
  119. empty: true, // KEEP empty attributes
  120. conditionals: true, // KEEP conditional internet explorer comments
  121. spare: true // KEEP redundant attributes
  122. });
  123. minimize.parse(body, function (error, data) {
  124. if (error) {
  125. deferred.reject(error);
  126. } else {
  127. deferred.resolve(data);
  128. }
  129. });
  130. return deferred.promise;
  131. }
  132. return {
  133. minifyFile: minifyFile,
  134. minifyJs: minifyJs,
  135. minifyCss: minifyCss,
  136. minifyHtml: minifyHtml,
  137. gainIsEnough: gainIsEnough
  138. };
  139. };
  140. module.exports = new FileMinifier();