html-minifier-next 5.0.6 → 5.1.1

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.
@@ -1,24 +0,0 @@
1
- /**
2
- * Minify SVG attribute value based on attribute name
3
- * @param {string} name - Attribute name
4
- * @param {string} value - Attribute value
5
- * @param {Object} options - Minification options
6
- * @returns {string} Minified attribute value
7
- */
8
- export function minifySVGAttributeValue(name: string, value: string, options?: any): string;
9
- /**
10
- * Check if an SVG attribute can be removed
11
- * @param {string} tag - Element tag name (e.g., `svg`, `rect`, `path`)
12
- * @param {string} name - Attribute name
13
- * @param {string} value - Attribute value
14
- * @param {Object} options - Minification options
15
- * @returns {boolean} True if attribute should be removed
16
- */
17
- export function shouldRemoveSVGAttribute(tag: string, name: string, value: string, options?: any): boolean;
18
- /**
19
- * Get default SVG minification options
20
- * @param {Object} userOptions - User-provided options
21
- * @returns {Object} Complete options object with defaults
22
- */
23
- export function getSVGMinifierOptions(userOptions: any): any;
24
- //# sourceMappingURL=svg.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"svg.d.ts","sourceRoot":"","sources":["../../../src/lib/svg.js"],"names":[],"mappings":"AA0VA;;;;;;GAMG;AACH,8CALW,MAAM,SACN,MAAM,kBAEJ,MAAM,CA0BlB;AAED;;;;;;;GAOG;AACH,8CANW,MAAM,QACN,MAAM,SACN,MAAM,kBAEJ,OAAO,CAanB;AAED;;;;GAIG;AACH,6DAkBC"}
package/src/lib/svg.js DELETED
@@ -1,424 +0,0 @@
1
- /**
2
- * Lightweight SVG optimizations:
3
- * - Numeric precision reduction for coordinates and path data
4
- * - Whitespace removal in attribute values (numeric sequences)
5
- * - Default attribute removal (safe, well-documented defaults)
6
- * - Color minification (hex shortening, rgb() to hex, named colors)
7
- * - Identity transform removal
8
- * - Path data space optimization
9
- */
10
-
11
- // Imports
12
-
13
- import { LRU } from './utils.js';
14
- import { RE_NUMERIC_VALUE } from './constants.js';
15
-
16
- // Cache for minified numbers
17
- const numberCache = new LRU(100);
18
-
19
- /**
20
- * Named colors that are shorter than their hex equivalents
21
- * Only includes cases where using the name saves bytes
22
- */
23
- const NAMED_COLORS = {
24
- '#f00': 'red', // #f00 (4) → red (3), saves 1
25
- '#c0c0c0': 'silver', // #c0c0c0 (7) → silver (6), saves 1
26
- '#808080': 'gray', // #808080 (7) → gray (4), saves 3
27
- '#800000': 'maroon', // #800000 (7) → maroon (6), saves 1
28
- '#808000': 'olive', // #808000 (7) → olive (5), saves 2
29
- '#008000': 'green', // #008000 (7) → green (5), saves 2
30
- '#800080': 'purple', // #800080 (7) → purple (6), saves 1
31
- '#008080': 'teal', // #008080 (7) → teal (4), saves 3
32
- '#000080': 'navy', // #000080 (7) → navy (4), saves 3
33
- '#ffa500': 'orange' // #ffa500 (7) → orange (6), saves 1
34
- };
35
-
36
- /**
37
- * Default SVG attribute values that can be safely removed
38
- * Only includes well-documented, widely-supported defaults
39
- */
40
- const SVG_DEFAULT_ATTRS = {
41
- // Fill and stroke defaults
42
- fill: value => value === 'black' || value === '#000' || value === '#000000',
43
- 'fill-opacity': value => value === '1',
44
- 'fill-rule': value => value === 'nonzero',
45
- stroke: value => value === 'none',
46
- 'stroke-dasharray': value => value === 'none',
47
- 'stroke-dashoffset': value => value === '0',
48
- 'stroke-linecap': value => value === 'butt',
49
- 'stroke-linejoin': value => value === 'miter',
50
- 'stroke-miterlimit': value => value === '4',
51
- 'stroke-opacity': value => value === '1',
52
- 'stroke-width': value => value === '1',
53
-
54
- // Text and font defaults
55
- 'font-family': value => value === 'inherit',
56
- 'font-size': value => value === 'medium',
57
- 'font-style': value => value === 'normal',
58
- 'font-variant': value => value === 'normal',
59
- 'font-weight': value => value === 'normal',
60
- 'letter-spacing': value => value === 'normal',
61
- 'text-decoration': value => value === 'none',
62
- 'text-anchor': value => value === 'start',
63
-
64
- // Other common defaults
65
- opacity: value => value === '1',
66
- visibility: value => value === 'visible',
67
- display: value => value === 'inline',
68
- // Note: Overflow handled especially in `isDefaultAttribute` (not safe for root `<svg>`)
69
-
70
- // Clipping and masking defaults
71
- 'clip-rule': value => value === 'nonzero',
72
- 'clip-path': value => value === 'none',
73
- mask: value => value === 'none',
74
-
75
- // Marker defaults
76
- 'marker-start': value => value === 'none',
77
- 'marker-mid': value => value === 'none',
78
- 'marker-end': value => value === 'none',
79
-
80
- // Filter and color defaults
81
- filter: value => value === 'none',
82
- 'color-interpolation': value => value === 'sRGB',
83
- 'color-interpolation-filters': value => value === 'linearRGB'
84
- };
85
-
86
- /**
87
- * Minify numeric value by removing trailing zeros and unnecessary decimals
88
- * @param {string} num - Numeric string to minify
89
- * @param {number} precision - Maximum decimal places to keep
90
- * @returns {string} Minified numeric string
91
- */
92
- function minifyNumber(num, precision = 3) {
93
- // Fast path for common values (avoids parsing and caching)
94
- if (num === '0' || num === '1') return num;
95
- // Common decimal variants that tools export
96
- if (num === '0.0' || num === '0.00' || num === '0.000') return '0';
97
- if (num === '1.0' || num === '1.00' || num === '1.000') return '1';
98
-
99
- // Check cache
100
- // (Note: Uses input string as key, so “0.0000” and “0.00000” create separate entries.
101
- // This is intentional to avoid parsing overhead.
102
- // Real-world SVG files from export tools typically use consistent formats.)
103
- const cacheKey = `${num}:${precision}`;
104
- const cached = numberCache.get(cacheKey);
105
- if (cached !== undefined) return cached;
106
-
107
- const parsed = parseFloat(num);
108
-
109
- // Handle special cases
110
- if (isNaN(parsed)) return num;
111
- if (parsed === 0) return '0';
112
- if (!isFinite(parsed)) return num;
113
-
114
- // Convert to fixed precision, then remove trailing zeros
115
- const fixed = parsed.toFixed(precision);
116
- const trimmed = fixed.replace(/\.?0+$/, '');
117
-
118
- // Remove leading zero before decimal point (e.g., `0.5` → `.5`, `-0.3` → `-.3`)
119
- const result = (trimmed || '0').replace(/^(-?)0\./, '$1.');
120
- numberCache.set(cacheKey, result);
121
- return result;
122
- }
123
-
124
- /**
125
- * Minify SVG path data by reducing numeric precision and removing unnecessary spaces
126
- * @param {string} pathData - SVG path data string
127
- * @param {number} precision - Decimal precision for coordinates
128
- * @returns {string} Minified path data
129
- */
130
- function minifyPathData(pathData, precision = 3) {
131
- if (!pathData || typeof pathData !== 'string') return pathData;
132
-
133
- // First, minify all numbers
134
- let result = pathData.replace(RE_NUMERIC_VALUE, (match) => {
135
- return minifyNumber(match, precision);
136
- });
137
-
138
- // Remove unnecessary spaces around path commands
139
- // Safe to remove space after a command letter when it’s followed by a number
140
- // (which may be negative or start with a decimal point)
141
- // `M 10 20` → `M10 20`, `L -5 -3` → `L-5-3`, `M .5 .3` → `M.5.3`
142
- result = result.replace(/([MLHVCSQTAZmlhvcsqtaz])\s+(?=-?\.?\d)/g, '$1');
143
-
144
- // Safe to remove space before command letter when preceded by a number
145
- // `0 L` → `0L`, `20 M` → `20M`, `.5 L` → `.5L`
146
- result = result.replace(/([\d.])\s+([MLHVCSQTAZmlhvcsqtaz])/g, '$1$2');
147
-
148
- // Safe to remove space before negative number when preceded by a number
149
- // `10 -20` → `10-20`, `.5 -.3` → `.5-.3` (minus sign is always a separator)
150
- result = result.replace(/([\d.])\s+(-)/g, '$1$2');
151
-
152
- // Safe to remove space between two decimal numbers (decimal point acts as separator)
153
- // `.5 .3` → `.5.3` (only when previous char is `.`, indicating a complete decimal)
154
- // Note: `0 .3` must not become `0.3` (that would change two numbers into one)
155
- result = result.replace(/(\.\d*)\s+(\.)/g, '$1$2');
156
-
157
- return result;
158
- }
159
-
160
- /**
161
- * Minify whitespace in numeric attribute values
162
- * Examples:
163
- * - “10 , 20" → "10,20"
164
- * - "translate( 10 20 )" → "translate(10 20)"
165
- * - "100, 10 40, 198" → "100,10 40,198"
166
- *
167
- * @param {string} value - Attribute value to minify
168
- * @returns {string} Minified value
169
- */
170
- function minifyAttributeWhitespace(value) {
171
- if (!value || typeof value !== 'string') return value;
172
-
173
- return value
174
- // Remove spaces around commas
175
- .replace(/\s*,\s*/g, ',')
176
- // Remove spaces around parentheses
177
- .replace(/\(\s+/g, '(')
178
- .replace(/\s+\)/g, ')')
179
- // Collapse multiple spaces to single space
180
- .replace(/\s+/g, ' ')
181
- // Trim leading/trailing whitespace
182
- .trim();
183
- }
184
-
185
- /**
186
- * Minify color values (hex shortening, rgb to hex conversion, named colors)
187
- * Only processes simple color values; preserves case-sensitive references like `url(#id)`
188
- * @param {string} color - Color value to minify
189
- * @returns {string} Minified color value
190
- */
191
- function minifyColor(color) {
192
- if (!color || typeof color !== 'string') return color;
193
-
194
- const trimmed = color.trim();
195
-
196
- // Don’t process values that aren’t simple colors (preserve case-sensitive references)
197
- // `url(#id)`, `var(--name)`, `inherit`, `currentColor`, etc.
198
- if (trimmed.includes('url(') || trimmed.includes('var(') || trimmed === 'inherit' || trimmed === 'currentColor') {
199
- return trimmed;
200
- }
201
-
202
- // Now safe to lowercase for color matching
203
- const lower = trimmed.toLowerCase();
204
-
205
- // Shorten 6-digit hex to 3-digit when possible
206
- // `#aabbcc` → `#abc`, `#000000` → `#000`
207
- const hexMatch = lower.match(/^#([0-9a-f]{6})$/);
208
- if (hexMatch) {
209
- const hex = hexMatch[1];
210
- if (hex[0] === hex[1] && hex[2] === hex[3] && hex[4] === hex[5]) {
211
- const shortened = '#' + hex[0] + hex[2] + hex[4];
212
- // Try to use named color if shorter
213
- return NAMED_COLORS[shortened] || shortened;
214
- }
215
- // Can’t shorten, but check for named color
216
- return NAMED_COLORS[lower] || lower;
217
- }
218
-
219
- // Match 3-digit hex colors
220
- const hex3Match = lower.match(/^#[0-9a-f]{3}$/);
221
- if (hex3Match) {
222
- // Check if there’s a shorter named color
223
- return NAMED_COLORS[lower] || lower;
224
- }
225
-
226
- // Convert rgb() to hex
227
- const rgbMatch = lower.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/);
228
- if (rgbMatch) {
229
- const r = parseInt(rgbMatch[1], 10);
230
- const g = parseInt(rgbMatch[2], 10);
231
- const b = parseInt(rgbMatch[3], 10);
232
-
233
- if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) {
234
- const toHex = (n) => {
235
- const h = n.toString(16);
236
- return h.length === 1 ? '0' + h : h;
237
- };
238
- const hexColor = '#' + toHex(r) + toHex(g) + toHex(b);
239
-
240
- // Try to shorten if possible
241
- if (hexColor[1] === hexColor[2] && hexColor[3] === hexColor[4] && hexColor[5] === hexColor[6]) {
242
- const shortened = '#' + hexColor[1] + hexColor[3] + hexColor[5];
243
- return NAMED_COLORS[shortened] || shortened;
244
- }
245
- return NAMED_COLORS[hexColor] || hexColor;
246
- }
247
- }
248
-
249
- // Not a recognized color format, return as-is (preserves case)
250
- return trimmed;
251
- }
252
-
253
- // Attributes that contain numeric sequences or path data
254
- const NUMERIC_ATTRS = new Set([
255
- 'd', // Path data
256
- 'points', // Polygon/polyline points
257
- 'viewBox', // `viewBox` coordinates
258
- 'transform', // Transform functions
259
- 'x', 'y', 'x1', 'y1', 'x2', 'y2', // Coordinates
260
- 'cx', 'cy', 'r', 'rx', 'ry', // Circle/ellipse
261
- 'width', 'height', // Dimensions
262
- 'dx', 'dy', // Text offsets
263
- 'offset', // Gradient offset
264
- 'startOffset', // `textPath`
265
- 'pathLength', // Path length
266
- 'stdDeviation', // Filter params
267
- 'baseFrequency', // Turbulence
268
- 'k1', 'k2', 'k3', 'k4' // Composite filter
269
- ]);
270
-
271
- // Attributes that contain color values
272
- const COLOR_ATTRS = new Set([
273
- 'fill',
274
- 'stroke',
275
- 'stop-color',
276
- 'flood-color',
277
- 'lighting-color'
278
- ]);
279
-
280
- // Pre-compiled regexes for identity transform detection (compiled once at module load)
281
- // Separator pattern: Accepts comma with optional spaces or one or more spaces
282
- const SEP = '(?:\\s*,\\s*|\\s+)';
283
-
284
- // `translate(0)`, `translate(0,0)`, `translate(0 0)` (matches 0, 0.0, 0.00, etc.)
285
- const IDENTITY_TRANSLATE_RE = new RegExp(`^translate\\s*\\(\\s*0(?:\\.0+)?\\s*(?:${SEP}0(?:\\.0+)?\\s*)?\\)$`, 'i');
286
-
287
- // `scale(1)`, `scale(1,1)`, `scale(1 1)` (matches 1, 1.0, 1.00, etc.)
288
- const IDENTITY_SCALE_RE = new RegExp(`^scale\\s*\\(\\s*1(?:\\.0+)?\\s*(?:${SEP}1(?:\\.0+)?\\s*)?\\)$`, 'i');
289
-
290
- // `rotate(0)`, `rotate(0 cx cy)`, `rotate(0, cx, cy)` (matches 0, 0.0, 0.00, etc.)
291
- // Note: `cx` and `cy` must be valid numbers if present
292
- const IDENTITY_ROTATE_RE = new RegExp(`^rotate\\s*\\(\\s*0(?:\\.0+)?\\s*(?:${SEP}-?\\d+(?:\\.\\d+)?${SEP}-?\\d+(?:\\.\\d+)?)?\\s*\\)$`, 'i');
293
-
294
- // `skewX(0)`, `skewY(0)` (matches 0, 0.0, 0.00, etc.)
295
- const IDENTITY_SKEW_RE = /^skew[XY]\s*\(\s*0(?:\.0+)?\s*\)$/i;
296
-
297
- // `matrix(1,0,0,1,0,0)`, `matrix(1 0 0 1 0 0)`—identity matrix (matches 1.0/0.0 variants)
298
- const IDENTITY_MATRIX_RE = new RegExp(`^matrix\\s*\\(\\s*1(?:\\.0+)?\\s*${SEP}0(?:\\.0+)?\\s*${SEP}0(?:\\.0+)?\\s*${SEP}1(?:\\.0+)?\\s*${SEP}0(?:\\.0+)?\\s*${SEP}0(?:\\.0+)?\\s*\\)$`, 'i');
299
-
300
- /**
301
- * Check if a transform attribute has no effect (identity transform)
302
- * @param {string} transform - Transform attribute value
303
- * @returns {boolean} True if transform is an identity (has no effect)
304
- */
305
- function isIdentityTransform(transform) {
306
- if (!transform || typeof transform !== 'string') return false;
307
-
308
- const trimmed = transform.trim();
309
-
310
- // Check for common identity transforms using pre-compiled regexes
311
- return IDENTITY_TRANSLATE_RE.test(trimmed) ||
312
- IDENTITY_SCALE_RE.test(trimmed) ||
313
- IDENTITY_ROTATE_RE.test(trimmed) ||
314
- IDENTITY_SKEW_RE.test(trimmed) ||
315
- IDENTITY_MATRIX_RE.test(trimmed);
316
- }
317
-
318
- /**
319
- * Check if an attribute should be removed based on default value
320
- * @param {string} tag - Element tag name (e.g., `svg`, `rect`, `path`)
321
- * @param {string} name - Attribute name
322
- * @param {string} value - Attribute value
323
- * @returns {boolean} True if attribute can be removed
324
- */
325
- function isDefaultAttribute(tag, name, value) {
326
- // Special case: `overflow="visible"` is unsafe for root `<svg>` element
327
- // Root SVG may need explicit `overflow="visible"` to show clipped content
328
- if (name === 'overflow' && value === 'visible') {
329
- return tag !== 'svg'; // Only remove for non-root SVG elements
330
- }
331
-
332
- const checker = SVG_DEFAULT_ATTRS[name];
333
- if (!checker) return false;
334
-
335
- // Special case: Don’t remove `fill="black"` if stroke exists without fill
336
- // This would change the rendering (stroke-only shapes would gain black fill)
337
- if (name === 'fill' && checker(value)) {
338
- // This check would require looking at other attributes on the same element
339
- // For safety, we’ll keep this conservative and not remove `fill="black"`
340
- // in the initial implementation. Can be refined later.
341
- return false;
342
- }
343
-
344
- return checker(value);
345
- }
346
-
347
- /**
348
- * Minify SVG attribute value based on attribute name
349
- * @param {string} name - Attribute name
350
- * @param {string} value - Attribute value
351
- * @param {Object} options - Minification options
352
- * @returns {string} Minified attribute value
353
- */
354
- export function minifySVGAttributeValue(name, value, options = {}) {
355
- if (!value || typeof value !== 'string') return value;
356
-
357
- const { precision = 3, minifyColors = true } = options;
358
-
359
- // Path data gets special treatment
360
- if (name === 'd') {
361
- return minifyPathData(value, precision);
362
- }
363
-
364
- // Numeric attributes get precision reduction and whitespace minification
365
- if (NUMERIC_ATTRS.has(name)) {
366
- const minified = value.replace(RE_NUMERIC_VALUE, (match) => {
367
- return minifyNumber(match, precision);
368
- });
369
- return minifyAttributeWhitespace(minified);
370
- }
371
-
372
- // Color attributes get color minification
373
- if (minifyColors && COLOR_ATTRS.has(name)) {
374
- return minifyColor(value);
375
- }
376
-
377
- return value;
378
- }
379
-
380
- /**
381
- * Check if an SVG attribute can be removed
382
- * @param {string} tag - Element tag name (e.g., `svg`, `rect`, `path`)
383
- * @param {string} name - Attribute name
384
- * @param {string} value - Attribute value
385
- * @param {Object} options - Minification options
386
- * @returns {boolean} True if attribute should be removed
387
- */
388
- export function shouldRemoveSVGAttribute(tag, name, value, options = {}) {
389
- const { removeDefaults = true } = options;
390
-
391
- if (!removeDefaults) return false;
392
-
393
- // Check for identity transforms
394
- if (name === 'transform' && isIdentityTransform(value)) {
395
- return true;
396
- }
397
-
398
- return isDefaultAttribute(tag, name, value);
399
- }
400
-
401
- /**
402
- * Get default SVG minification options
403
- * @param {Object} userOptions - User-provided options
404
- * @returns {Object} Complete options object with defaults
405
- */
406
- export function getSVGMinifierOptions(userOptions) {
407
- if (typeof userOptions === 'boolean') {
408
- return userOptions ? {
409
- precision: 3,
410
- removeDefaults: true,
411
- minifyColors: true
412
- } : null;
413
- }
414
-
415
- if (typeof userOptions === 'object' && userOptions !== null) {
416
- return {
417
- precision: userOptions.precision ?? 3,
418
- removeDefaults: userOptions.removeDefaults ?? true,
419
- minifyColors: userOptions.minifyColors ?? true
420
- };
421
- }
422
-
423
- return null;
424
- }