1 | const path = require('path');
|
2 | const log = require('fancy-log');
|
3 | const PluginError = require('plugin-error');
|
4 | const through = require('through2-concurrent');
|
5 | const prettyBytes = require('pretty-bytes');
|
6 | const chalk = require('chalk');
|
7 | const imagemin = require('imagemin');
|
8 | const plur = require('plur');
|
9 |
|
10 | const PLUGIN_NAME = 'gulp-imagemin';
|
11 | const defaultPlugins = ['gifsicle', 'mozjpeg', 'optipng', 'svgo'];
|
12 |
|
13 | const 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 |
|
21 | const exposePlugin = plugin => (...args) => loadPlugin(plugin, ...args);
|
22 |
|
23 | const 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 |
|
34 | module.exports = (plugins, options) => {
|
35 | if (typeof plugins === 'object' && !Array.isArray(plugins)) {
|
36 | options = plugins;
|
37 | plugins = undefined;
|
38 | }
|
39 |
|
40 | options = {
|
41 |
|
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 |
|
121 | module.exports.gifsicle = exposePlugin('gifsicle');
|
122 | module.exports.mozjpeg = exposePlugin('mozjpeg');
|
123 | module.exports.optipng = exposePlugin('optipng');
|
124 | module.exports.svgo = exposePlugin('svgo');
|