svgo-v2 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +294 -0
- package/bin/svgo +10 -0
- package/dist/svgo.browser.js +1 -0
- package/lib/css-tools.js +239 -0
- package/lib/parser.js +259 -0
- package/lib/path.js +347 -0
- package/lib/stringifier.js +326 -0
- package/lib/style.js +283 -0
- package/lib/svgo/coa.js +517 -0
- package/lib/svgo/config.js +138 -0
- package/lib/svgo/css-class-list.js +72 -0
- package/lib/svgo/css-select-adapter.d.ts +2 -0
- package/lib/svgo/css-select-adapter.js +120 -0
- package/lib/svgo/css-style-declaration.js +232 -0
- package/lib/svgo/jsAPI.d.ts +2 -0
- package/lib/svgo/jsAPI.js +443 -0
- package/lib/svgo/plugins.js +109 -0
- package/lib/svgo/tools.js +137 -0
- package/lib/svgo-node.js +106 -0
- package/lib/svgo.js +83 -0
- package/lib/types.ts +172 -0
- package/lib/xast.js +102 -0
- package/package.json +130 -0
- package/plugins/_applyTransforms.js +335 -0
- package/plugins/_collections.js +2168 -0
- package/plugins/_path.js +816 -0
- package/plugins/_transforms.js +379 -0
- package/plugins/addAttributesToSVGElement.js +87 -0
- package/plugins/addClassesToSVGElement.js +87 -0
- package/plugins/cleanupAttrs.js +55 -0
- package/plugins/cleanupEnableBackground.js +75 -0
- package/plugins/cleanupIDs.js +297 -0
- package/plugins/cleanupListOfValues.js +154 -0
- package/plugins/cleanupNumericValues.js +113 -0
- package/plugins/collapseGroups.js +135 -0
- package/plugins/convertColors.js +152 -0
- package/plugins/convertEllipseToCircle.js +39 -0
- package/plugins/convertPathData.js +1023 -0
- package/plugins/convertShapeToPath.js +175 -0
- package/plugins/convertStyleToAttrs.js +132 -0
- package/plugins/convertTransform.js +432 -0
- package/plugins/inlineStyles.js +379 -0
- package/plugins/mergePaths.js +104 -0
- package/plugins/mergeStyles.js +93 -0
- package/plugins/minifyStyles.js +148 -0
- package/plugins/moveElemsAttrsToGroup.js +130 -0
- package/plugins/moveGroupAttrsToElems.js +62 -0
- package/plugins/plugins.js +56 -0
- package/plugins/prefixIds.js +241 -0
- package/plugins/preset-default.js +80 -0
- package/plugins/removeAttributesBySelector.js +99 -0
- package/plugins/removeAttrs.js +159 -0
- package/plugins/removeComments.js +31 -0
- package/plugins/removeDesc.js +41 -0
- package/plugins/removeDimensions.js +43 -0
- package/plugins/removeDoctype.js +42 -0
- package/plugins/removeEditorsNSData.js +68 -0
- package/plugins/removeElementsByAttr.js +78 -0
- package/plugins/removeEmptyAttrs.js +33 -0
- package/plugins/removeEmptyContainers.js +58 -0
- package/plugins/removeEmptyText.js +57 -0
- package/plugins/removeHiddenElems.js +318 -0
- package/plugins/removeMetadata.js +29 -0
- package/plugins/removeNonInheritableGroupAttrs.js +38 -0
- package/plugins/removeOffCanvasPaths.js +138 -0
- package/plugins/removeRasterImages.js +33 -0
- package/plugins/removeScriptElement.js +29 -0
- package/plugins/removeStyleElement.js +29 -0
- package/plugins/removeTitle.js +29 -0
- package/plugins/removeUnknownsAndDefaults.js +218 -0
- package/plugins/removeUnusedNS.js +61 -0
- package/plugins/removeUselessDefs.js +65 -0
- package/plugins/removeUselessStrokeAndFill.js +144 -0
- package/plugins/removeViewBox.js +51 -0
- package/plugins/removeXMLNS.js +30 -0
- package/plugins/removeXMLProcInst.js +30 -0
- package/plugins/reusePaths.js +113 -0
- package/plugins/sortAttrs.js +113 -0
- package/plugins/sortDefsChildren.js +60 -0
package/lib/svgo/coa.js
ADDED
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const colors = require('picocolors');
|
|
6
|
+
const { loadConfig, optimize } = require('../svgo-node.js');
|
|
7
|
+
const pluginsMap = require('../../plugins/plugins.js');
|
|
8
|
+
const PKG = require('../../package.json');
|
|
9
|
+
const { encodeSVGDatauri, decodeSVGDatauri } = require('./tools.js');
|
|
10
|
+
|
|
11
|
+
const regSVGFile = /\.svg$/i;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Synchronously check if path is a directory. Tolerant to errors like ENOENT.
|
|
15
|
+
* @param {string} path
|
|
16
|
+
*/
|
|
17
|
+
function checkIsDir(path) {
|
|
18
|
+
try {
|
|
19
|
+
return fs.lstatSync(path).isDirectory();
|
|
20
|
+
} catch (e) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = function makeProgram(program) {
|
|
26
|
+
program
|
|
27
|
+
.name(PKG.name)
|
|
28
|
+
.description(PKG.description, {
|
|
29
|
+
INPUT: 'Alias to --input',
|
|
30
|
+
})
|
|
31
|
+
.version(PKG.version, '-v, --version')
|
|
32
|
+
.arguments('[INPUT...]')
|
|
33
|
+
.option('-i, --input <INPUT...>', 'Input files, "-" for STDIN')
|
|
34
|
+
.option('-s, --string <STRING>', 'Input SVG data string')
|
|
35
|
+
.option(
|
|
36
|
+
'-f, --folder <FOLDER>',
|
|
37
|
+
'Input folder, optimize and rewrite all *.svg files'
|
|
38
|
+
)
|
|
39
|
+
.option(
|
|
40
|
+
'-o, --output <OUTPUT...>',
|
|
41
|
+
'Output file or folder (by default the same as the input), "-" for STDOUT'
|
|
42
|
+
)
|
|
43
|
+
.option(
|
|
44
|
+
'-p, --precision <INTEGER>',
|
|
45
|
+
'Set number of digits in the fractional part, overrides plugins params'
|
|
46
|
+
)
|
|
47
|
+
.option('--config <CONFIG>', 'Custom config file, only .js is supported')
|
|
48
|
+
.option(
|
|
49
|
+
'--datauri <FORMAT>',
|
|
50
|
+
'Output as Data URI string (base64), URI encoded (enc) or unencoded (unenc)'
|
|
51
|
+
)
|
|
52
|
+
.option(
|
|
53
|
+
'--multipass',
|
|
54
|
+
'Pass over SVGs multiple times to ensure all optimizations are applied'
|
|
55
|
+
)
|
|
56
|
+
.option('--pretty', 'Make SVG pretty printed')
|
|
57
|
+
.option('--indent <INTEGER>', 'Indent number when pretty printing SVGs')
|
|
58
|
+
.option(
|
|
59
|
+
'--eol <EOL>',
|
|
60
|
+
'Line break to use when outputting SVG: lf, crlf. If unspecified, uses platform default.'
|
|
61
|
+
)
|
|
62
|
+
.option('--final-newline', 'Ensure SVG ends with a line break')
|
|
63
|
+
.option(
|
|
64
|
+
'-r, --recursive',
|
|
65
|
+
"Use with '--folder'. Optimizes *.svg files in folders recursively."
|
|
66
|
+
)
|
|
67
|
+
.option(
|
|
68
|
+
'--exclude <PATTERN...>',
|
|
69
|
+
"Use with '--folder'. Exclude files matching regular expression pattern."
|
|
70
|
+
)
|
|
71
|
+
.option(
|
|
72
|
+
'-q, --quiet',
|
|
73
|
+
'Only output error messages, not regular status messages'
|
|
74
|
+
)
|
|
75
|
+
.option('--show-plugins', 'Show available plugins and exit')
|
|
76
|
+
// used by picocolors internally
|
|
77
|
+
.option('--no-color', 'Output plain text without color')
|
|
78
|
+
.action(action);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
async function action(args, opts, command) {
|
|
82
|
+
var input = opts.input || args;
|
|
83
|
+
var output = opts.output;
|
|
84
|
+
var config = {};
|
|
85
|
+
|
|
86
|
+
if (opts.precision != null) {
|
|
87
|
+
const number = Number.parseInt(opts.precision, 10);
|
|
88
|
+
if (Number.isNaN(number)) {
|
|
89
|
+
console.error(
|
|
90
|
+
"error: option '-p, --precision' argument must be an integer number"
|
|
91
|
+
);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
} else {
|
|
94
|
+
opts.precision = number;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (opts.datauri != null) {
|
|
99
|
+
if (
|
|
100
|
+
opts.datauri !== 'base64' &&
|
|
101
|
+
opts.datauri !== 'enc' &&
|
|
102
|
+
opts.datauri !== 'unenc'
|
|
103
|
+
) {
|
|
104
|
+
console.error(
|
|
105
|
+
"error: option '--datauri' must have one of the following values: 'base64', 'enc' or 'unenc'"
|
|
106
|
+
);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (opts.indent != null) {
|
|
112
|
+
const number = Number.parseInt(opts.indent, 10);
|
|
113
|
+
if (Number.isNaN(number)) {
|
|
114
|
+
console.error(
|
|
115
|
+
"error: option '--indent' argument must be an integer number"
|
|
116
|
+
);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
} else {
|
|
119
|
+
opts.indent = number;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (opts.eol != null && opts.eol !== 'lf' && opts.eol !== 'crlf') {
|
|
124
|
+
console.error(
|
|
125
|
+
"error: option '--eol' must have one of the following values: 'lf' or 'crlf'"
|
|
126
|
+
);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// --show-plugins
|
|
131
|
+
if (opts.showPlugins) {
|
|
132
|
+
showAvailablePlugins();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// w/o anything
|
|
137
|
+
if (
|
|
138
|
+
(input.length === 0 || input[0] === '-') &&
|
|
139
|
+
!opts.string &&
|
|
140
|
+
!opts.stdin &&
|
|
141
|
+
!opts.folder &&
|
|
142
|
+
process.stdin.isTTY === true
|
|
143
|
+
) {
|
|
144
|
+
return command.help();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (
|
|
148
|
+
typeof process == 'object' &&
|
|
149
|
+
process.versions &&
|
|
150
|
+
process.versions.node &&
|
|
151
|
+
PKG &&
|
|
152
|
+
PKG.engines.node
|
|
153
|
+
) {
|
|
154
|
+
var nodeVersion = String(PKG.engines.node).match(/\d*(\.\d+)*/)[0];
|
|
155
|
+
if (parseFloat(process.versions.node) < parseFloat(nodeVersion)) {
|
|
156
|
+
throw Error(
|
|
157
|
+
`${PKG.name} requires Node.js version ${nodeVersion} or higher.`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// --config
|
|
163
|
+
const loadedConfig = await loadConfig(opts.config);
|
|
164
|
+
if (loadedConfig != null) {
|
|
165
|
+
config = loadedConfig;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// --quiet
|
|
169
|
+
if (opts.quiet) {
|
|
170
|
+
config.quiet = opts.quiet;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// --recursive
|
|
174
|
+
if (opts.recursive) {
|
|
175
|
+
config.recursive = opts.recursive;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// --exclude
|
|
179
|
+
config.exclude = opts.exclude
|
|
180
|
+
? opts.exclude.map((pattern) => RegExp(pattern))
|
|
181
|
+
: [];
|
|
182
|
+
|
|
183
|
+
// --precision
|
|
184
|
+
if (opts.precision != null) {
|
|
185
|
+
var precision = Math.min(Math.max(0, opts.precision), 20);
|
|
186
|
+
config.floatPrecision = precision;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// --multipass
|
|
190
|
+
if (opts.multipass) {
|
|
191
|
+
config.multipass = true;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// --pretty
|
|
195
|
+
if (opts.pretty) {
|
|
196
|
+
config.js2svg = config.js2svg || {};
|
|
197
|
+
config.js2svg.pretty = true;
|
|
198
|
+
if (opts.indent != null) {
|
|
199
|
+
config.js2svg.indent = opts.indent;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// --eol
|
|
204
|
+
if (opts.eol) {
|
|
205
|
+
config.js2svg = config.js2svg || {};
|
|
206
|
+
config.js2svg.eol = opts.eol;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// --final-newline
|
|
210
|
+
if (opts.finalNewline) {
|
|
211
|
+
config.js2svg = config.js2svg || {};
|
|
212
|
+
config.js2svg.finalNewline = true;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// --output
|
|
216
|
+
if (output) {
|
|
217
|
+
if (input.length && input[0] != '-') {
|
|
218
|
+
if (output.length == 1 && checkIsDir(output[0])) {
|
|
219
|
+
var dir = output[0];
|
|
220
|
+
for (var i = 0; i < input.length; i++) {
|
|
221
|
+
output[i] = checkIsDir(input[i])
|
|
222
|
+
? input[i]
|
|
223
|
+
: path.resolve(dir, path.basename(input[i]));
|
|
224
|
+
}
|
|
225
|
+
} else if (output.length < input.length) {
|
|
226
|
+
output = output.concat(input.slice(output.length));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
} else if (input.length) {
|
|
230
|
+
output = input;
|
|
231
|
+
} else if (opts.string) {
|
|
232
|
+
output = '-';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (opts.datauri) {
|
|
236
|
+
config.datauri = opts.datauri;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// --folder
|
|
240
|
+
if (opts.folder) {
|
|
241
|
+
var ouputFolder = (output && output[0]) || opts.folder;
|
|
242
|
+
await optimizeFolder(config, opts.folder, ouputFolder);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// --input
|
|
246
|
+
if (input.length !== 0) {
|
|
247
|
+
// STDIN
|
|
248
|
+
if (input[0] === '-') {
|
|
249
|
+
return new Promise((resolve, reject) => {
|
|
250
|
+
var data = '',
|
|
251
|
+
file = output[0];
|
|
252
|
+
|
|
253
|
+
process.stdin
|
|
254
|
+
.on('data', (chunk) => (data += chunk))
|
|
255
|
+
.once('end', () =>
|
|
256
|
+
processSVGData(config, { input: 'string' }, data, file).then(
|
|
257
|
+
resolve,
|
|
258
|
+
reject
|
|
259
|
+
)
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
// file
|
|
263
|
+
} else {
|
|
264
|
+
await Promise.all(
|
|
265
|
+
input.map((file, n) => optimizeFile(config, file, output[n]))
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// --string
|
|
270
|
+
} else if (opts.string) {
|
|
271
|
+
var data = decodeSVGDatauri(opts.string);
|
|
272
|
+
|
|
273
|
+
return processSVGData(config, { input: 'string' }, data, output[0]);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Optimize SVG files in a directory.
|
|
279
|
+
* @param {Object} config options
|
|
280
|
+
* @param {string} dir input directory
|
|
281
|
+
* @param {string} output output directory
|
|
282
|
+
* @return {Promise}
|
|
283
|
+
*/
|
|
284
|
+
function optimizeFolder(config, dir, output) {
|
|
285
|
+
if (!config.quiet) {
|
|
286
|
+
console.log(`Processing directory '${dir}':\n`);
|
|
287
|
+
}
|
|
288
|
+
return fs.promises
|
|
289
|
+
.readdir(dir)
|
|
290
|
+
.then((files) => processDirectory(config, dir, files, output));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Process given files, take only SVG.
|
|
295
|
+
* @param {Object} config options
|
|
296
|
+
* @param {string} dir input directory
|
|
297
|
+
* @param {Array} files list of file names in the directory
|
|
298
|
+
* @param {string} output output directory
|
|
299
|
+
* @return {Promise}
|
|
300
|
+
*/
|
|
301
|
+
function processDirectory(config, dir, files, output) {
|
|
302
|
+
// take only *.svg files, recursively if necessary
|
|
303
|
+
var svgFilesDescriptions = getFilesDescriptions(config, dir, files, output);
|
|
304
|
+
|
|
305
|
+
return svgFilesDescriptions.length
|
|
306
|
+
? Promise.all(
|
|
307
|
+
svgFilesDescriptions.map((fileDescription) =>
|
|
308
|
+
optimizeFile(
|
|
309
|
+
config,
|
|
310
|
+
fileDescription.inputPath,
|
|
311
|
+
fileDescription.outputPath
|
|
312
|
+
)
|
|
313
|
+
)
|
|
314
|
+
)
|
|
315
|
+
: Promise.reject(
|
|
316
|
+
new Error(`No SVG files have been found in '${dir}' directory.`)
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Get svg files descriptions
|
|
322
|
+
* @param {Object} config options
|
|
323
|
+
* @param {string} dir input directory
|
|
324
|
+
* @param {Array} files list of file names in the directory
|
|
325
|
+
* @param {string} output output directory
|
|
326
|
+
* @return {Array}
|
|
327
|
+
*/
|
|
328
|
+
function getFilesDescriptions(config, dir, files, output) {
|
|
329
|
+
const filesInThisFolder = files
|
|
330
|
+
.filter(
|
|
331
|
+
(name) =>
|
|
332
|
+
regSVGFile.test(name) &&
|
|
333
|
+
!config.exclude.some((regExclude) => regExclude.test(name))
|
|
334
|
+
)
|
|
335
|
+
.map((name) => ({
|
|
336
|
+
inputPath: path.resolve(dir, name),
|
|
337
|
+
outputPath: path.resolve(output, name),
|
|
338
|
+
}));
|
|
339
|
+
|
|
340
|
+
return config.recursive
|
|
341
|
+
? [].concat(
|
|
342
|
+
filesInThisFolder,
|
|
343
|
+
files
|
|
344
|
+
.filter((name) => checkIsDir(path.resolve(dir, name)))
|
|
345
|
+
.map((subFolderName) => {
|
|
346
|
+
const subFolderPath = path.resolve(dir, subFolderName);
|
|
347
|
+
const subFolderFiles = fs.readdirSync(subFolderPath);
|
|
348
|
+
const subFolderOutput = path.resolve(output, subFolderName);
|
|
349
|
+
return getFilesDescriptions(
|
|
350
|
+
config,
|
|
351
|
+
subFolderPath,
|
|
352
|
+
subFolderFiles,
|
|
353
|
+
subFolderOutput
|
|
354
|
+
);
|
|
355
|
+
})
|
|
356
|
+
.reduce((a, b) => [].concat(a, b), [])
|
|
357
|
+
)
|
|
358
|
+
: filesInThisFolder;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Read SVG file and pass to processing.
|
|
363
|
+
* @param {Object} config options
|
|
364
|
+
* @param {string} file
|
|
365
|
+
* @param {string} output
|
|
366
|
+
* @return {Promise}
|
|
367
|
+
*/
|
|
368
|
+
function optimizeFile(config, file, output) {
|
|
369
|
+
return fs.promises.readFile(file, 'utf8').then(
|
|
370
|
+
(data) =>
|
|
371
|
+
processSVGData(config, { input: 'file', path: file }, data, output, file),
|
|
372
|
+
(error) => checkOptimizeFileError(config, file, output, error)
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Optimize SVG data.
|
|
378
|
+
* @param {Object} config options
|
|
379
|
+
* @param {string} data SVG content to optimize
|
|
380
|
+
* @param {string} output where to write optimized file
|
|
381
|
+
* @param {string} [input] input file name (being used if output is a directory)
|
|
382
|
+
* @return {Promise}
|
|
383
|
+
*/
|
|
384
|
+
function processSVGData(config, info, data, output, input) {
|
|
385
|
+
var startTime = Date.now(),
|
|
386
|
+
prevFileSize = Buffer.byteLength(data, 'utf8');
|
|
387
|
+
|
|
388
|
+
const result = optimize(data, { ...config, ...info });
|
|
389
|
+
if (result.modernError) {
|
|
390
|
+
console.error(colors.red(result.modernError.toString()));
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
if (config.datauri) {
|
|
394
|
+
result.data = encodeSVGDatauri(result.data, config.datauri);
|
|
395
|
+
}
|
|
396
|
+
var resultFileSize = Buffer.byteLength(result.data, 'utf8'),
|
|
397
|
+
processingTime = Date.now() - startTime;
|
|
398
|
+
|
|
399
|
+
return writeOutput(input, output, result.data).then(
|
|
400
|
+
function () {
|
|
401
|
+
if (!config.quiet && output != '-') {
|
|
402
|
+
if (input) {
|
|
403
|
+
console.log(`\n${path.basename(input)}:`);
|
|
404
|
+
}
|
|
405
|
+
printTimeInfo(processingTime);
|
|
406
|
+
printProfitInfo(prevFileSize, resultFileSize);
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
(error) =>
|
|
410
|
+
Promise.reject(
|
|
411
|
+
new Error(
|
|
412
|
+
error.code === 'ENOTDIR'
|
|
413
|
+
? `Error: output '${output}' is not a directory.`
|
|
414
|
+
: error
|
|
415
|
+
)
|
|
416
|
+
)
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Write result of an optimization.
|
|
422
|
+
* @param {string} input
|
|
423
|
+
* @param {string} output output file name. '-' for stdout
|
|
424
|
+
* @param {string} data data to write
|
|
425
|
+
* @return {Promise}
|
|
426
|
+
*/
|
|
427
|
+
function writeOutput(input, output, data) {
|
|
428
|
+
if (output == '-') {
|
|
429
|
+
console.log(data);
|
|
430
|
+
return Promise.resolve();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
434
|
+
|
|
435
|
+
return fs.promises
|
|
436
|
+
.writeFile(output, data, 'utf8')
|
|
437
|
+
.catch((error) => checkWriteFileError(input, output, data, error));
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Write a time taken by optimization.
|
|
442
|
+
* @param {number} time time in milliseconds.
|
|
443
|
+
*/
|
|
444
|
+
function printTimeInfo(time) {
|
|
445
|
+
console.log(`Done in ${time} ms!`);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Write optimizing information in human readable format.
|
|
450
|
+
* @param {number} inBytes size before optimization.
|
|
451
|
+
* @param {number} outBytes size after optimization.
|
|
452
|
+
*/
|
|
453
|
+
function printProfitInfo(inBytes, outBytes) {
|
|
454
|
+
var profitPercents = 100 - (outBytes * 100) / inBytes;
|
|
455
|
+
|
|
456
|
+
console.log(
|
|
457
|
+
Math.round((inBytes / 1024) * 1000) / 1000 +
|
|
458
|
+
' KiB' +
|
|
459
|
+
(profitPercents < 0 ? ' + ' : ' - ') +
|
|
460
|
+
colors.green(Math.abs(Math.round(profitPercents * 10) / 10) + '%') +
|
|
461
|
+
' = ' +
|
|
462
|
+
Math.round((outBytes / 1024) * 1000) / 1000 +
|
|
463
|
+
' KiB'
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Check for errors, if it's a dir optimize the dir.
|
|
469
|
+
* @param {Object} config
|
|
470
|
+
* @param {string} input
|
|
471
|
+
* @param {string} output
|
|
472
|
+
* @param {Error} error
|
|
473
|
+
* @return {Promise}
|
|
474
|
+
*/
|
|
475
|
+
function checkOptimizeFileError(config, input, output, error) {
|
|
476
|
+
if (error.code == 'EISDIR') {
|
|
477
|
+
return optimizeFolder(config, input, output);
|
|
478
|
+
} else if (error.code == 'ENOENT') {
|
|
479
|
+
return Promise.reject(
|
|
480
|
+
new Error(`Error: no such file or directory '${error.path}'.`)
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
return Promise.reject(error);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Check for saving file error. If the output is a dir, then write file there.
|
|
488
|
+
* @param {string} input
|
|
489
|
+
* @param {string} output
|
|
490
|
+
* @param {string} data
|
|
491
|
+
* @param {Error} error
|
|
492
|
+
* @return {Promise}
|
|
493
|
+
*/
|
|
494
|
+
function checkWriteFileError(input, output, data, error) {
|
|
495
|
+
if (error.code == 'EISDIR' && input) {
|
|
496
|
+
return fs.promises.writeFile(
|
|
497
|
+
path.resolve(output, path.basename(input)),
|
|
498
|
+
data,
|
|
499
|
+
'utf8'
|
|
500
|
+
);
|
|
501
|
+
} else {
|
|
502
|
+
return Promise.reject(error);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Show list of available plugins with short description.
|
|
508
|
+
*/
|
|
509
|
+
function showAvailablePlugins() {
|
|
510
|
+
const list = Object.entries(pluginsMap)
|
|
511
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
512
|
+
.map(([name, plugin]) => ` [ ${colors.green(name)} ] ${plugin.description}`)
|
|
513
|
+
.join('\n');
|
|
514
|
+
console.log('Currently available plugins:\n' + list);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
module.exports.checkIsDir = checkIsDir;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const pluginsMap = require('../../plugins/plugins.js');
|
|
4
|
+
|
|
5
|
+
const pluginsOrder = [
|
|
6
|
+
'removeDoctype',
|
|
7
|
+
'removeXMLProcInst',
|
|
8
|
+
'removeComments',
|
|
9
|
+
'removeMetadata',
|
|
10
|
+
'removeXMLNS',
|
|
11
|
+
'removeEditorsNSData',
|
|
12
|
+
'cleanupAttrs',
|
|
13
|
+
'mergeStyles',
|
|
14
|
+
'inlineStyles',
|
|
15
|
+
'minifyStyles',
|
|
16
|
+
'convertStyleToAttrs',
|
|
17
|
+
'cleanupIDs',
|
|
18
|
+
'prefixIds',
|
|
19
|
+
'removeRasterImages',
|
|
20
|
+
'removeUselessDefs',
|
|
21
|
+
'cleanupNumericValues',
|
|
22
|
+
'cleanupListOfValues',
|
|
23
|
+
'convertColors',
|
|
24
|
+
'removeUnknownsAndDefaults',
|
|
25
|
+
'removeNonInheritableGroupAttrs',
|
|
26
|
+
'removeUselessStrokeAndFill',
|
|
27
|
+
'removeViewBox',
|
|
28
|
+
'cleanupEnableBackground',
|
|
29
|
+
'removeHiddenElems',
|
|
30
|
+
'removeEmptyText',
|
|
31
|
+
'convertShapeToPath',
|
|
32
|
+
'convertEllipseToCircle',
|
|
33
|
+
'moveElemsAttrsToGroup',
|
|
34
|
+
'moveGroupAttrsToElems',
|
|
35
|
+
'collapseGroups',
|
|
36
|
+
'convertPathData',
|
|
37
|
+
'convertTransform',
|
|
38
|
+
'removeEmptyAttrs',
|
|
39
|
+
'removeEmptyContainers',
|
|
40
|
+
'mergePaths',
|
|
41
|
+
'removeUnusedNS',
|
|
42
|
+
'sortAttrs',
|
|
43
|
+
'sortDefsChildren',
|
|
44
|
+
'removeTitle',
|
|
45
|
+
'removeDesc',
|
|
46
|
+
'removeDimensions',
|
|
47
|
+
'removeAttrs',
|
|
48
|
+
'removeAttributesBySelector',
|
|
49
|
+
'removeElementsByAttr',
|
|
50
|
+
'addClassesToSVGElement',
|
|
51
|
+
'removeStyleElement',
|
|
52
|
+
'removeScriptElement',
|
|
53
|
+
'addAttributesToSVGElement',
|
|
54
|
+
'removeOffCanvasPaths',
|
|
55
|
+
'reusePaths',
|
|
56
|
+
];
|
|
57
|
+
const defaultPlugins = pluginsOrder.filter((name) => pluginsMap[name].active);
|
|
58
|
+
exports.defaultPlugins = defaultPlugins;
|
|
59
|
+
|
|
60
|
+
const extendDefaultPlugins = (plugins) => {
|
|
61
|
+
console.warn(
|
|
62
|
+
'\n"extendDefaultPlugins" utility is deprecated.\n' +
|
|
63
|
+
'Use "preset-default" plugin with overrides instead.\n' +
|
|
64
|
+
'For example:\n' +
|
|
65
|
+
`{\n` +
|
|
66
|
+
` name: 'preset-default',\n` +
|
|
67
|
+
` params: {\n` +
|
|
68
|
+
` overrides: {\n` +
|
|
69
|
+
` // customize plugin options\n` +
|
|
70
|
+
` convertShapeToPath: {\n` +
|
|
71
|
+
` convertArcs: true\n` +
|
|
72
|
+
` },\n` +
|
|
73
|
+
` // disable plugins\n` +
|
|
74
|
+
` convertPathData: false\n` +
|
|
75
|
+
` }\n` +
|
|
76
|
+
` }\n` +
|
|
77
|
+
`}\n`
|
|
78
|
+
);
|
|
79
|
+
const extendedPlugins = pluginsOrder.map((name) => ({
|
|
80
|
+
name,
|
|
81
|
+
active: pluginsMap[name].active,
|
|
82
|
+
}));
|
|
83
|
+
for (const plugin of plugins) {
|
|
84
|
+
const resolvedPlugin = resolvePluginConfig(plugin);
|
|
85
|
+
const index = pluginsOrder.indexOf(resolvedPlugin.name);
|
|
86
|
+
if (index === -1) {
|
|
87
|
+
extendedPlugins.push(plugin);
|
|
88
|
+
} else {
|
|
89
|
+
extendedPlugins[index] = plugin;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return extendedPlugins;
|
|
93
|
+
};
|
|
94
|
+
exports.extendDefaultPlugins = extendDefaultPlugins;
|
|
95
|
+
|
|
96
|
+
const resolvePluginConfig = (plugin) => {
|
|
97
|
+
let configParams = {};
|
|
98
|
+
if (typeof plugin === 'string') {
|
|
99
|
+
// resolve builtin plugin specified as string
|
|
100
|
+
const pluginConfig = pluginsMap[plugin];
|
|
101
|
+
if (pluginConfig == null) {
|
|
102
|
+
throw Error(`Unknown builtin plugin "${plugin}" specified.`);
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
...pluginConfig,
|
|
106
|
+
name: plugin,
|
|
107
|
+
active: true,
|
|
108
|
+
params: { ...pluginConfig.params, ...configParams },
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (typeof plugin === 'object' && plugin != null) {
|
|
112
|
+
if (plugin.name == null) {
|
|
113
|
+
throw Error(`Plugin name should be specified`);
|
|
114
|
+
}
|
|
115
|
+
if (plugin.fn) {
|
|
116
|
+
// resolve custom plugin with implementation
|
|
117
|
+
return {
|
|
118
|
+
active: true,
|
|
119
|
+
...plugin,
|
|
120
|
+
params: { ...configParams, ...plugin.params },
|
|
121
|
+
};
|
|
122
|
+
} else {
|
|
123
|
+
// resolve builtin plugin specified as object without implementation
|
|
124
|
+
const pluginConfig = pluginsMap[plugin.name];
|
|
125
|
+
if (pluginConfig == null) {
|
|
126
|
+
throw Error(`Unknown builtin plugin "${plugin.name}" specified.`);
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
...pluginConfig,
|
|
130
|
+
active: true,
|
|
131
|
+
...plugin,
|
|
132
|
+
params: { ...pluginConfig.params, ...configParams, ...plugin.params },
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
};
|
|
138
|
+
exports.resolvePluginConfig = resolvePluginConfig;
|