d1351ad61cc522785af49e0ad344b136c1abdbabcf27faa4b6bea4c4556adea0a80a50ccc2db3b31d834fbbb80d0d679339fad1a3b3575e4d5e997bca32d51 13 KB

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