~vovd/node-lodash/HEAD

« back to all changes in this revision

Viewing changes to lodash-cli/lib/minify.js

  • Committer: Pirate Praveen
  • Date: 2019-06-27 13:33:14 UTC
  • Revision ID: git-v1:938a1fd13c57018e0e1859f0c4ec13533eb09b6e
Import Debian changes 4.17.11+dfsg-3

node-lodash (4.17.11+dfsg-3) experimental; urgency=medium

  [ Xavier Guimard ]
  * Add upstream/metadata
  * Update URLs to https
  * Use debian/clean to clean build
  * Fix debian/watch

  [ Pirate Praveen ]
  * Generate ES modules and provide node-lodash-es

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
'use strict';
 
2
 
 
3
var _ = require('lodash'),
 
4
    spawn = require('child_process').spawn,
 
5
    zlib = require('zlib');
 
6
 
 
7
var util = require('./util'),
 
8
    preprocess = require('./preprocess'),
 
9
    postprocess = require('./postprocess');
 
10
 
 
11
var fs = util.fs,
 
12
    path = util.path;
 
13
 
 
14
/** Used to specify the default minifer modes. */
 
15
var DEFAULT_MODES = ['simple', 'advanced', 'hybrid'];
 
16
 
 
17
/** The minimum version of Java required for the Closure Compiler. */
 
18
var JAVA_MIN_VERSION = '1.7.0';
 
19
 
 
20
/** Native method shortcut. */
 
21
var push = Array.prototype.push;
 
22
 
 
23
/** Used to extract version numbers. */
 
24
var reDigits = /^[.\d]+/;
 
25
 
 
26
/** The Closure Compiler optimization modes. */
 
27
var optimizationModes = {
 
28
  'simple': 'simple_optimizations',
 
29
  'advanced': 'advanced_optimizations'
 
30
};
 
31
 
 
32
/*----------------------------------------------------------------------------*/
 
33
 
 
34
/**
 
35
 * Minifies a given lodash `source` and invokes the `options.onComplete`
 
36
 * callback when finished. The `onComplete` callback is invoked with one
 
37
 * argument: (outputSource).
 
38
 *
 
39
 * @param {string|string[]} [source=''] The source to minify or array of commands.
 
40
 *  -o, --output - Write output to a given path/filename.
 
41
 *  -s, --silent - Skip status updates normally logged to the console.
 
42
 *  -t, --template - Applies template specific minifier options.
 
43
 *
 
44
 * @param {Object} [options={}] The options object.
 
45
 * @param {string} [options.outputPath] Write output to a given path/filename.
 
46
 * @param {boolean} [options.isSilent] Skip status updates normally logged to the console.
 
47
 * @param {boolean} [options.isTemplate] Applies template specific minifier options.
 
48
 * @param {Function} [options.onComplete] The function called once minification has finished.
 
49
 */
 
50
function minify(source, options) {
 
51
  // Convert commands to an options object.
 
52
  if (_.isArray(source)) {
 
53
    options = source;
 
54
 
 
55
    // Used to specify the source map URL.
 
56
    var sourceMapURL;
 
57
 
 
58
    // Used to report invalid command-line arguments.
 
59
    var invalidArgs = _.reject(options, function(value, index, options) {
 
60
      if (/^(?:-o|--output)$/.test(options[index - 1]) ||
 
61
          /^modes=.*$/.test(value)) {
 
62
        return true;
 
63
      }
 
64
      var result = _.includes([
 
65
        '-m', '--source-map',
 
66
        '-o', '--output',
 
67
        '-s', '--silent',
 
68
        '-t', '--template'
 
69
      ], value);
 
70
 
 
71
      if (!result && /^(?:-m|--source-map)$/.test(options[index - 1])) {
 
72
        sourceMapURL = value;
 
73
        return true;
 
74
      }
 
75
      return result;
 
76
    });
 
77
 
 
78
    // Report invalid arguments.
 
79
    if (!_.isEmpty(invalidArgs)) {
 
80
      console.log('\nInvalid argument' + (_.size(invalidArgs) > 1 ? 's' : '') + ' passed:', invalidArgs.join(', '));
 
81
      return;
 
82
    }
 
83
    var filePath = path.normalize(_.last(options)),
 
84
        outputPath = path.join(path.dirname(filePath), path.basename(filePath, '.js') + '.min.js');
 
85
 
 
86
    outputPath = _.reduce(options, function(result, value, index) {
 
87
      if (/-o|--output/.test(value)) {
 
88
        result = path.normalize(options[index + 1]);
 
89
        var dirname = path.dirname(result);
 
90
        fs.mkdirpSync(dirname);
 
91
        result = path.join(fs.realpathSync(dirname), path.basename(result));
 
92
      }
 
93
      return result;
 
94
    }, outputPath);
 
95
 
 
96
    options = {
 
97
      'filePath': filePath,
 
98
      'isMapped': getOption(options, '-m') || getOption(options, '--source-map'),
 
99
      'isSilent': getOption(options, '-s') || getOption(options, '--silent'),
 
100
      'isTemplate': getOption(options, '-t') || getOption(options, '--template'),
 
101
      'modes': getOption(options, 'modes', DEFAULT_MODES),
 
102
      'outputPath': outputPath,
 
103
      'sourceMapURL': sourceMapURL
 
104
    };
 
105
 
 
106
    source = fs.readFileSync(filePath, 'utf8');
 
107
  }
 
108
  else {
 
109
    options = _.cloneDeep(options);
 
110
  }
 
111
  source = _.toString(source);
 
112
 
 
113
  options.filePath = path.normalize(options.filePath);
 
114
  options.modes = _.get(options, 'modes', DEFAULT_MODES);
 
115
  options.outputPath = path.normalize(options.outputPath);
 
116
 
 
117
  if (options.isMapped) {
 
118
    _.pull(options.modes, 'advanced', 'hybrid');
 
119
  }
 
120
  if (options.isTemplate) {
 
121
    _.pull(options.modes, 'advanced');
 
122
  }
 
123
  new Minify(source, options);
 
124
}
 
125
 
 
126
/**
 
127
 * The Minify constructor used to keep state of each `minify` invocation.
 
128
 *
 
129
 * @private
 
130
 * @constructor
 
131
 * @param {string} source The source to minify.
 
132
 * @param {Object} options The options object.
 
133
 * @param {string} [options.outputPath=''] Write output to a given path/filename.
 
134
 * @param {boolean} [options.isMapped] Specify creating a source map for the minified source.
 
135
 * @param {boolean} [options.isSilent] Skip status updates normally logged to the console.
 
136
 * @param {boolean} [options.isTemplate] Applies template specific minifier options.
 
137
 * @param {Function} [options.onComplete] The function called once minification has finished.
 
138
 */
 
139
function Minify(source, options) {
 
140
  // Juggle arguments.
 
141
  if (_.isObject(source)) {
 
142
    options = source;
 
143
    source = options.source || '';
 
144
  }
 
145
  var modes = options.modes;
 
146
 
 
147
  this.compiled = { 'simple': {}, 'advanced': {} };
 
148
  this.hybrid = { 'simple': {}, 'advanced': {} };
 
149
  this.uglified = {};
 
150
 
 
151
  this.filePath = options.filePath;
 
152
  this.isMapped = !!options.isMapped;
 
153
  this.isSilent = !!options.isSilent;
 
154
  this.isTemplate = !!options.isTemplate;
 
155
  this.modes = modes;
 
156
  this.outputPath = options.outputPath;
 
157
  this.source = source;
 
158
  this.sourceMapURL = options.sourceMapURL;
 
159
 
 
160
  this.onComplete = options.onComplete || function(data) {
 
161
    var outputPath = this.outputPath,
 
162
        sourceMap = data.sourceMap;
 
163
 
 
164
    fs.writeFileSync(outputPath, data.source, 'utf8');
 
165
    if (sourceMap) {
 
166
      fs.writeFileSync(getMapPath(outputPath), sourceMap, 'utf8');
 
167
    }
 
168
  };
 
169
 
 
170
  // Begin the minification process.
 
171
  if (this.isMapped) {
 
172
    uglify.call(this, source, onUglify.bind(this));
 
173
  } else if (_.includes(modes, 'simple')) {
 
174
    closureCompiler.call(this, source, 'simple', onClosureSimpleCompile.bind(this));
 
175
  } else if (_.includes(modes, 'advanced')) {
 
176
    onClosureSimpleGzip.call(this);
 
177
  } else {
 
178
    onClosureAdvancedGzip.call(this);
 
179
  }
 
180
}
 
181
 
 
182
/*----------------------------------------------------------------------------*/
 
183
 
 
184
/**
 
185
 * Asynchronously checks if Java 1.7 is installed. The callback is invoked
 
186
 * with one argument: (success).
 
187
 *
 
188
 * @private
 
189
 * @type Function
 
190
 * @param {Function} callback The function called once the status is resolved.
 
191
 */
 
192
var checkJava = (function() {
 
193
  var result;
 
194
  return function(callback) {
 
195
    if (result != null) {
 
196
      _.defer(callback, result);
 
197
      return;
 
198
    }
 
199
    var java = spawn('java', ['-version']);
 
200
 
 
201
    java.stderr.on('data', function(data) {
 
202
      java.stderr.removeAllListeners('data');
 
203
 
 
204
      var version = _.get(/(?:java|openjdk) version "(.+)"/.exec(data.toString()), 1, '');
 
205
      result = compareVersion(version, JAVA_MIN_VERSION) > -1;
 
206
      if (result) {
 
207
        callback(result);
 
208
      } else {
 
209
        java.emit('error');
 
210
      }
 
211
    });
 
212
 
 
213
    java.on('error', function() {
 
214
      result = false;
 
215
      callback(result);
 
216
    });
 
217
  };
 
218
}());
 
219
 
 
220
/**
 
221
 * Compares two version strings to determine if the first is greater than,
 
222
 * equal to, or less then the second.
 
223
 *
 
224
 * @private
 
225
 * @param {string} [version=''] The version string to compare to `other`.
 
226
 * @param {string} [other=''] The version string to compare to `version`.
 
227
 * @returns {number} Returns `1` if greater then, `0` if equal to, or `-1` if
 
228
 *  less than the second version string.
 
229
 */
 
230
function compareVersion(version, other) {
 
231
  version = splitVersion(version);
 
232
  other = splitVersion(other);
 
233
 
 
234
  var index = -1,
 
235
      verLength = version.length,
 
236
      othLength = other.length,
 
237
      maxLength = Math.max(verLength, othLength),
 
238
      diff = Math.abs(verLength - othLength);
 
239
 
 
240
  push.apply(verLength == maxLength ? other : version, _.range(0, diff, 0));
 
241
  while (++index < maxLength) {
 
242
    var verValue = version[index],
 
243
        othValue = other[index];
 
244
 
 
245
    if (verValue > othValue) {
 
246
      return 1;
 
247
    }
 
248
    if (verValue < othValue) {
 
249
      return -1;
 
250
    }
 
251
  }
 
252
  return 0;
 
253
}
 
254
 
 
255
/**
 
256
 * Resolves the source map path from the given output path.
 
257
 *
 
258
 * @private
 
259
 * @param {string} outputPath The output path.
 
260
 * @returns {string} Returns the source map path.
 
261
 */
 
262
function getMapPath(outputPath) {
 
263
  return path.join(path.dirname(outputPath), path.basename(outputPath, '.js') + '.map');
 
264
}
 
265
 
 
266
/**
 
267
 * Gets the value of a given name from the `options` array. If no value is
 
268
 * available the `defaultValue` is returned.
 
269
 *
 
270
 * @private
 
271
 * @param {Array} options The options array to inspect.
 
272
 * @param {string} name The name of the option.
 
273
 * @param {*} defaultValue The default option value.
 
274
 * @returns {*} Returns the option value.
 
275
 */
 
276
function getOption(options, name, defaultValue) {
 
277
  var isArr = _.isArray(defaultValue);
 
278
  return _.reduce(options, function(result, value) {
 
279
    if (isArr) {
 
280
      value = optionToArray(name, value);
 
281
      return _.isEmpty(value) ? result : value;
 
282
    }
 
283
    value = optionToValue(name, value);
 
284
    return value == null ? result : value;
 
285
  }, defaultValue);
 
286
}
 
287
 
 
288
/**
 
289
 * Compress a source with Gzip. Yields the gzip buffer and any exceptions
 
290
 * encountered to a callback function.
 
291
 *
 
292
 * @private
 
293
 * @param {string} source The source to gzip.
 
294
 * @param {Function} callback The function called once the process has completed.
 
295
 * @param {Buffer} result The gzipped source buffer.
 
296
 */
 
297
function gzip(source, callback) {
 
298
  return _.size(zlib.gzip) > 2
 
299
    ? zlib.gzip(source, { 'level': zlib.Z_BEST_COMPRESSION } , callback)
 
300
    : zlib.gzip(source, callback);
 
301
}
 
302
 
 
303
/**
 
304
 * Converts a comma separated option value into an array.
 
305
 *
 
306
 * @private
 
307
 * @param {string} name The name of the option to inspect.
 
308
 * @param {string} string The options string.
 
309
 * @returns {Array} Returns the new converted array.
 
310
 */
 
311
function optionToArray(name, string) {
 
312
  return _.compact(_.invokeMap((optionToValue(name, string) || '').split(/, */), 'trim'));
 
313
}
 
314
 
 
315
/**
 
316
 * Extracts the option value from an option string.
 
317
 *
 
318
 * @private
 
319
 * @param {string} name The name of the option to inspect.
 
320
 * @param {string} string The options string.
 
321
 * @returns {string|undefined} Returns the option value, else `undefined`.
 
322
 */
 
323
function optionToValue(name, string) {
 
324
  var result = (result = string.match(RegExp('^' + name + '(?:=([\\s\\S]+))?$'))) && (result[1] ? result[1].trim() : true);
 
325
  if (result === 'false') {
 
326
    return false;
 
327
  }
 
328
  return result || undefined;
 
329
}
 
330
 
 
331
/**
 
332
 * Splits a version string by its decimal points into its numeric components.
 
333
 *
 
334
 * @private
 
335
 * @param {string} [version=''] The version string to split.
 
336
 * @returns {number[]} Returns the array of numeric components.
 
337
 */
 
338
function splitVersion(version) {
 
339
  if (version == null) {
 
340
    return [];
 
341
  }
 
342
  var result = String(version).match(reDigits);
 
343
  return result ? _.map(result[0].split('.'), Number) : [];
 
344
}
 
345
 
 
346
/*----------------------------------------------------------------------------*/
 
347
 
 
348
/**
 
349
 * Compress a source using the Closure Compiler. Yields the minified result
 
350
 * and any exceptions encountered to a callback function.
 
351
 *
 
352
 * @private
 
353
 * @param {string} source The JavaScript source to minify.
 
354
 * @param {string} mode The optimization mode.
 
355
 * @param {string} [label] The label to log.
 
356
 * @param {Function} callback The function called once the process has completed.
 
357
 */
 
358
function closureCompiler(source, mode, label, callback) {
 
359
  var compiler = require('closure-compiler'),
 
360
      isSilent = this.isSilent,
 
361
      isTemplate = this.isTemplate,
 
362
      modes = this.modes,
 
363
      outputPath = this.outputPath;
 
364
 
 
365
  var options = {
 
366
    'charset': isTemplate ? 'utf8': 'ascii',
 
367
    'compilation_level': optimizationModes[mode],
 
368
    'warning_level': 'quiet'
 
369
  };
 
370
 
 
371
  if (callback == null && typeof label == 'function') {
 
372
    callback = label;
 
373
    label = null;
 
374
  }
 
375
  if (label == null) {
 
376
    label = 'the Closure Compiler (' + mode + ')';
 
377
  }
 
378
  checkJava(function(success) {
 
379
    // Skip using the Closure Compiler if Java is not installed.
 
380
    if (!success) {
 
381
      if (!isSilent) {
 
382
        console.warn('The Closure Compiler requires Java %s. Skipping...', JAVA_MIN_VERSION);
 
383
      }
 
384
      _.pull(modes, 'advanced', 'hybrid');
 
385
      callback();
 
386
      return;
 
387
    }
 
388
    source = preprocess(source, { 'isTemplate': isTemplate });
 
389
 
 
390
    // Remove the copyright header to make other modifications easier.
 
391
    var license = _.get(/^(?:\s*\/\/.*|\s*\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)*\s*/.exec(source), 0, '');
 
392
    source = source.replace(license, '');
 
393
 
 
394
    var hasIIFE = /^;?\(function[^{]+{/.test(source),
 
395
        isStrict = /^;?\(function[^{]+{\s*["']use strict["']/.test(source);
 
396
 
 
397
    // To avoid stripping the IIFE, convert it to a function call.
 
398
    if (hasIIFE) {
 
399
      source = source
 
400
        .replace(/\(function/, '__iife__$&')
 
401
        .replace(/\.call\(this\)\)([\s;]*(?:\n\/\/.+)?)$/, ', this)$1');
 
402
    }
 
403
    if (!isSilent) {
 
404
      console.log('Compressing %s using %s...', path.basename(outputPath, '.js'), label);
 
405
    }
 
406
    compiler.compile(source, options, function(error, output) {
 
407
      if (error) {
 
408
        callback(error);
 
409
        return;
 
410
      }
 
411
      // Restore IIFE and move exposed vars inside the IIFE.
 
412
      if (hasIIFE) {
 
413
        output = output
 
414
          .replace(/\b__iife__\b/, '')
 
415
          .replace(/^((?:var (?:[$\w]+=(?:!0|!1|null)[,;])+)?)([\s\S]*?function[^{]+{)/, '$2$1')
 
416
          .replace(/,\s*this\)([\s;]*(?:\n\/\/.+)?)$/, '.call(this))$1');
 
417
      }
 
418
      // Inject "use strict" directive.
 
419
      if (isStrict) {
 
420
        output = output.replace(/^[\s\S]*?function[^{]+{/, '$&"use strict";');
 
421
      }
 
422
      // Restore copyright header.
 
423
      if (license) {
 
424
        output = license + output;
 
425
      }
 
426
      callback(error, output);
 
427
    });
 
428
  });
 
429
}
 
430
 
 
431
/**
 
432
 * Compress a source using UglifyJS. Yields the minified result and any
 
433
 * exceptions encountered to a callback function.
 
434
 *
 
435
 * @private
 
436
 * @param {string} source The JavaScript source to minify.
 
437
 * @param {string} [label] The label to log.
 
438
 * @param {Function} callback The function called once the process has completed.
 
439
 */
 
440
function uglify(source, label, callback) {
 
441
  var uglifyJS = require('uglify-js'),
 
442
      sourceMapURL = this.isMapped && (this.sourceMapURL || path.basename(getMapPath(this.outputPath)));
 
443
 
 
444
  if (callback == null && typeof label == 'function') {
 
445
    callback = label;
 
446
    label = null;
 
447
  }
 
448
  if (label == null) {
 
449
    label = 'UglifyJS';
 
450
  }
 
451
  if (!this.isSilent) {
 
452
    console.log('Compressing %s using %s...', path.basename(this.outputPath, '.js'), label);
 
453
  }
 
454
  try {
 
455
    source = preprocess(source, { 'isTemplate': this.isTemplate });
 
456
 
 
457
    var result = uglifyJS.minify(source, {
 
458
      'fromString': true,
 
459
      'outSourceMap': sourceMapURL,
 
460
      'compress': {
 
461
        'collapse_vars': true,
 
462
        'negate_iife': false,
 
463
        'pure_getters': true,
 
464
        'unsafe': true,
 
465
        'warnings': false
 
466
      },
 
467
      'output': _.assign({
 
468
        'ascii_only': !this.isTemplate,
 
469
        'max_line_len': 500
 
470
      }, sourceMapURL ? {} : {
 
471
        'comments': /@license/
 
472
      })
 
473
    });
 
474
  } catch (e) {
 
475
    var error = e;
 
476
    result = {};
 
477
  }
 
478
  result.map = !sourceMapURL ? null : JSON.stringify(_.assign(JSON.parse(result.map), {
 
479
    'file': path.basename(this.outputPath),
 
480
    'sources': [path.basename(this.filePath)]
 
481
  }));
 
482
 
 
483
  _.defer(callback, error, result.code, result.map);
 
484
}
 
485
 
 
486
/*----------------------------------------------------------------------------*/
 
487
 
 
488
/**
 
489
 * The Closure Compiler callback for simple optimizations.
 
490
 *
 
491
 * @private
 
492
 * @param {Object} [error] The error object.
 
493
 * @param {string} result The minified source.
 
494
 */
 
495
function onClosureSimpleCompile(error, result) {
 
496
  if (error) {
 
497
    throw error;
 
498
  }
 
499
  if (result != null) {
 
500
    result = postprocess(result);
 
501
    this.compiled.simple.source = result;
 
502
    gzip(result, onClosureSimpleGzip.bind(this));
 
503
  }
 
504
  else {
 
505
    onClosureSimpleGzip.call(this);
 
506
  }
 
507
}
 
508
 
 
509
/**
 
510
 * The Closure Compiler `gzip` callback for simple optimizations.
 
511
 *
 
512
 * @private
 
513
 * @param {Object} [error] The error object.
 
514
 * @param {Buffer} result The gzipped source buffer.
 
515
 */
 
516
function onClosureSimpleGzip(error, result) {
 
517
  if (error) {
 
518
    throw error;
 
519
  }
 
520
  if (result != null) {
 
521
    if (!this.isSilent) {
 
522
      console.log('Done. Size: %d bytes.', _.size(result));
 
523
    }
 
524
    this.compiled.simple.gzip = result;
 
525
  }
 
526
  if (_.includes(this.modes, 'advanced')) {
 
527
    closureCompiler.call(this, this.source, 'advanced', onClosureAdvancedCompile.bind(this));
 
528
  } else {
 
529
    onClosureAdvancedGzip.call(this);
 
530
  }
 
531
}
 
532
 
 
533
/**
 
534
 * The Closure Compiler callback for advanced optimizations.
 
535
 *
 
536
 * @private
 
537
 * @param {Object} [error] The error object.
 
538
 * @param {string} result The minified source.
 
539
 */
 
540
function onClosureAdvancedCompile(error, result) {
 
541
  if (error) {
 
542
    throw error;
 
543
  }
 
544
  if (result != null) {
 
545
    result = postprocess(result);
 
546
    this.compiled.advanced.source = result;
 
547
    gzip(result, onClosureAdvancedGzip.bind(this));
 
548
  }
 
549
  else {
 
550
    onClosureAdvancedGzip.call(this);
 
551
  }
 
552
}
 
553
 
 
554
/**
 
555
 * The Closure Compiler `gzip` callback for advanced optimizations.
 
556
 *
 
557
 * @private
 
558
 * @param {Object} [error] The error object.
 
559
 * @param {Buffer} result The gzipped source buffer.
 
560
 */
 
561
function onClosureAdvancedGzip(error, result) {
 
562
  if (error) {
 
563
    throw error;
 
564
  }
 
565
  if (result != null) {
 
566
    if (!this.isSilent) {
 
567
      console.log('Done. Size: %d bytes.', _.size(result));
 
568
    }
 
569
    this.compiled.advanced.gzip = result;
 
570
  }
 
571
  uglify.call(this, this.source, onUglify.bind(this));
 
572
}
 
573
 
 
574
/**
 
575
 * The UglifyJS callback.
 
576
 *
 
577
 * @private
 
578
 * @param {Object} [error] The error object.
 
579
 * @param {string} result The minified source.
 
580
 * @param {string} map The source map output.
 
581
 */
 
582
function onUglify(error, result, map) {
 
583
  if (error) {
 
584
    throw error;
 
585
  }
 
586
  result = postprocess(result, !!map);
 
587
  _.assign(this.uglified, { 'source': result, 'sourceMap': map });
 
588
  gzip(result, onUglifyGzip.bind(this));
 
589
}
 
590
 
 
591
/**
 
592
 * The UglifyJS `gzip` callback.
 
593
 *
 
594
 * @private
 
595
 * @param {Object} [error] The error object.
 
596
 * @param {Buffer} result The gzipped source buffer.
 
597
 */
 
598
function onUglifyGzip(error, result) {
 
599
  if (error) {
 
600
    throw error;
 
601
  }
 
602
  if (result != null) {
 
603
    if (!this.isSilent) {
 
604
      console.log('Done. Size: %d bytes.', _.size(result));
 
605
    }
 
606
    this.uglified.gzip = result;
 
607
  }
 
608
  // Minify the already Closure Compiler simple optimized source using UglifyJS.
 
609
  var modes = this.modes;
 
610
  if (_.includes(modes, 'hybrid')) {
 
611
    if (_.includes(modes, 'simple')) {
 
612
      uglify.call(this, this.compiled.simple.source, 'hybrid (simple)', onSimpleHybrid.bind(this));
 
613
    } else if (_.includes(modes, 'advanced')) {
 
614
      onSimpleHybridGzip.call(this);
 
615
    }
 
616
  } else {
 
617
    onComplete.call(this);
 
618
  }
 
619
}
 
620
 
 
621
/**
 
622
 * The hybrid callback for simple optimizations.
 
623
 *
 
624
 * @private
 
625
 * @param {Object} [error] The error object.
 
626
 * @param {string} result The minified source.
 
627
 */
 
628
function onSimpleHybrid(error, result) {
 
629
  if (error) {
 
630
    throw error;
 
631
  }
 
632
  result = postprocess(result);
 
633
  this.hybrid.simple.source = result;
 
634
  gzip(result, onSimpleHybridGzip.bind(this));
 
635
}
 
636
 
 
637
/**
 
638
 * The hybrid `gzip` callback for simple optimizations.
 
639
 *
 
640
 * @private
 
641
 * @param {Object} [error] The error object.
 
642
 * @param {Buffer} result The gzipped source buffer.
 
643
 */
 
644
function onSimpleHybridGzip(error, result) {
 
645
  if (error) {
 
646
    throw error;
 
647
  }
 
648
  if (result != null) {
 
649
    if (!this.isSilent) {
 
650
      console.log('Done. Size: %d bytes.', _.size(result));
 
651
    }
 
652
    this.hybrid.simple.gzip = result;
 
653
  }
 
654
  // Minify the already Closure Compiler advance optimized source using UglifyJS.
 
655
  if (_.includes(this.modes, 'advanced')) {
 
656
    uglify.call(this, this.compiled.advanced.source, 'hybrid (advanced)', onAdvancedHybrid.bind(this));
 
657
  } else {
 
658
    onComplete.call(this);
 
659
  }
 
660
}
 
661
 
 
662
/**
 
663
 * The hybrid callback for advanced optimizations.
 
664
 *
 
665
 * @private
 
666
 * @param {Object} [error] The error object.
 
667
 * @param {string} result The minified source.
 
668
 */
 
669
function onAdvancedHybrid(error, result) {
 
670
  if (error) {
 
671
    throw error;
 
672
  }
 
673
  result = postprocess(result);
 
674
  this.hybrid.advanced.source = result;
 
675
  gzip(result, onAdvancedHybridGzip.bind(this));
 
676
}
 
677
 
 
678
/**
 
679
 * The hybrid `gzip` callback for advanced optimizations.
 
680
 *
 
681
 * @private
 
682
 * @param {Object} [error] The error object.
 
683
 * @param {Buffer} result The gzipped source buffer.
 
684
 */
 
685
function onAdvancedHybridGzip(error, result) {
 
686
  if (error) {
 
687
    throw error;
 
688
  }
 
689
  if (result != null) {
 
690
    if (!this.isSilent) {
 
691
      console.log('Done. Size: %d bytes.', _.size(result));
 
692
    }
 
693
    this.hybrid.advanced.gzip = result;
 
694
  }
 
695
  // Finish by choosing the smallest compressed file.
 
696
  onComplete.call(this);
 
697
}
 
698
 
 
699
/**
 
700
 * The callback executed after the source is minified and gzipped.
 
701
 *
 
702
 * @private
 
703
 */
 
704
function onComplete() {
 
705
  var objects = [
 
706
    this.compiled.simple,
 
707
    this.compiled.advanced,
 
708
    this.uglified,
 
709
    this.hybrid.simple,
 
710
    this.hybrid.advanced
 
711
  ];
 
712
 
 
713
  var gzips = _.compact(_.map(objects, 'gzip'));
 
714
 
 
715
  // Select the smallest gzipped file and use its minified counterpart as the
 
716
  // official minified release (ties go to the Closure Compiler).
 
717
  var min = _.size(_.minBy(gzips, 'length'));
 
718
 
 
719
  // Pass the minified source to the "onComplete" callback.
 
720
  _.each(objects, _.bind(function(data) {
 
721
    if (_.size(data.gzip) == min) {
 
722
      data.outputPath = this.outputPath;
 
723
      this.onComplete(data);
 
724
      return false;
 
725
    }
 
726
  }, this));
 
727
}
 
728
 
 
729
module.exports = minify;