index.js 13.9 KB
'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* eslint-disable
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       no-param-reassign
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     */


var _crypto = require('crypto');

var _crypto2 = _interopRequireDefault(_crypto);

var _path = require('path');

var _path2 = _interopRequireDefault(_path);

var _sourceMap = require('source-map');

var _webpackSources = require('webpack-sources');

var _RequestShortener = require('webpack/lib/RequestShortener');

var _RequestShortener2 = _interopRequireDefault(_RequestShortener);

var _ModuleFilenameHelpers = require('webpack/lib/ModuleFilenameHelpers');

var _ModuleFilenameHelpers2 = _interopRequireDefault(_ModuleFilenameHelpers);

var _schemaUtils = require('schema-utils');

var _schemaUtils2 = _interopRequireDefault(_schemaUtils);

var _serializeJavascript = require('serialize-javascript');

var _serializeJavascript2 = _interopRequireDefault(_serializeJavascript);

var _options = require('./options.json');

var _options2 = _interopRequireDefault(_options);

var _uglify = require('./uglify');

var _uglify2 = _interopRequireDefault(_uglify);

var _versions = require('./uglify/versions');

var _versions2 = _interopRequireDefault(_versions);

var _utils = require('./utils');

var _utils2 = _interopRequireDefault(_utils);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var warningRegex = /\[.+:([0-9]+),([0-9]+)\]/;

var UglifyJsPlugin = function () {
  function UglifyJsPlugin() {
    var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

    _classCallCheck(this, UglifyJsPlugin);

    (0, _schemaUtils2.default)(_options2.default, options, 'UglifyJs Plugin');

    var _options$uglifyOption = options.uglifyOptions,
        uglifyOptions = _options$uglifyOption === undefined ? {} : _options$uglifyOption,
        _options$test = options.test,
        test = _options$test === undefined ? /\.js(\?.*)?$/i : _options$test,
        _options$warningsFilt = options.warningsFilter,
        warningsFilter = _options$warningsFilt === undefined ? function () {
      return true;
    } : _options$warningsFilt,
        _options$extractComme = options.extractComments,
        extractComments = _options$extractComme === undefined ? false : _options$extractComme,
        _options$sourceMap = options.sourceMap,
        sourceMap = _options$sourceMap === undefined ? false : _options$sourceMap,
        _options$cache = options.cache,
        cache = _options$cache === undefined ? false : _options$cache,
        _options$parallel = options.parallel,
        parallel = _options$parallel === undefined ? false : _options$parallel,
        include = options.include,
        exclude = options.exclude;


    this.options = {
      test,
      warningsFilter,
      extractComments,
      sourceMap,
      cache,
      parallel,
      include,
      exclude,
      uglifyOptions: Object.assign({
        compress: {
          inline: 1
        },
        output: {
          comments: extractComments ? false : /^\**!|@preserve|@license|@cc_on/
        }
      }, uglifyOptions)
    };
  }

  _createClass(UglifyJsPlugin, [{
    key: 'apply',
    value: function apply(compiler) {
      var _this = this;

      var requestShortener = new _RequestShortener2.default(compiler.context);

      var buildModuleFn = function buildModuleFn(moduleArg) {
        // to get detailed location info about errors
        moduleArg.useSourceMap = true;
      };

      var optimizeFn = function optimizeFn(compilation, chunks, callback) {
        var uglify = new _uglify2.default({
          cache: _this.options.cache,
          parallel: _this.options.parallel
        });

        var uglifiedAssets = new WeakSet();
        var tasks = [];

        chunks.reduce(function (acc, chunk) {
          return acc.concat(chunk.files || []);
        }, []).concat(compilation.additionalChunkAssets || []).filter(_ModuleFilenameHelpers2.default.matchObject.bind(null, _this.options)).forEach(function (file) {
          var inputSourceMap = void 0;
          var asset = compilation.assets[file];
          if (uglifiedAssets.has(asset)) {
            return;
          }

          try {
            var input = void 0;

            if (_this.options.sourceMap && asset.sourceAndMap) {
              var _asset$sourceAndMap = asset.sourceAndMap(),
                  source = _asset$sourceAndMap.source,
                  map = _asset$sourceAndMap.map;

              input = source;

              if (_utils2.default.isSourceMap(map)) {
                inputSourceMap = map;
              } else {
                inputSourceMap = map;
                compilation.warnings.push(new Error(`${file} contains invalid source map`));
              }
            } else {
              input = asset.source();
              inputSourceMap = null;
            }

            // Handling comment extraction
            var commentsFile = false;
            if (_this.options.extractComments) {
              commentsFile = _this.options.extractComments.filename || `${file}.LICENSE`;
              if (typeof commentsFile === 'function') {
                commentsFile = commentsFile(file);
              }
            }

            var task = {
              file,
              input,
              inputSourceMap,
              commentsFile,
              extractComments: _this.options.extractComments,
              uglifyOptions: _this.options.uglifyOptions
            };

            if (_this.options.cache) {
              task.cacheKey = (0, _serializeJavascript2.default)({
                'uglify-es': _versions2.default.uglify,
                'uglifyjs-webpack-plugin': _versions2.default.plugin,
                'uglifyjs-webpack-plugin-options': _this.options,
                path: compiler.outputPath ? `${compiler.outputPath}/${file}` : file,
                hash: _crypto2.default.createHash('md4').update(input).digest('hex')
              });
            }

            tasks.push(task);
          } catch (error) {
            compilation.errors.push(UglifyJsPlugin.buildError(error, file, UglifyJsPlugin.buildSourceMap(inputSourceMap), requestShortener));
          }
        });

        uglify.runTasks(tasks, function (tasksError, results) {
          if (tasksError) {
            compilation.errors.push(tasksError);
            return;
          }

          results.forEach(function (data, index) {
            var _tasks$index = tasks[index],
                file = _tasks$index.file,
                input = _tasks$index.input,
                inputSourceMap = _tasks$index.inputSourceMap,
                commentsFile = _tasks$index.commentsFile;
            var error = data.error,
                map = data.map,
                code = data.code,
                warnings = data.warnings,
                extractedComments = data.extractedComments;


            var sourceMap = null;

            if (error || warnings && warnings.length > 0) {
              sourceMap = UglifyJsPlugin.buildSourceMap(inputSourceMap);
            }

            // Handling results
            // Error case: add errors, and go to next file
            if (error) {
              compilation.errors.push(UglifyJsPlugin.buildError(error, file, sourceMap, requestShortener));

              return;
            }

            var outputSource = void 0;
            if (map) {
              outputSource = new _webpackSources.SourceMapSource(code, file, JSON.parse(map), input, inputSourceMap);
            } else {
              outputSource = new _webpackSources.RawSource(code);
            }

            // Write extracted comments to commentsFile
            if (commentsFile && extractedComments.length > 0) {
              // Add a banner to the original file
              if (_this.options.extractComments.banner !== false) {
                var banner = _this.options.extractComments.banner || `For license information please see ${_path2.default.posix.basename(commentsFile)}`;

                if (typeof banner === 'function') {
                  banner = banner(commentsFile);
                }

                if (banner) {
                  outputSource = new _webpackSources.ConcatSource(`/*! ${banner} */\n`, outputSource);
                }
              }

              var commentsSource = new _webpackSources.RawSource(`${extractedComments.join('\n\n')}\n`);

              if (commentsFile in compilation.assets) {
                // commentsFile already exists, append new comments...
                if (compilation.assets[commentsFile] instanceof _webpackSources.ConcatSource) {
                  compilation.assets[commentsFile].add('\n');
                  compilation.assets[commentsFile].add(commentsSource);
                } else {
                  compilation.assets[commentsFile] = new _webpackSources.ConcatSource(compilation.assets[commentsFile], '\n', commentsSource);
                }
              } else {
                compilation.assets[commentsFile] = commentsSource;
              }
            }

            // Updating assets
            uglifiedAssets.add(compilation.assets[file] = outputSource);

            // Handling warnings
            if (warnings && warnings.length > 0) {
              warnings.forEach(function (warning) {
                var builtWarning = UglifyJsPlugin.buildWarning(warning, file, sourceMap, _this.options.warningsFilter, requestShortener);

                if (builtWarning) {
                  compilation.warnings.push(builtWarning);
                }
              });
            }
          });

          uglify.exit();

          callback();
        });
      };

      if (compiler.hooks) {
        var plugin = { name: 'UglifyJSPlugin' };

        compiler.hooks.compilation.tap(plugin, function (compilation) {
          if (_this.options.sourceMap) {
            compilation.hooks.buildModule.tap(plugin, buildModuleFn);
          }

          compilation.hooks.optimizeChunkAssets.tapAsync(plugin, optimizeFn.bind(_this, compilation));
        });
      } else {
        compiler.plugin('compilation', function (compilation) {
          if (_this.options.sourceMap) {
            compilation.plugin('build-module', buildModuleFn);
          }

          compilation.plugin('optimize-chunk-assets', optimizeFn.bind(_this, compilation));
        });
      }
    }
  }], [{
    key: 'buildSourceMap',
    value: function buildSourceMap(inputSourceMap) {
      if (!inputSourceMap || !_utils2.default.isSourceMap(inputSourceMap)) {
        return null;
      }

      return new _sourceMap.SourceMapConsumer(inputSourceMap);
    }
  }, {
    key: 'buildError',
    value: function buildError(err, file, sourceMap, requestShortener) {
      // Handling error which should have line, col, filename and message
      if (err.line) {
        var original = sourceMap && sourceMap.originalPositionFor({
          line: err.line,
          column: err.col
        });
        if (original && original.source) {
          return new Error(`${file} from UglifyJs\n${err.message} [${requestShortener.shorten(original.source)}:${original.line},${original.column}][${file}:${err.line},${err.col}]`);
        }
        return new Error(`${file} from UglifyJs\n${err.message} [${file}:${err.line},${err.col}]`);
      } else if (err.stack) {
        return new Error(`${file} from UglifyJs\n${err.stack}`);
      }
      return new Error(`${file} from UglifyJs\n${err.message}`);
    }
  }, {
    key: 'buildWarning',
    value: function buildWarning(warning, file, sourceMap, warningsFilter, requestShortener) {
      if (!file || !sourceMap) {
        return warning;
      }

      var match = warningRegex.exec(warning);
      var line = +match[1];
      var column = +match[2];
      var original = sourceMap.originalPositionFor({
        line,
        column
      });

      if (!warningsFilter(original.source)) {
        return null;
      }

      var warningMessage = warning.replace(warningRegex, '');

      if (original && original.source && original.source !== file) {
        warningMessage += `[${requestShortener.shorten(original.source)}:${original.line},${original.column}]`;
      }

      return `UglifyJs Plugin: ${warningMessage} in ${file}`;
    }
  }]);

  return UglifyJsPlugin;
}();

exports.default = UglifyJsPlugin;