minify.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. "use strict";
  2. const {
  3. minify: terserMinify
  4. } = require('terser');
  5. const buildTerserOptions = ({
  6. ecma,
  7. warnings,
  8. parse = {},
  9. compress = {},
  10. mangle,
  11. module,
  12. output,
  13. toplevel,
  14. nameCache,
  15. ie8,
  16. /* eslint-disable camelcase */
  17. keep_classnames,
  18. keep_fnames,
  19. /* eslint-enable camelcase */
  20. safari10
  21. } = {}) => ({
  22. ecma,
  23. warnings,
  24. parse: { ...parse
  25. },
  26. compress: typeof compress === 'boolean' ? compress : { ...compress
  27. },
  28. // eslint-disable-next-line no-nested-ternary
  29. mangle: mangle == null ? true : typeof mangle === 'boolean' ? mangle : { ...mangle
  30. },
  31. output: {
  32. beautify: false,
  33. ...output
  34. },
  35. module,
  36. // Ignoring sourceMap from options
  37. sourceMap: null,
  38. toplevel,
  39. nameCache,
  40. ie8,
  41. keep_classnames,
  42. keep_fnames,
  43. safari10
  44. });
  45. function isObject(value) {
  46. const type = typeof value;
  47. return value != null && (type === 'object' || type === 'function');
  48. }
  49. const buildComments = (options, terserOptions, extractedComments) => {
  50. const condition = {};
  51. const commentsOpts = terserOptions.output.comments;
  52. const {
  53. extractComments
  54. } = options;
  55. condition.preserve = typeof commentsOpts !== 'undefined' ? commentsOpts : false;
  56. if (typeof extractComments === 'boolean' && extractComments) {
  57. condition.extract = 'some';
  58. } else if (typeof extractComments === 'string' || extractComments instanceof RegExp) {
  59. condition.extract = extractComments;
  60. } else if (typeof extractComments === 'function') {
  61. condition.extract = extractComments;
  62. } else if (isObject(extractComments)) {
  63. condition.extract = typeof extractComments.condition === 'boolean' && extractComments.condition ? 'some' : typeof extractComments.condition !== 'undefined' ? extractComments.condition : 'some';
  64. } else {
  65. // No extract
  66. // Preserve using "commentsOpts" or "some"
  67. condition.preserve = typeof commentsOpts !== 'undefined' ? commentsOpts : 'some';
  68. condition.extract = false;
  69. } // Ensure that both conditions are functions
  70. ['preserve', 'extract'].forEach(key => {
  71. let regexStr;
  72. let regex;
  73. switch (typeof condition[key]) {
  74. case 'boolean':
  75. condition[key] = condition[key] ? () => true : () => false;
  76. break;
  77. case 'function':
  78. break;
  79. case 'string':
  80. if (condition[key] === 'all') {
  81. condition[key] = () => true;
  82. break;
  83. }
  84. if (condition[key] === 'some') {
  85. condition[key] = (astNode, comment) => {
  86. return (comment.type === 'comment2' || comment.type === 'comment1') && /@preserve|@lic|@cc_on|^\**!/i.test(comment.value);
  87. };
  88. break;
  89. }
  90. regexStr = condition[key];
  91. condition[key] = (astNode, comment) => {
  92. return new RegExp(regexStr).test(comment.value);
  93. };
  94. break;
  95. default:
  96. regex = condition[key];
  97. condition[key] = (astNode, comment) => regex.test(comment.value);
  98. }
  99. }); // Redefine the comments function to extract and preserve
  100. // comments according to the two conditions
  101. return (astNode, comment) => {
  102. if (condition.extract(astNode, comment)) {
  103. const commentText = comment.type === 'comment2' ? `/*${comment.value}*/` : `//${comment.value}`; // Don't include duplicate comments
  104. if (!extractedComments.includes(commentText)) {
  105. extractedComments.push(commentText);
  106. }
  107. }
  108. return condition.preserve(astNode, comment);
  109. };
  110. };
  111. const minify = options => {
  112. const {
  113. file,
  114. input,
  115. inputSourceMap,
  116. minify: minifyFn
  117. } = options;
  118. if (minifyFn) {
  119. return minifyFn({
  120. [file]: input
  121. }, inputSourceMap);
  122. } // Copy terser options
  123. const terserOptions = buildTerserOptions(options.terserOptions); // Let terser generate a SourceMap
  124. if (inputSourceMap) {
  125. terserOptions.sourceMap = {
  126. asObject: true
  127. };
  128. }
  129. const extractedComments = [];
  130. terserOptions.output.comments = buildComments(options, terserOptions, extractedComments);
  131. const {
  132. error,
  133. map,
  134. code,
  135. warnings
  136. } = terserMinify({
  137. [file]: input
  138. }, terserOptions);
  139. return {
  140. error,
  141. map,
  142. code,
  143. warnings,
  144. extractedComments
  145. };
  146. };
  147. module.exports = minify;