1
0

uglify.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. /*
  2. * grunt-contrib-uglify
  3. * https://gruntjs.com/
  4. *
  5. * Copyright (c) 2016 "Cowboy" Ben Alman, contributors
  6. * Licensed under the MIT license.
  7. */
  8. 'use strict';
  9. // External libs.
  10. var path = require('path');
  11. var UglifyJS = require('uglify-js');
  12. var _ = require('lodash');
  13. var uriPath = require('uri-path');
  14. var getOutputOptions;
  15. exports.init = function(grunt) {
  16. var exports = {};
  17. // Minify with UglifyJS.
  18. // From https://github.com/mishoo/UglifyJS2
  19. // API docs at http://lisperator.net/uglifyjs/
  20. exports.minify = function(files, dest, options) {
  21. options = options || {};
  22. grunt.verbose.write('Minifying with UglifyJS...');
  23. var topLevel = null;
  24. var totalCode = '';
  25. var sourcesContent = {};
  26. var outputOptions = getOutputOptions(options, dest);
  27. var output = UglifyJS.OutputStream(outputOptions);
  28. // Grab and parse all source files
  29. files.forEach(function(file) {
  30. var code = grunt.file.read(file);
  31. totalCode += code;
  32. // The src file name must be relative to the source map for things to work
  33. var basename = path.basename(file);
  34. var fileDir = path.dirname(file);
  35. var sourceMapDir = path.dirname(options.generatedSourceMapName);
  36. var relativePath = path.relative(sourceMapDir, fileDir);
  37. var pathPrefix = relativePath ? relativePath + path.sep : '';
  38. var bare_returns = options.bare_returns || undefined;
  39. // Convert paths to use forward slashes for sourcemap use in the browser
  40. file = uriPath(pathPrefix + basename);
  41. sourcesContent[file] = code;
  42. topLevel = UglifyJS.parse(code, {
  43. filename: file,
  44. toplevel: topLevel,
  45. expression: options.expression,
  46. bare_returns: bare_returns
  47. });
  48. });
  49. // Wrap code in a common js wrapper.
  50. if (options.wrap) {
  51. topLevel = topLevel.wrap_commonjs(options.wrap, options.exportAll);
  52. }
  53. // Wrap code in closure with configurable arguments/parameters list.
  54. if (options.enclose) {
  55. var argParamList = _.map(options.enclose, function(val, key) {
  56. return key + ':' + val;
  57. });
  58. topLevel = topLevel.wrap_enclose(argParamList);
  59. }
  60. var topLevelCache = null;
  61. if (options.nameCache) {
  62. topLevelCache = UglifyJS.readNameCache(options.nameCache, 'vars');
  63. }
  64. // Need to call this before we mangle or compress,
  65. // and call after any compression or ast altering
  66. if (options.expression === false) {
  67. topLevel.figure_out_scope({ screw_ie8: options.screwIE8, cache: topLevelCache });
  68. }
  69. if (options.compress !== false) {
  70. if (options.compress === true) {
  71. options.compress = {};
  72. }
  73. if (options.compress.warnings !== true) {
  74. options.compress.warnings = false;
  75. }
  76. if (options.screwIE8) {
  77. options.compress.screw_ie8 = true;
  78. }
  79. var compressor = UglifyJS.Compressor(options.compress);
  80. topLevel = topLevel.transform(compressor);
  81. // Need to figure out scope again after source being altered
  82. if (options.expression === false) {
  83. topLevel.figure_out_scope({screw_ie8: options.screwIE8, cache: topLevelCache});
  84. }
  85. }
  86. var mangleExclusions = { vars: [], props: [] };
  87. if (options.reserveDOMProperties) {
  88. mangleExclusions = UglifyJS.readDefaultReservedFile();
  89. }
  90. if (options.exceptionsFiles) {
  91. try {
  92. options.exceptionsFiles.forEach(function(filename) {
  93. mangleExclusions = UglifyJS.readReservedFile(filename, mangleExclusions);
  94. });
  95. } catch (ex) {
  96. grunt.warn(ex);
  97. }
  98. }
  99. var cache = null;
  100. if (options.nameCache) {
  101. cache = UglifyJS.readNameCache(options.nameCache, 'props');
  102. }
  103. if (typeof(options.mangleProperties) !== 'undefined' && options.mangleProperties !== false) {
  104. // if options.mangleProperties is a boolean (true) convert it into an object
  105. if (typeof options.mangleProperties !== 'object') {
  106. options.mangleProperties = {};
  107. }
  108. options.mangleProperties.reserved = mangleExclusions ? mangleExclusions.props : null;
  109. options.mangleProperties.cache = cache;
  110. topLevel = UglifyJS.mangle_properties(topLevel, options.mangleProperties);
  111. if (options.nameCache) {
  112. UglifyJS.writeNameCache(options.nameCache, 'props', cache);
  113. }
  114. // Need to figure out scope again since topLevel has been altered
  115. if (options.expression === false) {
  116. topLevel.figure_out_scope({screw_ie8: options.screwIE8, cache: topLevelCache});
  117. }
  118. }
  119. if (options.mangle !== false) {
  120. if (options.mangle === true) {
  121. options.mangle = {};
  122. }
  123. if (options.screwIE8) {
  124. options.mangle.screw_ie8 = true;
  125. }
  126. // disabled due to:
  127. // 1) preserve stable name mangling
  128. // 2) it increases gzipped file size, see https://github.com/mishoo/UglifyJS2#mangler-options
  129. // // compute_char_frequency optimizes names for compression
  130. // topLevel.compute_char_frequency(options.mangle);
  131. // if options.mangle is a boolean (true) convert it into an object
  132. if (typeof options.mangle !== 'object') {
  133. options.mangle = {};
  134. }
  135. options.mangle.cache = topLevelCache;
  136. options.mangle.except = options.mangle.except ? options.mangle.except : [];
  137. if (mangleExclusions.vars) {
  138. mangleExclusions.vars.forEach(function(name) {
  139. UglifyJS.push_uniq(options.mangle.except, name);
  140. });
  141. }
  142. // Requires previous call to figure_out_scope
  143. // and should always be called after compressor transform
  144. topLevel.mangle_names(options.mangle);
  145. UglifyJS.writeNameCache(options.nameCache, 'vars', options.mangle.cache);
  146. }
  147. if (options.sourceMap && options.sourceMapIncludeSources) {
  148. for (var file in sourcesContent) {
  149. if (sourcesContent.hasOwnProperty(file)) {
  150. outputOptions.source_map.get().setSourceContent(file, sourcesContent[file]);
  151. }
  152. }
  153. }
  154. // Print the ast to OutputStream
  155. topLevel.print(output);
  156. var min = output.get();
  157. // Add the source map reference to the end of the file
  158. if (options.sourceMap) {
  159. // Set all paths to forward slashes for use in the browser
  160. var sourceMappingURL;
  161. sourceMappingURL = options.destToSourceMap.match(/^http[s]?\:\/\//) === null ? uriPath(options.destToSourceMap) : options.destToSourceMap;
  162. min += '\n//# sourceMappingURL=' + sourceMappingURL;
  163. }
  164. var result = {
  165. max: totalCode,
  166. min: min,
  167. sourceMap: outputOptions.source_map
  168. };
  169. grunt.verbose.ok();
  170. return result;
  171. };
  172. getOutputOptions = function(options, dest) {
  173. var outputOptions = {
  174. beautify: false,
  175. source_map: null
  176. };
  177. if (options.banner && options.sourceMap) {
  178. outputOptions.preamble = options.banner;
  179. }
  180. if (options.screwIE8) {
  181. outputOptions.screw_ie8 = true;
  182. }
  183. if (options.sourceMap) {
  184. var destBasename = path.basename(dest);
  185. var sourceMapIn;
  186. if (options.sourceMapIn) {
  187. sourceMapIn = grunt.file.readJSON(options.sourceMapIn);
  188. }
  189. outputOptions.source_map = UglifyJS.SourceMap({
  190. file: destBasename,
  191. root: options.sourceMapRoot,
  192. orig: sourceMapIn
  193. });
  194. if (options.sourceMapIncludeSources && sourceMapIn && sourceMapIn.sourcesContent) {
  195. sourceMapIn.sourcesContent.forEach(function(content, idx) {
  196. outputOptions.source_map.get().setSourceContent(sourceMapIn.sources[idx], content);
  197. });
  198. }
  199. if (options.sourceMapIn) {
  200. outputOptions.source_map.get()._file = destBasename;
  201. }
  202. }
  203. if (!_.isUndefined(options.indentLevel)) {
  204. outputOptions.indent_level = options.indentLevel;
  205. }
  206. if (!_.isUndefined(options.maxLineLen)) {
  207. outputOptions.max_line_len = options.maxLineLen;
  208. }
  209. if (!_.isUndefined(options.ASCIIOnly)) {
  210. outputOptions.ascii_only = options.ASCIIOnly;
  211. }
  212. if (!_.isUndefined(options.quoteStyle)) {
  213. outputOptions.quote_style = options.quoteStyle;
  214. }
  215. if (!_.isUndefined(options.preserveComments)) {
  216. outputOptions.comments = options.preserveComments;
  217. }
  218. if (options.beautify) {
  219. if (_.isObject(options.beautify)) {
  220. _.assign(outputOptions, options.beautify);
  221. } else {
  222. outputOptions.beautify = true;
  223. }
  224. }
  225. return outputOptions;
  226. };
  227. return exports;
  228. };