minify.js 4.5 KB

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