index.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. var _crypto = _interopRequireDefault(require("crypto"));
  7. var _path = _interopRequireDefault(require("path"));
  8. var _sourceMap = require("source-map");
  9. var _webpackSources = require("webpack-sources");
  10. var _RequestShortener = _interopRequireDefault(require("webpack/lib/RequestShortener"));
  11. var _ModuleFilenameHelpers = _interopRequireDefault(require("webpack/lib/ModuleFilenameHelpers"));
  12. var _schemaUtils = _interopRequireDefault(require("schema-utils"));
  13. var _serializeJavascript = _interopRequireDefault(require("serialize-javascript"));
  14. var _package = _interopRequireDefault(require("uglify-js/package.json"));
  15. var _options = _interopRequireDefault(require("./options.json"));
  16. var _TaskRunner = _interopRequireDefault(require("./TaskRunner"));
  17. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  18. function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
  19. function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
  20. function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
  21. const warningRegex = /\[.+:([0-9]+),([0-9]+)\]/;
  22. class UglifyJsPlugin {
  23. constructor(options = {}) {
  24. (0, _schemaUtils.default)(_options.default, options, 'UglifyJs Plugin');
  25. const {
  26. minify,
  27. uglifyOptions = {},
  28. test = /\.js(\?.*)?$/i,
  29. chunkFilter = () => true,
  30. warningsFilter = () => true,
  31. extractComments = false,
  32. sourceMap = false,
  33. cache = false,
  34. cacheKeys = defaultCacheKeys => defaultCacheKeys,
  35. parallel = false,
  36. include,
  37. exclude
  38. } = options;
  39. this.options = {
  40. test,
  41. chunkFilter,
  42. warningsFilter,
  43. extractComments,
  44. sourceMap,
  45. cache,
  46. cacheKeys,
  47. parallel,
  48. include,
  49. exclude,
  50. minify,
  51. uglifyOptions: _objectSpread({
  52. output: {
  53. comments: extractComments ? false : /^\**!|@preserve|@license|@cc_on/i
  54. }
  55. }, uglifyOptions)
  56. };
  57. }
  58. static isSourceMap(input) {
  59. // All required options for `new SourceMapConsumer(...options)`
  60. // https://github.com/mozilla/source-map#new-sourcemapconsumerrawsourcemap
  61. return Boolean(input && input.version && input.sources && Array.isArray(input.sources) && typeof input.mappings === 'string');
  62. }
  63. static buildSourceMap(inputSourceMap) {
  64. if (!inputSourceMap || !UglifyJsPlugin.isSourceMap(inputSourceMap)) {
  65. return null;
  66. }
  67. return new _sourceMap.SourceMapConsumer(inputSourceMap);
  68. }
  69. static buildError(err, file, sourceMap, requestShortener) {
  70. // Handling error which should have line, col, filename and message
  71. if (err.line) {
  72. const original = sourceMap && sourceMap.originalPositionFor({
  73. line: err.line,
  74. column: err.col
  75. });
  76. if (original && original.source && requestShortener) {
  77. return new Error(`${file} from UglifyJs\n${err.message} [${requestShortener.shorten(original.source)}:${original.line},${original.column}][${file}:${err.line},${err.col}]`);
  78. }
  79. return new Error(`${file} from UglifyJs\n${err.message} [${file}:${err.line},${err.col}]`);
  80. } else if (err.stack) {
  81. return new Error(`${file} from UglifyJs\n${err.stack}`);
  82. }
  83. return new Error(`${file} from UglifyJs\n${err.message}`);
  84. }
  85. static buildWarning(warning, file, sourceMap, requestShortener, warningsFilter) {
  86. let warningMessage = warning;
  87. let locationMessage = '';
  88. let source = null;
  89. if (sourceMap) {
  90. const match = warningRegex.exec(warning);
  91. if (match) {
  92. const line = +match[1];
  93. const column = +match[2];
  94. const original = sourceMap.originalPositionFor({
  95. line,
  96. column
  97. });
  98. if (original && original.source && original.source !== file && requestShortener) {
  99. ({
  100. source
  101. } = original);
  102. warningMessage = `${warningMessage.replace(warningRegex, '')}`;
  103. locationMessage = `[${requestShortener.shorten(original.source)}:${original.line},${original.column}]`;
  104. }
  105. }
  106. }
  107. if (warningsFilter && !warningsFilter(warning, source)) {
  108. return null;
  109. }
  110. return `UglifyJs Plugin: ${warningMessage}${locationMessage}`;
  111. }
  112. apply(compiler) {
  113. const buildModuleFn = moduleArg => {
  114. // to get detailed location info about errors
  115. moduleArg.useSourceMap = true;
  116. };
  117. const optimizeFn = (compilation, chunks, callback) => {
  118. const taskRunner = new _TaskRunner.default({
  119. cache: this.options.cache,
  120. parallel: this.options.parallel
  121. });
  122. const processedAssets = new WeakSet();
  123. const tasks = [];
  124. const {
  125. chunkFilter
  126. } = this.options;
  127. Array.from(chunks).filter(chunk => chunkFilter && chunkFilter(chunk)).reduce((acc, chunk) => acc.concat(chunk.files || []), []).concat(compilation.additionalChunkAssets || []).filter(_ModuleFilenameHelpers.default.matchObject.bind(null, this.options)).forEach(file => {
  128. let inputSourceMap;
  129. const asset = compilation.assets[file];
  130. if (processedAssets.has(asset)) {
  131. return;
  132. }
  133. try {
  134. let input;
  135. if (this.options.sourceMap && asset.sourceAndMap) {
  136. const {
  137. source,
  138. map
  139. } = asset.sourceAndMap();
  140. input = source;
  141. if (UglifyJsPlugin.isSourceMap(map)) {
  142. inputSourceMap = map;
  143. } else {
  144. inputSourceMap = map;
  145. compilation.warnings.push(new Error(`${file} contains invalid source map`));
  146. }
  147. } else {
  148. input = asset.source();
  149. inputSourceMap = null;
  150. } // Handling comment extraction
  151. let commentsFile = false;
  152. if (this.options.extractComments) {
  153. commentsFile = this.options.extractComments.filename || `${file}.LICENSE`;
  154. if (typeof commentsFile === 'function') {
  155. commentsFile = commentsFile(file);
  156. }
  157. }
  158. const task = {
  159. file,
  160. input,
  161. inputSourceMap,
  162. commentsFile,
  163. extractComments: this.options.extractComments,
  164. uglifyOptions: this.options.uglifyOptions,
  165. minify: this.options.minify
  166. };
  167. if (this.options.cache) {
  168. const defaultCacheKeys = {
  169. 'uglify-js': _package.default.version,
  170. node_version: process.version,
  171. // eslint-disable-next-line global-require
  172. 'uglifyjs-webpack-plugin': require('../package.json').version,
  173. 'uglifyjs-webpack-plugin-options': this.options,
  174. hash: _crypto.default.createHash('md4').update(input).digest('hex')
  175. };
  176. task.cacheKeys = this.options.cacheKeys(defaultCacheKeys, file);
  177. }
  178. tasks.push(task);
  179. } catch (error) {
  180. compilation.errors.push(UglifyJsPlugin.buildError(error, file, UglifyJsPlugin.buildSourceMap(inputSourceMap), new _RequestShortener.default(compiler.context)));
  181. }
  182. });
  183. taskRunner.run(tasks, (tasksError, results) => {
  184. if (tasksError) {
  185. compilation.errors.push(tasksError);
  186. return;
  187. }
  188. results.forEach((data, index) => {
  189. const {
  190. file,
  191. input,
  192. inputSourceMap,
  193. commentsFile
  194. } = tasks[index];
  195. const {
  196. error,
  197. map,
  198. code,
  199. warnings
  200. } = data;
  201. let {
  202. extractedComments
  203. } = data;
  204. let sourceMap = null;
  205. if (error || warnings && warnings.length > 0) {
  206. sourceMap = UglifyJsPlugin.buildSourceMap(inputSourceMap);
  207. } // Handling results
  208. // Error case: add errors, and go to next file
  209. if (error) {
  210. compilation.errors.push(UglifyJsPlugin.buildError(error, file, sourceMap, new _RequestShortener.default(compiler.context)));
  211. return;
  212. }
  213. let outputSource;
  214. if (map) {
  215. outputSource = new _webpackSources.SourceMapSource(code, file, JSON.parse(map), input, inputSourceMap, true);
  216. } else {
  217. outputSource = new _webpackSources.RawSource(code);
  218. } // Write extracted comments to commentsFile
  219. if (commentsFile && extractedComments && extractedComments.length > 0) {
  220. if (commentsFile in compilation.assets) {
  221. const commentsFileSource = compilation.assets[commentsFile].source();
  222. extractedComments = extractedComments.filter(comment => !commentsFileSource.includes(comment));
  223. }
  224. if (extractedComments.length > 0) {
  225. // Add a banner to the original file
  226. if (this.options.extractComments.banner !== false) {
  227. let banner = this.options.extractComments.banner || `For license information please see ${_path.default.posix.basename(commentsFile)}`;
  228. if (typeof banner === 'function') {
  229. banner = banner(commentsFile);
  230. }
  231. if (banner) {
  232. outputSource = new _webpackSources.ConcatSource(`/*! ${banner} */\n`, outputSource);
  233. }
  234. }
  235. const commentsSource = new _webpackSources.RawSource(`${extractedComments.join('\n\n')}\n`);
  236. if (commentsFile in compilation.assets) {
  237. // commentsFile already exists, append new comments...
  238. if (compilation.assets[commentsFile] instanceof _webpackSources.ConcatSource) {
  239. compilation.assets[commentsFile].add('\n');
  240. compilation.assets[commentsFile].add(commentsSource);
  241. } else {
  242. compilation.assets[commentsFile] = new _webpackSources.ConcatSource(compilation.assets[commentsFile], '\n', commentsSource);
  243. }
  244. } else {
  245. compilation.assets[commentsFile] = commentsSource;
  246. }
  247. }
  248. } // Updating assets
  249. processedAssets.add(compilation.assets[file] = outputSource); // Handling warnings
  250. if (warnings && warnings.length > 0) {
  251. warnings.forEach(warning => {
  252. const builtWarning = UglifyJsPlugin.buildWarning(warning, file, sourceMap, new _RequestShortener.default(compiler.context), this.options.warningsFilter);
  253. if (builtWarning) {
  254. compilation.warnings.push(builtWarning);
  255. }
  256. });
  257. }
  258. });
  259. taskRunner.exit();
  260. callback();
  261. });
  262. };
  263. const plugin = {
  264. name: this.constructor.name
  265. };
  266. compiler.hooks.compilation.tap(plugin, compilation => {
  267. if (this.options.sourceMap) {
  268. compilation.hooks.buildModule.tap(plugin, buildModuleFn);
  269. }
  270. const {
  271. mainTemplate,
  272. chunkTemplate
  273. } = compilation; // Regenerate `contenthash` for minified assets
  274. for (const template of [mainTemplate, chunkTemplate]) {
  275. template.hooks.hashForChunk.tap(plugin, hash => {
  276. const data = (0, _serializeJavascript.default)({
  277. uglifyjs: _package.default.version,
  278. uglifyjsOptions: this.options.uglifyOptions
  279. });
  280. hash.update('UglifyJsPlugin');
  281. hash.update(data);
  282. });
  283. }
  284. compilation.hooks.optimizeChunkAssets.tapAsync(plugin, optimizeFn.bind(this, compilation));
  285. });
  286. }
  287. }
  288. var _default = UglifyJsPlugin;
  289. exports.default = _default;