index.js 12 KB

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