cssminify2 2.0.1 → 2.1.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.
- checksums.yaml +5 -5
- data/.travis.yml +10 -5
- data/CHANGELOG.md +121 -0
- data/Dockerfile +16 -0
- data/Gemfile +1 -1
- data/README.md +501 -42
- data/cssminify2.gemspec +6 -2
- data/docs/ADVANCED_USAGE.md +616 -0
- data/docs/API_REFERENCE.md +464 -0
- data/docs/EXAMPLES.md +844 -0
- data/docs/MIGRATION_GUIDE.md +586 -0
- data/lib/cssminify2/cssmin.rb +89 -7
- data/lib/cssminify2/cssmin_enhanced.rb +424 -0
- data/lib/cssminify2/enhanced.rb +818 -0
- data/lib/cssminify2/version.rb +1 -1
- data/lib/cssminify2.rb +53 -4
- data/spec/cssminify_spec.rb +49 -34
- data/spec/tests/README +6 -0
- data/spec/tests/_munge.js +8 -0
- data/spec/tests/_munge.js.min +1 -0
- data/spec/tests/_string_combo.js +5 -0
- data/spec/tests/_string_combo.js.min +1 -0
- data/spec/tests/_string_combo2.js +4 -0
- data/spec/tests/_string_combo2.js.min +1 -0
- data/spec/tests/_string_combo3.js +5 -0
- data/spec/tests/_string_combo3.js.min +1 -0
- data/spec/tests/_syntax_error.js +73 -0
- data/spec/tests/_syntax_error.js.min +1 -0
- data/spec/tests/border-none.css +6 -1
- data/spec/tests/border-none.css.min +1 -1
- data/spec/tests/bug-flex.css +3 -0
- data/spec/tests/bug-flex.css.min +1 -0
- data/spec/tests/bug-nested-pseudoclass.css +3 -0
- data/spec/tests/bug-nested-pseudoclass.css.min +1 -0
- data/spec/tests/bug-preservetoken-calc.css +8 -0
- data/spec/tests/bug-preservetoken-calc.css.min +1 -0
- data/spec/tests/color-keyword.css +1 -0
- data/spec/tests/color-keyword.css.min +1 -0
- data/spec/tests/color.css +2 -0
- data/spec/tests/color.css.min +1 -1
- data/spec/tests/concat-charset.css +2 -2
- data/spec/tests/concat-charset.css.min +1 -1
- data/spec/tests/dataurl-singlequote-font.css +1 -1
- data/spec/tests/dataurl-validity.html +29 -0
- data/spec/tests/float.js +2 -0
- data/spec/tests/float.js.min +1 -0
- data/spec/tests/hsla-issue81.css.FAIL +4 -0
- data/spec/tests/hsla-issue81.css.min +1 -0
- data/spec/tests/ie-backslash9-hack.css +2 -0
- data/spec/tests/ie-backslash9-hack.css.min +1 -0
- data/spec/tests/issue-59.css +7 -0
- data/spec/tests/issue-59.css.min +1 -0
- data/spec/tests/issue151.css +8 -0
- data/spec/tests/issue151.css.min +1 -0
- data/spec/tests/issue172.css.FAIL +4 -0
- data/spec/tests/issue172.css.min +1 -0
- data/spec/tests/issue180.css +16 -0
- data/spec/tests/issue180.css.min +1 -0
- data/spec/tests/issue205.css +2 -0
- data/spec/tests/issue205.css.min +1 -0
- data/spec/tests/issue221.css +1 -1
- data/spec/tests/issue221.css.min +1 -1
- data/spec/tests/issue222.css +2 -2
- data/spec/tests/issue222.css.min +1 -1
- data/spec/tests/issue71.js.FAIL +4 -0
- data/spec/tests/issue71.js.min +1 -0
- data/spec/tests/issue86.js +2 -0
- data/spec/tests/issue86.js.min +1 -0
- data/spec/tests/jquery-1.6.4.js +9046 -0
- data/spec/tests/jquery-1.6.4.js.min +23 -0
- data/spec/tests/lowercasing.css +63 -0
- data/spec/tests/lowercasing.css.min +1 -0
- data/spec/tests/media-test.css +2 -2
- data/spec/tests/old-ie-filter-matrix.css +8 -0
- data/spec/tests/old-ie-filter-matrix.css.min +1 -0
- data/spec/tests/opera-pixel-ratio.css +14 -0
- data/spec/tests/opera-pixel-ratio.css.min +1 -0
- data/spec/tests/pointzeros.css +6 -0
- data/spec/tests/pointzeros.css.min +1 -0
- data/spec/tests/preserve-important.css +1 -0
- data/spec/tests/preserve-important.css.min +1 -0
- data/spec/tests/promise-catch-finally-issue203.js +4 -0
- data/spec/tests/promise-catch-finally-issue203.js.min +1 -0
- data/spec/tests/pseudo-first.css +2 -2
- data/spec/tests/rgb-issue81.css.FAIL +4 -0
- data/spec/tests/rgb-issue81.css.min +1 -0
- data/spec/tests/suite.rhino +3 -0
- data/spec/tests/suite.sh +49 -0
- data/spec/tests/zeros.css +2 -2
- data/spec/tests/zeros.css.min +1 -1
- metadata +129 -14
- data/spec/tests/bug2528093.css +0 -3
- data/spec/tests/bug2528093.css.min +0 -1
- data/spec/tests/keyframe.css +0 -4
- data/spec/tests/keyframe.css.min +0 -1
@@ -0,0 +1,616 @@
|
|
1
|
+
# Advanced Usage Guide
|
2
|
+
|
3
|
+
This guide covers advanced usage patterns, optimization strategies, and best practices for CSSminify2 enhanced features.
|
4
|
+
|
5
|
+
## Table of Contents
|
6
|
+
|
7
|
+
- [Configuration Strategies](#configuration-strategies)
|
8
|
+
- [Optimization Patterns](#optimization-patterns)
|
9
|
+
- [Performance Tuning](#performance-tuning)
|
10
|
+
- [Integration Patterns](#integration-patterns)
|
11
|
+
- [Troubleshooting](#troubleshooting)
|
12
|
+
- [Best Practices](#best-practices)
|
13
|
+
|
14
|
+
## Configuration Strategies
|
15
|
+
|
16
|
+
### Environment-Based Configuration
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
# config/css_compression.rb
|
20
|
+
class CSSCompressionConfig
|
21
|
+
def self.for_environment(env = Rails.env)
|
22
|
+
case env.to_sym
|
23
|
+
when :development
|
24
|
+
{
|
25
|
+
optimize_shorthand_properties: true,
|
26
|
+
strict_error_handling: true # Catch issues early
|
27
|
+
}
|
28
|
+
when :test
|
29
|
+
{
|
30
|
+
optimize_shorthand_properties: true,
|
31
|
+
merge_duplicate_selectors: true,
|
32
|
+
strict_error_handling: true
|
33
|
+
}
|
34
|
+
when :production
|
35
|
+
{
|
36
|
+
merge_duplicate_selectors: true,
|
37
|
+
optimize_shorthand_properties: true,
|
38
|
+
compress_css_variables: true,
|
39
|
+
advanced_color_optimization: true,
|
40
|
+
strict_error_handling: false # Graceful fallbacks in production
|
41
|
+
}
|
42
|
+
else
|
43
|
+
{} # Conservative defaults
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Usage
|
49
|
+
config = CSSCompressionConfig.for_environment
|
50
|
+
result = CSSminify2.compress_enhanced(css, config)
|
51
|
+
```
|
52
|
+
|
53
|
+
### Project-Specific Configurations
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
# For CSS framework projects (Bootstrap, Tailwind, etc.)
|
57
|
+
FRAMEWORK_CONFIG = {
|
58
|
+
merge_duplicate_selectors: true, # Frameworks often have duplicates
|
59
|
+
optimize_shorthand_properties: true,
|
60
|
+
compress_css_variables: false, # Preserve framework variables
|
61
|
+
advanced_color_optimization: true,
|
62
|
+
strict_error_handling: false
|
63
|
+
}.freeze
|
64
|
+
|
65
|
+
# For single-page applications with CSS-in-JS
|
66
|
+
SPA_CONFIG = {
|
67
|
+
merge_duplicate_selectors: false, # CSS-in-JS usually unique
|
68
|
+
optimize_shorthand_properties: true,
|
69
|
+
compress_css_variables: true, # Often many utility variables
|
70
|
+
advanced_color_optimization: true,
|
71
|
+
strict_error_handling: true # Controlled environment
|
72
|
+
}.freeze
|
73
|
+
|
74
|
+
# For legacy projects with lots of technical debt
|
75
|
+
LEGACY_CONFIG = {
|
76
|
+
merge_duplicate_selectors: false, # May break cascade
|
77
|
+
optimize_shorthand_properties: true, # Safe optimization
|
78
|
+
compress_css_variables: false, # May have unusual patterns
|
79
|
+
advanced_color_optimization: false, # May break IE filters
|
80
|
+
strict_error_handling: false # Lots of malformed CSS
|
81
|
+
}.freeze
|
82
|
+
```
|
83
|
+
|
84
|
+
## Optimization Patterns
|
85
|
+
|
86
|
+
### Selective Optimization by File Type
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
class SmartCSSCompressor
|
90
|
+
def self.compress_by_type(css, filename)
|
91
|
+
config = case File.extname(filename)
|
92
|
+
when '.variables.css', '.custom-props.css'
|
93
|
+
{ compress_css_variables: true }
|
94
|
+
when '.grid.css', '.layout.css'
|
95
|
+
{ optimize_shorthand_properties: true }
|
96
|
+
when '.components.css'
|
97
|
+
{ merge_duplicate_selectors: true }
|
98
|
+
when '.utilities.css'
|
99
|
+
{
|
100
|
+
optimize_shorthand_properties: true,
|
101
|
+
compress_css_variables: true
|
102
|
+
}
|
103
|
+
else
|
104
|
+
CSSminify2Enhanced::Configuration.aggressive
|
105
|
+
end
|
106
|
+
|
107
|
+
CSSminify2.compress_enhanced(css, config)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
### Progressive Optimization
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
class ProgressiveCSSOptimizer
|
116
|
+
def initialize
|
117
|
+
@stats = []
|
118
|
+
end
|
119
|
+
|
120
|
+
def optimize_progressively(css)
|
121
|
+
# Start conservative
|
122
|
+
result = css
|
123
|
+
config = {}
|
124
|
+
|
125
|
+
# Apply optimizations progressively, measuring impact
|
126
|
+
optimizations = [
|
127
|
+
{ optimize_shorthand_properties: true },
|
128
|
+
{ merge_duplicate_selectors: true },
|
129
|
+
{ compress_css_variables: true },
|
130
|
+
{ advanced_color_optimization: true }
|
131
|
+
]
|
132
|
+
|
133
|
+
optimizations.each do |optimization|
|
134
|
+
config.merge!(optimization)
|
135
|
+
|
136
|
+
stats = CSSminify2.compress_with_stats(css, config)
|
137
|
+
@stats << {
|
138
|
+
optimization: optimization.keys.first,
|
139
|
+
ratio: stats[:statistics][:compression_ratio],
|
140
|
+
size: stats[:statistics][:compressed_size]
|
141
|
+
}
|
142
|
+
|
143
|
+
result = stats[:compressed_css]
|
144
|
+
end
|
145
|
+
|
146
|
+
{ result: result, progression: @stats }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
```
|
150
|
+
|
151
|
+
### Conditional Optimization
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
def smart_compress(css, options = {})
|
155
|
+
# Analyze CSS to determine best optimizations
|
156
|
+
analysis = analyze_css(css)
|
157
|
+
|
158
|
+
config = {}
|
159
|
+
|
160
|
+
# Enable selector merging only if we detect duplicates
|
161
|
+
if analysis[:duplicate_selectors] > 5
|
162
|
+
config[:merge_duplicate_selectors] = true
|
163
|
+
end
|
164
|
+
|
165
|
+
# Enable variable compression only if we have many variables
|
166
|
+
if analysis[:css_variables] > 10
|
167
|
+
config[:compress_css_variables] = true
|
168
|
+
end
|
169
|
+
|
170
|
+
# Always safe to enable
|
171
|
+
config[:optimize_shorthand_properties] = true
|
172
|
+
config[:advanced_color_optimization] = true
|
173
|
+
|
174
|
+
CSSminify2.compress_enhanced(css, config.merge(options))
|
175
|
+
end
|
176
|
+
|
177
|
+
def analyze_css(css)
|
178
|
+
{
|
179
|
+
duplicate_selectors: css.scan(/([^{]+)\{[^}]*\}/).flatten
|
180
|
+
.group_by(&:strip)
|
181
|
+
.count { |_, v| v.length > 1 },
|
182
|
+
css_variables: css.scan(/--[\w-]+/).uniq.count,
|
183
|
+
size: css.length
|
184
|
+
}
|
185
|
+
end
|
186
|
+
```
|
187
|
+
|
188
|
+
## Performance Tuning
|
189
|
+
|
190
|
+
### Batch Processing
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
class BatchCSSProcessor
|
194
|
+
def initialize(config = {})
|
195
|
+
@compressor = CSSminify2Enhanced::Compressor.new(
|
196
|
+
CSSminify2Enhanced::Configuration.new.tap do |c|
|
197
|
+
config.each { |k, v| c.send("#{k}=", v) }
|
198
|
+
end
|
199
|
+
)
|
200
|
+
end
|
201
|
+
|
202
|
+
def process_files(file_patterns)
|
203
|
+
results = {}
|
204
|
+
total_savings = 0
|
205
|
+
|
206
|
+
Dir.glob(file_patterns).each do |file|
|
207
|
+
css = File.read(file)
|
208
|
+
result = @compressor.compress(css)
|
209
|
+
|
210
|
+
output_file = file.sub(/\.css$/, '.min.css')
|
211
|
+
File.write(output_file, result)
|
212
|
+
|
213
|
+
savings = css.length - result.length
|
214
|
+
total_savings += savings
|
215
|
+
|
216
|
+
results[file] = {
|
217
|
+
original_size: css.length,
|
218
|
+
compressed_size: result.length,
|
219
|
+
savings: savings,
|
220
|
+
ratio: (savings.to_f / css.length * 100).round(2)
|
221
|
+
}
|
222
|
+
end
|
223
|
+
|
224
|
+
results.merge(total_savings: total_savings)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Usage
|
229
|
+
processor = BatchCSSProcessor.new({
|
230
|
+
merge_duplicate_selectors: true,
|
231
|
+
optimize_shorthand_properties: true
|
232
|
+
})
|
233
|
+
|
234
|
+
results = processor.process_files(['app/assets/stylesheets/**/*.css'])
|
235
|
+
puts "Total bytes saved: #{results[:total_savings]}"
|
236
|
+
```
|
237
|
+
|
238
|
+
### Memory-Efficient Processing
|
239
|
+
|
240
|
+
```ruby
|
241
|
+
class StreamingCSSProcessor
|
242
|
+
def self.process_large_file(input_path, output_path, config = {})
|
243
|
+
# For very large CSS files, process in chunks if needed
|
244
|
+
css = File.read(input_path)
|
245
|
+
|
246
|
+
if css.length > 1_000_000 # 1MB threshold
|
247
|
+
# Process in sections for memory efficiency
|
248
|
+
sections = css.split(/(?<=\})/)
|
249
|
+
compressed_sections = []
|
250
|
+
|
251
|
+
sections.each_slice(100) do |section_batch|
|
252
|
+
batch_css = section_batch.join('')
|
253
|
+
compressed = CSSminify2.compress_enhanced(batch_css, config)
|
254
|
+
compressed_sections << compressed
|
255
|
+
end
|
256
|
+
|
257
|
+
result = compressed_sections.join('')
|
258
|
+
else
|
259
|
+
result = CSSminify2.compress_enhanced(css, config)
|
260
|
+
end
|
261
|
+
|
262
|
+
File.write(output_path, result)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
```
|
266
|
+
|
267
|
+
## Integration Patterns
|
268
|
+
|
269
|
+
### Rails Asset Pipeline Integration
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
# config/initializers/css_compression.rb
|
273
|
+
class EnhancedCSSCompressor
|
274
|
+
def initialize(options = {})
|
275
|
+
@options = {
|
276
|
+
merge_duplicate_selectors: true,
|
277
|
+
optimize_shorthand_properties: true,
|
278
|
+
compress_css_variables: Rails.env.production?,
|
279
|
+
advanced_color_optimization: true
|
280
|
+
}.merge(options)
|
281
|
+
end
|
282
|
+
|
283
|
+
def compress(css)
|
284
|
+
CSSminify2.compress_enhanced(css, @options)
|
285
|
+
rescue => e
|
286
|
+
Rails.logger.warn "CSS compression failed: #{e.message}"
|
287
|
+
# Fallback to basic compression
|
288
|
+
CSSminify2.compress(css)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# config/application.rb
|
293
|
+
config.assets.css_compressor = EnhancedCSSCompressor.new
|
294
|
+
```
|
295
|
+
|
296
|
+
### Webpack Integration via Ruby Bridge
|
297
|
+
|
298
|
+
```ruby
|
299
|
+
# lib/webpack_css_compressor.rb
|
300
|
+
class WebpackCSSCompressor
|
301
|
+
def self.compress(css, options_json = '{}')
|
302
|
+
options = JSON.parse(options_json, symbolize_names: true)
|
303
|
+
|
304
|
+
stats = CSSminify2.compress_with_stats(css, options)
|
305
|
+
|
306
|
+
# Return JSON for JavaScript consumption
|
307
|
+
{
|
308
|
+
css: stats[:compressed_css],
|
309
|
+
stats: stats[:statistics]
|
310
|
+
}.to_json
|
311
|
+
rescue => e
|
312
|
+
{
|
313
|
+
css: css, # Return original on error
|
314
|
+
error: e.message,
|
315
|
+
stats: { fallback_used: true }
|
316
|
+
}.to_json
|
317
|
+
end
|
318
|
+
end
|
319
|
+
```
|
320
|
+
|
321
|
+
### Jekyll Plugin
|
322
|
+
|
323
|
+
```ruby
|
324
|
+
# _plugins/enhanced_css_minifier.rb
|
325
|
+
module Jekyll
|
326
|
+
class EnhancedCSSMinifier < Jekyll::Generator
|
327
|
+
safe true
|
328
|
+
priority :low
|
329
|
+
|
330
|
+
def generate(site)
|
331
|
+
config = site.config['cssminify'] || {}
|
332
|
+
options = {
|
333
|
+
merge_duplicate_selectors: config['merge_selectors'],
|
334
|
+
optimize_shorthand_properties: config['optimize_shorthand'],
|
335
|
+
compress_css_variables: config['compress_variables']
|
336
|
+
}.compact
|
337
|
+
|
338
|
+
site.static_files.each do |file|
|
339
|
+
next unless file.extname == '.css'
|
340
|
+
|
341
|
+
css = File.read(file.path)
|
342
|
+
compressed = CSSminify2.compress_enhanced(css, options)
|
343
|
+
File.write(file.path, compressed)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
```
|
349
|
+
|
350
|
+
### Gulp Integration
|
351
|
+
|
352
|
+
```ruby
|
353
|
+
# tools/css_compressor.rb
|
354
|
+
#!/usr/bin/env ruby
|
355
|
+
require 'cssminify2'
|
356
|
+
require 'json'
|
357
|
+
|
358
|
+
# Read from STDIN, write to STDOUT for Gulp integration
|
359
|
+
input = STDIN.read
|
360
|
+
options = JSON.parse(ARGV[0] || '{}', symbolize_names: true)
|
361
|
+
|
362
|
+
begin
|
363
|
+
stats = CSSminify2.compress_with_stats(input, options)
|
364
|
+
|
365
|
+
result = {
|
366
|
+
css: stats[:compressed_css],
|
367
|
+
originalSize: stats[:statistics][:original_size],
|
368
|
+
compressedSize: stats[:statistics][:compressed_size],
|
369
|
+
ratio: stats[:statistics][:compression_ratio],
|
370
|
+
success: true
|
371
|
+
}
|
372
|
+
|
373
|
+
puts JSON.generate(result)
|
374
|
+
rescue => e
|
375
|
+
puts JSON.generate({
|
376
|
+
css: input,
|
377
|
+
error: e.message,
|
378
|
+
success: false
|
379
|
+
})
|
380
|
+
exit 1
|
381
|
+
end
|
382
|
+
```
|
383
|
+
|
384
|
+
## Troubleshooting
|
385
|
+
|
386
|
+
### Debug Mode for Complex Issues
|
387
|
+
|
388
|
+
```ruby
|
389
|
+
class CSSDebugger
|
390
|
+
def self.debug_compression(css, options = {})
|
391
|
+
puts "=== CSS COMPRESSION DEBUG ==="
|
392
|
+
puts "Original size: #{css.length} characters"
|
393
|
+
puts "Configuration: #{options.inspect}"
|
394
|
+
puts ""
|
395
|
+
|
396
|
+
# Test each optimization individually
|
397
|
+
optimizations = [
|
398
|
+
:merge_duplicate_selectors,
|
399
|
+
:optimize_shorthand_properties,
|
400
|
+
:compress_css_variables,
|
401
|
+
:advanced_color_optimization
|
402
|
+
]
|
403
|
+
|
404
|
+
results = {}
|
405
|
+
|
406
|
+
optimizations.each do |opt|
|
407
|
+
test_config = { opt => true }
|
408
|
+
|
409
|
+
begin
|
410
|
+
stats = CSSminify2.compress_with_stats(css, test_config)
|
411
|
+
results[opt] = {
|
412
|
+
success: true,
|
413
|
+
size: stats[:statistics][:compressed_size],
|
414
|
+
ratio: stats[:statistics][:compression_ratio]
|
415
|
+
}
|
416
|
+
puts "✅ #{opt}: #{results[opt][:ratio].round(2)}% compression"
|
417
|
+
rescue => e
|
418
|
+
results[opt] = {
|
419
|
+
success: false,
|
420
|
+
error: e.message
|
421
|
+
}
|
422
|
+
puts "❌ #{opt}: FAILED - #{e.message}"
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
# Test full configuration
|
427
|
+
begin
|
428
|
+
full_stats = CSSminify2.compress_with_stats(css, options)
|
429
|
+
puts ""
|
430
|
+
puts "🚀 Full compression: #{full_stats[:statistics][:compression_ratio].round(2)}%"
|
431
|
+
puts "Final size: #{full_stats[:statistics][:compressed_size]} characters"
|
432
|
+
|
433
|
+
if full_stats[:statistics][:fallback_used]
|
434
|
+
puts "⚠️ Fallback compression was used"
|
435
|
+
end
|
436
|
+
rescue => e
|
437
|
+
puts "❌ Full compression failed: #{e.message}"
|
438
|
+
end
|
439
|
+
|
440
|
+
results
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
# Usage
|
445
|
+
CSSDebugger.debug_compression(problematic_css, {
|
446
|
+
merge_duplicate_selectors: true,
|
447
|
+
optimize_shorthand_properties: true
|
448
|
+
})
|
449
|
+
```
|
450
|
+
|
451
|
+
### Validation and Recovery
|
452
|
+
|
453
|
+
```ruby
|
454
|
+
def safe_compress_with_validation(css, options = {})
|
455
|
+
# Pre-compression validation
|
456
|
+
validator = CSSValidator.new(css)
|
457
|
+
warnings = validator.validate
|
458
|
+
|
459
|
+
if warnings.any?
|
460
|
+
puts "⚠️ CSS warnings detected:"
|
461
|
+
warnings.each { |w| puts " - #{w}" }
|
462
|
+
end
|
463
|
+
|
464
|
+
# Attempt compression with error recovery
|
465
|
+
begin
|
466
|
+
# Try strict mode first
|
467
|
+
result = CSSminify2.compress_enhanced(css, options.merge(
|
468
|
+
strict_error_handling: true
|
469
|
+
))
|
470
|
+
|
471
|
+
puts "✅ Strict compression successful"
|
472
|
+
result
|
473
|
+
rescue CSSminify2Enhanced::MalformedCSSError => e
|
474
|
+
puts "⚠️ CSS validation failed, trying non-strict mode"
|
475
|
+
puts "Errors: #{e.css_errors.join(', ')}"
|
476
|
+
|
477
|
+
# Fallback to non-strict
|
478
|
+
CSSminify2.compress_enhanced(css, options.merge(
|
479
|
+
strict_error_handling: false
|
480
|
+
))
|
481
|
+
rescue => e
|
482
|
+
puts "❌ Enhanced compression failed: #{e.message}"
|
483
|
+
puts "Using basic compression"
|
484
|
+
|
485
|
+
# Ultimate fallback
|
486
|
+
CSSminify2.compress(css)
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
class CSSValidator
|
491
|
+
def initialize(css)
|
492
|
+
@css = css
|
493
|
+
end
|
494
|
+
|
495
|
+
def validate
|
496
|
+
warnings = []
|
497
|
+
|
498
|
+
# Check for potential issues
|
499
|
+
warnings << "Many duplicate selectors detected" if duplicate_selectors > 20
|
500
|
+
warnings << "Very large file (>500KB)" if @css.length > 500_000
|
501
|
+
warnings << "Many CSS variables (>50)" if css_variables > 50
|
502
|
+
warnings << "Potentially malformed CSS" if syntax_issues?
|
503
|
+
|
504
|
+
warnings
|
505
|
+
end
|
506
|
+
|
507
|
+
private
|
508
|
+
|
509
|
+
def duplicate_selectors
|
510
|
+
@css.scan(/([^{]+)\{/).flatten.group_by(&:strip).count { |_, v| v.length > 1 }
|
511
|
+
end
|
512
|
+
|
513
|
+
def css_variables
|
514
|
+
@css.scan(/--[\w-]+/).uniq.count
|
515
|
+
end
|
516
|
+
|
517
|
+
def syntax_issues?
|
518
|
+
@css.count('{') != @css.count('}') ||
|
519
|
+
@css.scan(/"/).length.odd? ||
|
520
|
+
@css.scan(/'/).length.odd?
|
521
|
+
end
|
522
|
+
end
|
523
|
+
```
|
524
|
+
|
525
|
+
## Best Practices
|
526
|
+
|
527
|
+
### 1. **Start Conservative, Scale Up**
|
528
|
+
```ruby
|
529
|
+
# Begin with safe optimizations
|
530
|
+
initial_config = {
|
531
|
+
optimize_shorthand_properties: true
|
532
|
+
}
|
533
|
+
|
534
|
+
# Add features as you gain confidence
|
535
|
+
full_config = {
|
536
|
+
merge_duplicate_selectors: true,
|
537
|
+
optimize_shorthand_properties: true,
|
538
|
+
compress_css_variables: true,
|
539
|
+
advanced_color_optimization: true
|
540
|
+
}
|
541
|
+
```
|
542
|
+
|
543
|
+
### 2. **Use Statistics for Optimization**
|
544
|
+
```ruby
|
545
|
+
def optimize_build_process(css_files)
|
546
|
+
css_files.each do |file|
|
547
|
+
css = File.read(file)
|
548
|
+
|
549
|
+
stats = CSSminify2.compress_with_stats(css, full_config)
|
550
|
+
|
551
|
+
# Only use advanced features if they provide significant benefit
|
552
|
+
if stats[:statistics][:compression_ratio] < 30
|
553
|
+
puts "⚠️ #{file}: Low compression ratio, consider reviewing CSS structure"
|
554
|
+
end
|
555
|
+
|
556
|
+
if stats[:statistics][:fallback_used]
|
557
|
+
puts "🚨 #{file}: Fallback used, may need manual review"
|
558
|
+
end
|
559
|
+
end
|
560
|
+
end
|
561
|
+
```
|
562
|
+
|
563
|
+
### 3. **Environment-Specific Configurations**
|
564
|
+
- **Development**: Enable `strict_error_handling` to catch issues early
|
565
|
+
- **Testing**: Use aggressive optimization to test edge cases
|
566
|
+
- **Production**: Use balanced configuration with graceful fallbacks
|
567
|
+
|
568
|
+
### 4. **Monitor Compression Performance**
|
569
|
+
```ruby
|
570
|
+
class CompressionMonitor
|
571
|
+
def self.monitor(css, options = {})
|
572
|
+
start_time = Time.now
|
573
|
+
stats = CSSminify2.compress_with_stats(css, options)
|
574
|
+
end_time = Time.now
|
575
|
+
|
576
|
+
{
|
577
|
+
result: stats[:compressed_css],
|
578
|
+
performance: {
|
579
|
+
compression_time: (end_time - start_time) * 1000, # ms
|
580
|
+
original_size: stats[:statistics][:original_size],
|
581
|
+
compressed_size: stats[:statistics][:compressed_size],
|
582
|
+
compression_ratio: stats[:statistics][:compression_ratio],
|
583
|
+
compression_speed: stats[:statistics][:original_size] / (end_time - start_time) # bytes/sec
|
584
|
+
}
|
585
|
+
}
|
586
|
+
end
|
587
|
+
end
|
588
|
+
```
|
589
|
+
|
590
|
+
### 5. **Testing Strategy**
|
591
|
+
```ruby
|
592
|
+
# Test your CSS compression in your test suite
|
593
|
+
RSpec.describe 'CSS Compression' do
|
594
|
+
it 'compresses CSS without breaking functionality' do
|
595
|
+
original_css = File.read('app/assets/stylesheets/application.css')
|
596
|
+
|
597
|
+
compressed = CSSminify2.compress_enhanced(original_css, production_config)
|
598
|
+
|
599
|
+
expect(compressed.length).to be < original_css.length
|
600
|
+
expect(compressed).to include('.main-header') # Key selectors preserved
|
601
|
+
expect(compressed).not_to include('/* comments */') # Comments removed
|
602
|
+
end
|
603
|
+
|
604
|
+
it 'handles malformed CSS gracefully' do
|
605
|
+
malformed_css = '.test { color: red .broken { }'
|
606
|
+
|
607
|
+
expect {
|
608
|
+
CSSminify2.compress_enhanced(malformed_css, {
|
609
|
+
strict_error_handling: false
|
610
|
+
})
|
611
|
+
}.not_to raise_error
|
612
|
+
end
|
613
|
+
end
|
614
|
+
```
|
615
|
+
|
616
|
+
This advanced usage guide covers the most common patterns and strategies for getting the most out of CSSminify2's enhanced features. Remember to always test thoroughly when adopting new optimization settings!
|