UNPKG

3.36 kBJavaScriptView Raw
1const path = require('path');
2const log = require('fancy-log');
3const PluginError = require('plugin-error');
4const through = require('through2-concurrent');
5const prettyBytes = require('pretty-bytes');
6const chalk = require('chalk');
7const imagemin = require('imagemin');
8const plur = require('plur');
9
10const PLUGIN_NAME = 'gulp-imagemin';
11const defaultPlugins = ['gifsicle', 'mozjpeg', 'optipng', 'svgo'];
12
13const loadPlugin = (plugin, ...args) => {
14 try {
15 return require(`imagemin-${plugin}`)(...args);
16 } catch (_) {
17 log(`${PLUGIN_NAME}: Couldn't load default plugin "${plugin}"`);
18 }
19};
20
21const exposePlugin = plugin => (...args) => loadPlugin(plugin, ...args);
22
23const getDefaultPlugins = () =>
24 defaultPlugins.reduce((plugins, plugin) => {
25 const instance = loadPlugin(plugin);
26
27 if (!instance) {
28 return plugins;
29 }
30
31 return plugins.concat(instance);
32 }, []);
33
34module.exports = (plugins, options) => {
35 if (typeof plugins === 'object' && !Array.isArray(plugins)) {
36 options = plugins;
37 plugins = undefined;
38 }
39
40 options = {
41 // TODO: Remove this when Gulp gets a real logger with levels
42 silent: process.argv.includes('--silent'),
43 verbose: process.argv.includes('--verbose'),
44 ...options
45 };
46
47 const validExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.svg'];
48
49 let totalBytes = 0;
50 let totalSavedBytes = 0;
51 let totalFiles = 0;
52
53 return through.obj({
54 maxConcurrency: 8
55 }, (file, encoding, callback) => {
56 if (file.isNull()) {
57 callback(null, file);
58 return;
59 }
60
61 if (file.isStream()) {
62 callback(new PluginError(PLUGIN_NAME, 'Streaming not supported'));
63 return;
64 }
65
66 if (!validExtensions.includes(path.extname(file.path).toLowerCase())) {
67 if (options.verbose) {
68 log(`${PLUGIN_NAME}: Skipping unsupported image ${chalk.blue(file.relative)}`);
69 }
70
71 callback(null, file);
72 return;
73 }
74
75 const localPlugins = plugins || getDefaultPlugins();
76
77 (async () => {
78 try {
79 const data = await imagemin.buffer(file.contents, {
80 plugins: localPlugins
81 });
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 totalFiles++;
93 }
94
95 if (options.verbose) {
96 log(`${PLUGIN_NAME}:`, chalk.green('✔ ') + file.relative + chalk.gray(` (${msg})`));
97 }
98
99 file.contents = data;
100 callback(null, file);
101 } catch (error) {
102 callback(new PluginError(PLUGIN_NAME, error, {fileName: file.path}));
103 }
104 })();
105 }, callback => {
106 if (!options.silent) {
107 const percent = totalBytes > 0 ? (totalSavedBytes / totalBytes) * 100 : 0;
108 let msg = `Minified ${totalFiles} ${plur('image', totalFiles)}`;
109
110 if (totalFiles > 0) {
111 msg += chalk.gray(` (saved ${prettyBytes(totalSavedBytes)} - ${percent.toFixed(1).replace(/\.0$/, '')}%)`);
112 }
113
114 log(`${PLUGIN_NAME}:`, msg);
115 }
116
117 callback();
118 });
119};
120
121module.exports.gifsicle = exposePlugin('gifsicle');
122module.exports.mozjpeg = exposePlugin('mozjpeg');
123module.exports.optipng = exposePlugin('optipng');
124module.exports.svgo = exposePlugin('svgo');