1 |
|
2 |
|
3 | "use strict";
|
4 | const path = require('path');
|
5 | const through = require('through2');
|
6 | const PluginError = require('../lib/error');
|
7 | const prettyBytes = require('./pretty-bytes');
|
8 | const chalk = require('ansi-colors');
|
9 | const imagemin = require('imagemin');
|
10 | const log = require('../log/logger');
|
11 | const color = require('../log/color');
|
12 |
|
13 | const PLUGIN_NAME = 'image';
|
14 | const defaultPlugins = ['gifsicle', 'jpegtran', 'optipng', 'svgo'];
|
15 |
|
16 | const loadPlugin = (plugin, ...args) => {
|
17 | try {
|
18 | return require(`imagemin-${plugin}`)(...args);
|
19 | } catch (error) {
|
20 | log.error(`${PLUGIN_NAME}: Couldn't load default plugin "${plugin}"`);
|
21 | }
|
22 | };
|
23 |
|
24 | const exposePlugin = plugin => (...args) => loadPlugin(plugin, ...args);
|
25 |
|
26 | const getDefaultPlugins = () =>
|
27 | defaultPlugins.reduce((plugins, plugin) => {
|
28 | const instance = loadPlugin(plugin);
|
29 |
|
30 | if (!instance) {
|
31 | return plugins;
|
32 | }
|
33 |
|
34 | return plugins.concat(instance);
|
35 | }, []);
|
36 |
|
37 |
|
38 | module.exports = (plugins, options) => {
|
39 | if (typeof plugins === 'object' && !Array.isArray(plugins)) {
|
40 | options = plugins;
|
41 | plugins = null;
|
42 | }
|
43 |
|
44 | options = Object.assign({
|
45 |
|
46 | verbose: process.argv.includes('--verbose')
|
47 | }, options);
|
48 |
|
49 | const validExts = ['.jpg', '.jpeg', '.png', '.gif', '.svg'];
|
50 |
|
51 | let totalBytes = 0;
|
52 | let totalSavedBytes = 0;
|
53 |
|
54 | return through.obj(
|
55 |
|
56 |
|
57 |
|
58 | (file, enc, cb) => {
|
59 | if (file.isNull()) {
|
60 | cb(null, file);
|
61 | return;
|
62 | }
|
63 |
|
64 | if (file.isStream()) {
|
65 | cb(new PluginError(PLUGIN_NAME, 'Streaming not supported'));
|
66 | return;
|
67 | }
|
68 |
|
69 | if (!validExts.includes(path.extname(file.path).toLowerCase())) {
|
70 | if (options.verbose) {
|
71 | log(`${PLUGIN_NAME}: Skipping unsupported image ${chalk.blue(file.relative)}`);
|
72 | }
|
73 |
|
74 | cb(null, file);
|
75 | return;
|
76 | }
|
77 |
|
78 | const use = plugins || getDefaultPlugins();
|
79 |
|
80 | imagemin.buffer(file.contents, { plugins:use })
|
81 | .then(data => {
|
82 | const originalSize = file.contents.length;
|
83 | const optimizedSize = data.length;
|
84 | const saved = originalSize - optimizedSize;
|
85 | const percent = originalSize > 0 ? (saved / originalSize) * 100 : 0;
|
86 | const savedMsg = `saved ${prettyBytes(saved)} - ${percent.toFixed(1).replace(/\.0$/, '')}%`;
|
87 | const msg = saved > 0 ? savedMsg : 'already optimized';
|
88 |
|
89 | if (saved > 0) {
|
90 | totalBytes += originalSize;
|
91 | totalSavedBytes += saved;
|
92 | }
|
93 |
|
94 | if (options.verbose) {
|
95 | log(color(`${PLUGIN_NAME}:`), chalk.underline(file.relative), chalk.gray(` (${msg})`));
|
96 | }
|
97 |
|
98 | file.contents = data;
|
99 | cb(null, file);
|
100 | })
|
101 | .catch(error => {
|
102 | cb(new PluginError(PLUGIN_NAME, error, { fileName: file.path }));
|
103 | });
|
104 | }, cb => {
|
105 | this.percent = totalBytes > 0 ? (totalSavedBytes / totalBytes) * 100 : 0;
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 | cb();
|
114 | });
|
115 | };
|
116 |
|
117 | module.exports.getDefaultPlugins = getDefaultPlugins;
|
118 | module.exports.gifsicle = exposePlugin('gifsicle');
|
119 | module.exports.jpegtran = exposePlugin('jpegtran');
|
120 | module.exports.optipng = exposePlugin('optipng');
|
121 | module.exports.svgo = exposePlugin('svgo');
|