|
|
"use strict";
Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0;
var _path = _interopRequireDefault(require("path"));
var _os = _interopRequireDefault(require("os"));
var _sourceMap = require("source-map");
var _webpack = _interopRequireWildcard(require("webpack"));
var _RequestShortener = _interopRequireDefault(require("webpack/lib/RequestShortener"));
var _schemaUtils = require("schema-utils");
var _serializeJavascript = _interopRequireDefault(require("serialize-javascript"));
var _package = _interopRequireDefault(require("terser/package.json"));
var _pLimit = _interopRequireDefault(require("p-limit"));
var _jestWorker = _interopRequireDefault(require("jest-worker"));
var _options = _interopRequireDefault(require("./options.json"));
var _minify = require("./minify");
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// webpack 5 exposes the sources property to ensure the right version of webpack-sources is used
const { SourceMapSource, RawSource, ConcatSource } = // eslint-disable-next-line global-require
_webpack.default.sources || require('webpack-sources');
class TerserPlugin { constructor(options = {}) { (0, _schemaUtils.validate)(_options.default, options, { name: 'Terser Plugin', baseDataPath: 'options' }); const { minify, terserOptions = {}, test = /\.[cm]?js(\?.*)?$/i, extractComments = true, sourceMap, cache = true, cacheKeys = defaultCacheKeys => defaultCacheKeys, parallel = true, include, exclude } = options; this.options = { test, extractComments, sourceMap, cache, cacheKeys, parallel, include, exclude, minify, terserOptions }; }
static isSourceMap(input) { // All required options for `new SourceMapConsumer(...options)`
// https://github.com/mozilla/source-map#new-sourcemapconsumerrawsourcemap
return Boolean(input && input.version && input.sources && Array.isArray(input.sources) && typeof input.mappings === 'string'); }
static buildError(error, file, sourceMap, requestShortener) { if (error.line) { const original = sourceMap && sourceMap.originalPositionFor({ line: error.line, column: error.col });
if (original && original.source && requestShortener) { return new Error(`${file} from Terser\n${error.message} [${requestShortener.shorten(original.source)}:${original.line},${original.column}][${file}:${error.line},${error.col}]${error.stack ? `\n${error.stack.split('\n').slice(1).join('\n')}` : ''}`); }
return new Error(`${file} from Terser\n${error.message} [${file}:${error.line},${error.col}]${error.stack ? `\n${error.stack.split('\n').slice(1).join('\n')}` : ''}`); }
if (error.stack) { return new Error(`${file} from Terser\n${error.stack}`); }
return new Error(`${file} from Terser\n${error.message}`); }
static isWebpack4() { return _webpack.version[0] === '4'; }
static getAvailableNumberOfCores(parallel) { // In some cases cpus() returns undefined
// https://github.com/nodejs/node/issues/19022
const cpus = _os.default.cpus() || { length: 1 }; return parallel === true ? cpus.length - 1 : Math.min(Number(parallel) || 0, cpus.length - 1); } // eslint-disable-next-line consistent-return
static getAsset(compilation, name) { // New API
if (compilation.getAsset) { return compilation.getAsset(name); } /* istanbul ignore next */
if (compilation.assets[name]) { return { name, source: compilation.assets[name], info: {} }; } }
static emitAsset(compilation, name, source, assetInfo) { // New API
if (compilation.emitAsset) { compilation.emitAsset(name, source, assetInfo); } // eslint-disable-next-line no-param-reassign
compilation.assets[name] = source; }
static updateAsset(compilation, name, newSource, assetInfo) { // New API
if (compilation.updateAsset) { compilation.updateAsset(name, newSource, assetInfo); } // eslint-disable-next-line no-param-reassign
compilation.assets[name] = newSource; }
async optimize(compiler, compilation, assets, CacheEngine, weakCache) { let assetNames;
if (TerserPlugin.isWebpack4()) { assetNames = [].concat(Array.from(compilation.additionalChunkAssets || [])).concat( // In webpack@4 it is `chunks`
Array.from(assets).reduce((acc, chunk) => acc.concat(Array.from(chunk.files || [])), [])).concat(Object.keys(compilation.assets)).filter((assetName, index, existingAssets) => existingAssets.indexOf(assetName) === index).filter(assetName => _webpack.ModuleFilenameHelpers.matchObject.bind( // eslint-disable-next-line no-undefined
undefined, this.options)(assetName)); } else { assetNames = Object.keys(assets).filter(assetName => _webpack.ModuleFilenameHelpers.matchObject.bind( // eslint-disable-next-line no-undefined
undefined, this.options)(assetName)); }
if (assetNames.length === 0) { return; }
const availableNumberOfCores = TerserPlugin.getAvailableNumberOfCores(this.options.parallel); let concurrency = Infinity; let worker;
if (availableNumberOfCores > 0) { // Do not create unnecessary workers when the number of files is less than the available cores, it saves memory
const numWorkers = Math.min(assetNames.length, availableNumberOfCores); concurrency = numWorkers; worker = new _jestWorker.default(require.resolve('./minify'), { numWorkers }); // https://github.com/facebook/jest/issues/8872#issuecomment-524822081
const workerStdout = worker.getStdout();
if (workerStdout) { workerStdout.on('data', chunk => { return process.stdout.write(chunk); }); }
const workerStderr = worker.getStderr();
if (workerStderr) { workerStderr.on('data', chunk => { return process.stderr.write(chunk); }); } }
const limit = (0, _pLimit.default)(concurrency); const cache = new CacheEngine(compilation, { cache: this.options.cache }, weakCache); const allExtractedComments = new Map(); const scheduledTasks = [];
for (const name of assetNames) { scheduledTasks.push(limit(async () => { const { info, source: inputSource } = TerserPlugin.getAsset(compilation, name); // Skip double minimize assets from child compilation
if (info.minimized) { return; }
let input; let inputSourceMap; // TODO refactor after drop webpack@4, webpack@5 always has `sourceAndMap` on sources
if (this.options.sourceMap && inputSource.sourceAndMap) { const { source, map } = inputSource.sourceAndMap(); input = source;
if (map) { if (TerserPlugin.isSourceMap(map)) { inputSourceMap = map; } else { inputSourceMap = map; compilation.warnings.push(new Error(`${name} contains invalid source map`)); } } } else { input = inputSource.source(); inputSourceMap = null; }
if (Buffer.isBuffer(input)) { input = input.toString(); }
const cacheData = { name, inputSource };
if (TerserPlugin.isWebpack4()) { if (this.options.cache) { const { outputOptions: { hashSalt, hashDigest, hashDigestLength, hashFunction } } = compilation;
const hash = _webpack.util.createHash(hashFunction);
if (hashSalt) { hash.update(hashSalt); }
hash.update(input); const digest = hash.digest(hashDigest); cacheData.input = input; cacheData.inputSourceMap = inputSourceMap; cacheData.cacheKeys = this.options.cacheKeys({ terser: _package.default.version, // eslint-disable-next-line global-require
'terser-webpack-plugin': require('../package.json').version, 'terser-webpack-plugin-options': this.options, name, contentHash: digest.substr(0, hashDigestLength) }, name); } }
let output = await cache.get(cacheData, { RawSource, ConcatSource, SourceMapSource });
if (!output) { const minimizerOptions = { name, input, inputSourceMap, minify: this.options.minify, minimizerOptions: this.options.terserOptions, extractComments: this.options.extractComments };
if (/\.mjs(\?.*)?$/i.test(name)) { this.options.terserOptions.module = true; }
try { output = await (worker ? worker.transform((0, _serializeJavascript.default)(minimizerOptions)) : (0, _minify.minify)(minimizerOptions)); } catch (error) { compilation.errors.push(TerserPlugin.buildError(error, name, inputSourceMap && TerserPlugin.isSourceMap(inputSourceMap) ? new _sourceMap.SourceMapConsumer(inputSourceMap) : null, new _RequestShortener.default(compiler.context))); return; }
let shebang;
if (this.options.extractComments.banner !== false && output.extractedComments && output.extractedComments.length > 0 && output.code.startsWith('#!')) { const firstNewlinePosition = output.code.indexOf('\n'); shebang = output.code.substring(0, firstNewlinePosition); output.code = output.code.substring(firstNewlinePosition + 1); }
if (output.map) { output.source = new SourceMapSource(output.code, name, output.map, input, inputSourceMap, true); } else { output.source = new RawSource(output.code); }
let commentsFilename;
if (output.extractedComments && output.extractedComments.length > 0) { commentsFilename = this.options.extractComments.filename || '[file].LICENSE.txt[query]'; let query = ''; let filename = name; const querySplit = filename.indexOf('?');
if (querySplit >= 0) { query = filename.substr(querySplit); filename = filename.substr(0, querySplit); }
const lastSlashIndex = filename.lastIndexOf('/'); const basename = lastSlashIndex === -1 ? filename : filename.substr(lastSlashIndex + 1); const data = { filename, basename, query }; commentsFilename = compilation.getPath(commentsFilename, data); output.commentsFilename = commentsFilename; let banner; // Add a banner to the original file
if (this.options.extractComments.banner !== false) { banner = this.options.extractComments.banner || `For license information please see ${_path.default.relative(_path.default.dirname(name), commentsFilename).replace(/\\/g, '/')}`;
if (typeof banner === 'function') { banner = banner(commentsFilename); }
if (banner) { output.source = new ConcatSource(shebang ? `${shebang}\n` : '', `/*! ${banner} */\n`, output.source); output.banner = banner; output.shebang = shebang; } }
const extractedCommentsString = output.extractedComments.sort().join('\n\n'); output.extractedCommentsSource = new RawSource(`${extractedCommentsString}\n`); }
await cache.store({ ...output, ...cacheData }); } // TODO `...` required only for webpack@4
const newInfo = { ...info, minimized: true }; const { source, extractedCommentsSource } = output; // Write extracted comments to commentsFilename
if (extractedCommentsSource) { const { commentsFilename } = output; // TODO `...` required only for webpack@4
newInfo.related = { license: commentsFilename, ...info.related }; allExtractedComments.set(name, { extractedCommentsSource, commentsFilename }); }
TerserPlugin.updateAsset(compilation, name, source, newInfo); })); }
await Promise.all(scheduledTasks);
if (worker) { await worker.end(); }
await Array.from(allExtractedComments).sort().reduce(async (previousPromise, [from, value]) => { const previous = await previousPromise; const { commentsFilename, extractedCommentsSource } = value;
if (previous && previous.commentsFilename === commentsFilename) { const { from: previousFrom, source: prevSource } = previous; const mergedName = `${previousFrom}|${from}`; const cacheData = { target: 'comments' };
if (TerserPlugin.isWebpack4()) { const { outputOptions: { hashSalt, hashDigest, hashDigestLength, hashFunction } } = compilation;
const previousHash = _webpack.util.createHash(hashFunction);
const hash = _webpack.util.createHash(hashFunction);
if (hashSalt) { previousHash.update(hashSalt); hash.update(hashSalt); }
previousHash.update(prevSource.source()); hash.update(extractedCommentsSource.source()); const previousDigest = previousHash.digest(hashDigest); const digest = hash.digest(hashDigest); cacheData.cacheKeys = { mergedName, previousContentHash: previousDigest.substr(0, hashDigestLength), contentHash: digest.substr(0, hashDigestLength) }; cacheData.inputSource = extractedCommentsSource; } else { const mergedInputSource = [prevSource, extractedCommentsSource]; cacheData.name = `${commentsFilename}|${mergedName}`; cacheData.inputSource = mergedInputSource; }
let output = await cache.get(cacheData, { ConcatSource });
if (!output) { output = new ConcatSource(Array.from(new Set([...prevSource.source().split('\n\n'), ...extractedCommentsSource.source().split('\n\n')])).join('\n\n')); await cache.store({ ...cacheData, output }); }
TerserPlugin.updateAsset(compilation, commentsFilename, output); return { commentsFilename, from: mergedName, source: output }; }
const existingAsset = TerserPlugin.getAsset(compilation, commentsFilename);
if (existingAsset) { return { commentsFilename, from: commentsFilename, source: existingAsset.source }; }
TerserPlugin.emitAsset(compilation, commentsFilename, extractedCommentsSource); return { commentsFilename, from, source: extractedCommentsSource }; }, Promise.resolve()); }
static getEcmaVersion(environment) { // ES 6th
if (environment.arrowFunction || environment.const || environment.destructuring || environment.forOf || environment.module) { return 2015; } // ES 11th
if (environment.bigIntLiteral || environment.dynamicImport) { return 2020; }
return 5; }
apply(compiler) { const { devtool, output, plugins } = compiler.options; this.options.sourceMap = typeof this.options.sourceMap === 'undefined' ? devtool && !devtool.includes('eval') && !devtool.includes('cheap') && (devtool.includes('source-map') || // Todo remove when `webpack@4` support will be dropped
devtool.includes('sourcemap')) || plugins && plugins.some(plugin => plugin instanceof _webpack.SourceMapDevToolPlugin && plugin.options && plugin.options.columns) : Boolean(this.options.sourceMap);
if (typeof this.options.terserOptions.module === 'undefined' && typeof output.module !== 'undefined') { this.options.terserOptions.module = output.module; }
if (typeof this.options.terserOptions.ecma === 'undefined') { this.options.terserOptions.ecma = TerserPlugin.getEcmaVersion(output.environment || {}); }
const pluginName = this.constructor.name; const weakCache = TerserPlugin.isWebpack4() && (this.options.cache === true || typeof this.options.cache === 'string') ? new WeakMap() : // eslint-disable-next-line no-undefined
undefined; compiler.hooks.compilation.tap(pluginName, compilation => { if (this.options.sourceMap) { compilation.hooks.buildModule.tap(pluginName, moduleArg => { // to get detailed location info about errors
// eslint-disable-next-line no-param-reassign
moduleArg.useSourceMap = true; }); }
if (TerserPlugin.isWebpack4()) { // eslint-disable-next-line global-require
const CacheEngine = require('./Webpack4Cache').default;
const { mainTemplate, chunkTemplate } = compilation; const data = (0, _serializeJavascript.default)({ terser: _package.default.version, terserOptions: this.options.terserOptions }); // Regenerate `contenthash` for minified assets
for (const template of [mainTemplate, chunkTemplate]) { template.hooks.hashForChunk.tap(pluginName, hash => { hash.update('TerserPlugin'); hash.update(data); }); }
compilation.hooks.optimizeChunkAssets.tapPromise(pluginName, assets => this.optimize(compiler, compilation, assets, CacheEngine, weakCache)); } else { // eslint-disable-next-line global-require
const CacheEngine = require('./Webpack5Cache').default; // eslint-disable-next-line global-require
const Compilation = require('webpack/lib/Compilation');
const hooks = _webpack.javascript.JavascriptModulesPlugin.getCompilationHooks(compilation);
const data = (0, _serializeJavascript.default)({ terser: _package.default.version, terserOptions: this.options.terserOptions }); hooks.chunkHash.tap(pluginName, (chunk, hash) => { hash.update('TerserPlugin'); hash.update(data); }); compilation.hooks.processAssets.tapPromise({ name: pluginName, stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE }, assets => this.optimize(compiler, compilation, assets, CacheEngine)); compilation.hooks.statsPrinter.tap(pluginName, stats => { stats.hooks.print.for('asset.info.minimized').tap('terser-webpack-plugin', (minimized, { green, formatFlag }) => // eslint-disable-next-line no-undefined
minimized ? green(formatFlag('minimized')) : undefined); }); } }); }
}
var _default = TerserPlugin; exports.default = _default;
|