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.
Files changed (95) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +10 -5
  3. data/CHANGELOG.md +121 -0
  4. data/Dockerfile +16 -0
  5. data/Gemfile +1 -1
  6. data/README.md +501 -42
  7. data/cssminify2.gemspec +6 -2
  8. data/docs/ADVANCED_USAGE.md +616 -0
  9. data/docs/API_REFERENCE.md +464 -0
  10. data/docs/EXAMPLES.md +844 -0
  11. data/docs/MIGRATION_GUIDE.md +586 -0
  12. data/lib/cssminify2/cssmin.rb +89 -7
  13. data/lib/cssminify2/cssmin_enhanced.rb +424 -0
  14. data/lib/cssminify2/enhanced.rb +818 -0
  15. data/lib/cssminify2/version.rb +1 -1
  16. data/lib/cssminify2.rb +53 -4
  17. data/spec/cssminify_spec.rb +49 -34
  18. data/spec/tests/README +6 -0
  19. data/spec/tests/_munge.js +8 -0
  20. data/spec/tests/_munge.js.min +1 -0
  21. data/spec/tests/_string_combo.js +5 -0
  22. data/spec/tests/_string_combo.js.min +1 -0
  23. data/spec/tests/_string_combo2.js +4 -0
  24. data/spec/tests/_string_combo2.js.min +1 -0
  25. data/spec/tests/_string_combo3.js +5 -0
  26. data/spec/tests/_string_combo3.js.min +1 -0
  27. data/spec/tests/_syntax_error.js +73 -0
  28. data/spec/tests/_syntax_error.js.min +1 -0
  29. data/spec/tests/border-none.css +6 -1
  30. data/spec/tests/border-none.css.min +1 -1
  31. data/spec/tests/bug-flex.css +3 -0
  32. data/spec/tests/bug-flex.css.min +1 -0
  33. data/spec/tests/bug-nested-pseudoclass.css +3 -0
  34. data/spec/tests/bug-nested-pseudoclass.css.min +1 -0
  35. data/spec/tests/bug-preservetoken-calc.css +8 -0
  36. data/spec/tests/bug-preservetoken-calc.css.min +1 -0
  37. data/spec/tests/color-keyword.css +1 -0
  38. data/spec/tests/color-keyword.css.min +1 -0
  39. data/spec/tests/color.css +2 -0
  40. data/spec/tests/color.css.min +1 -1
  41. data/spec/tests/concat-charset.css +2 -2
  42. data/spec/tests/concat-charset.css.min +1 -1
  43. data/spec/tests/dataurl-singlequote-font.css +1 -1
  44. data/spec/tests/dataurl-validity.html +29 -0
  45. data/spec/tests/float.js +2 -0
  46. data/spec/tests/float.js.min +1 -0
  47. data/spec/tests/hsla-issue81.css.FAIL +4 -0
  48. data/spec/tests/hsla-issue81.css.min +1 -0
  49. data/spec/tests/ie-backslash9-hack.css +2 -0
  50. data/spec/tests/ie-backslash9-hack.css.min +1 -0
  51. data/spec/tests/issue-59.css +7 -0
  52. data/spec/tests/issue-59.css.min +1 -0
  53. data/spec/tests/issue151.css +8 -0
  54. data/spec/tests/issue151.css.min +1 -0
  55. data/spec/tests/issue172.css.FAIL +4 -0
  56. data/spec/tests/issue172.css.min +1 -0
  57. data/spec/tests/issue180.css +16 -0
  58. data/spec/tests/issue180.css.min +1 -0
  59. data/spec/tests/issue205.css +2 -0
  60. data/spec/tests/issue205.css.min +1 -0
  61. data/spec/tests/issue221.css +1 -1
  62. data/spec/tests/issue221.css.min +1 -1
  63. data/spec/tests/issue222.css +2 -2
  64. data/spec/tests/issue222.css.min +1 -1
  65. data/spec/tests/issue71.js.FAIL +4 -0
  66. data/spec/tests/issue71.js.min +1 -0
  67. data/spec/tests/issue86.js +2 -0
  68. data/spec/tests/issue86.js.min +1 -0
  69. data/spec/tests/jquery-1.6.4.js +9046 -0
  70. data/spec/tests/jquery-1.6.4.js.min +23 -0
  71. data/spec/tests/lowercasing.css +63 -0
  72. data/spec/tests/lowercasing.css.min +1 -0
  73. data/spec/tests/media-test.css +2 -2
  74. data/spec/tests/old-ie-filter-matrix.css +8 -0
  75. data/spec/tests/old-ie-filter-matrix.css.min +1 -0
  76. data/spec/tests/opera-pixel-ratio.css +14 -0
  77. data/spec/tests/opera-pixel-ratio.css.min +1 -0
  78. data/spec/tests/pointzeros.css +6 -0
  79. data/spec/tests/pointzeros.css.min +1 -0
  80. data/spec/tests/preserve-important.css +1 -0
  81. data/spec/tests/preserve-important.css.min +1 -0
  82. data/spec/tests/promise-catch-finally-issue203.js +4 -0
  83. data/spec/tests/promise-catch-finally-issue203.js.min +1 -0
  84. data/spec/tests/pseudo-first.css +2 -2
  85. data/spec/tests/rgb-issue81.css.FAIL +4 -0
  86. data/spec/tests/rgb-issue81.css.min +1 -0
  87. data/spec/tests/suite.rhino +3 -0
  88. data/spec/tests/suite.sh +49 -0
  89. data/spec/tests/zeros.css +2 -2
  90. data/spec/tests/zeros.css.min +1 -1
  91. metadata +129 -14
  92. data/spec/tests/bug2528093.css +0 -3
  93. data/spec/tests/bug2528093.css.min +0 -1
  94. data/spec/tests/keyframe.css +0 -4
  95. data/spec/tests/keyframe.css.min +0 -1
data/docs/EXAMPLES.md ADDED
@@ -0,0 +1,844 @@
1
+ # Examples & Usage Patterns
2
+
3
+ Real-world examples and common usage patterns for CSSminify2.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Basic Usage Examples](#basic-usage-examples)
8
+ - [Enhanced Feature Examples](#enhanced-feature-examples)
9
+ - [Framework Integration Examples](#framework-integration-examples)
10
+ - [Build Tool Examples](#build-tool-examples)
11
+ - [Error Handling Examples](#error-handling-examples)
12
+ - [Performance Optimization Examples](#performance-optimization-examples)
13
+
14
+ ## Basic Usage Examples
15
+
16
+ ### Simple CSS Compression
17
+
18
+ ```ruby
19
+ require 'cssminify2'
20
+
21
+ # Basic string compression
22
+ css = "body { margin: 0; padding: 0; }"
23
+ result = CSSminify2.compress(css)
24
+ puts result
25
+ # Output: "body{margin:0;padding:0}"
26
+
27
+ # File compression
28
+ original_css = File.read('styles.css')
29
+ compressed_css = CSSminify2.compress(original_css)
30
+ File.write('styles.min.css', compressed_css)
31
+ ```
32
+
33
+ ### With Line Length Control
34
+
35
+ ```ruby
36
+ long_css = ".very-long-selector { background: url('very-long-url.png'); }"
37
+
38
+ # Default line length (5000 characters)
39
+ default_result = CSSminify2.compress(long_css)
40
+
41
+ # Custom line length (80 characters)
42
+ short_lines = CSSminify2.compress(long_css, 80)
43
+ puts "Short lines result:\n#{short_lines}"
44
+ ```
45
+
46
+ ### Instance-Based Usage
47
+
48
+ ```ruby
49
+ compressor = CSSminify2.new
50
+
51
+ css_files = ['main.css', 'components.css', 'utilities.css']
52
+ css_files.each do |file|
53
+ css = File.read(file)
54
+ compressed = compressor.compress(css)
55
+ File.write(file.gsub('.css', '.min.css'), compressed)
56
+ end
57
+ ```
58
+
59
+ ## Enhanced Feature Examples
60
+
61
+ ### Duplicate Selector Merging
62
+
63
+ ```ruby
64
+ css_with_duplicates = <<-CSS
65
+ .btn { color: red; }
66
+ .card { background: white; }
67
+ .btn { background: blue; }
68
+ .btn { color: green; } /* This will override the red */
69
+ .card { border: 1px solid gray; }
70
+ CSS
71
+
72
+ result = CSSminify2.compress_enhanced(css_with_duplicates, {
73
+ merge_duplicate_selectors: true
74
+ })
75
+
76
+ puts result
77
+ # Output: ".btn{color:green;background:blue}.card{background:white;border:1px solid gray}"
78
+
79
+ # Note: Properties are merged correctly, with later declarations taking precedence
80
+ ```
81
+
82
+ ### Shorthand Property Optimization
83
+
84
+ ```ruby
85
+ css_with_longhand = <<-CSS
86
+ .container {
87
+ margin: 10px 10px 10px 10px;
88
+ padding: 20px 20px;
89
+ border: 1px solid red 1px;
90
+ background: none repeat scroll 0 0 #ffffff;
91
+ }
92
+
93
+ .grid {
94
+ flex: 1 1 auto;
95
+ grid-gap: 15px 15px;
96
+ }
97
+ CSS
98
+
99
+ result = CSSminify2.compress_enhanced(css_with_longhand, {
100
+ optimize_shorthand_properties: true
101
+ })
102
+
103
+ puts result
104
+ # Output includes optimizations like:
105
+ # margin: 10px (instead of 10px 10px 10px 10px)
106
+ # padding: 20px (instead of 20px 20px)
107
+ # gap: 15px (instead of grid-gap: 15px 15px)
108
+ ```
109
+
110
+ ### CSS Variables Optimization
111
+
112
+ ```ruby
113
+ css_with_variables = <<-CSS
114
+ :root {
115
+ --primary-color: #3366CC;
116
+ --secondary-color: #FF6633;
117
+ --unused-variable: #DEAD00;
118
+ --single-use-spacing: 8px;
119
+ --very-long-variable-name-for-padding: 16px;
120
+ }
121
+
122
+ .component-one {
123
+ color: var(--primary-color);
124
+ background: var(--secondary-color);
125
+ margin: var(--single-use-spacing); /* Only used once */
126
+ padding: var(--very-long-variable-name-for-padding);
127
+ }
128
+
129
+ .component-two {
130
+ color: var(--primary-color);
131
+ padding: var(--very-long-variable-name-for-padding);
132
+ }
133
+ CSS
134
+
135
+ result = CSSminify2.compress_enhanced(css_with_variables, {
136
+ compress_css_variables: true,
137
+ advanced_color_optimization: true
138
+ })
139
+
140
+ puts result
141
+ # Output optimizes variables:
142
+ # - Removes --unused-variable (never used)
143
+ # - Inlines --single-use-spacing (used only once)
144
+ # - Shortens --very-long-variable-name-for-padding to --v1
145
+ # - Converts #3366CC to #36C, #FF6633 to #f63
146
+ ```
147
+
148
+ ### Modern CSS Layout Optimization
149
+
150
+ ```ruby
151
+ modern_css = <<-CSS
152
+ .layout {
153
+ display: grid;
154
+ grid-template-columns: repeat(12, 1fr);
155
+ grid-gap: 20px 20px;
156
+ justify-content: flex-start;
157
+ align-items: flex-start;
158
+ }
159
+
160
+ .flex-container {
161
+ display: flex;
162
+ flex: 1 1 auto;
163
+ justify-content: flex-end;
164
+ align-items: center;
165
+ transform: translate(0, 0) scale(1, 1) rotate(0deg);
166
+ }
167
+ CSS
168
+
169
+ result = CSSminify2.compress_enhanced(modern_css, {
170
+ optimize_shorthand_properties: true
171
+ })
172
+
173
+ puts result
174
+ # Output includes modern optimizations:
175
+ # gap: 20px (instead of grid-gap: 20px 20px)
176
+ # justify-content: start (instead of flex-start)
177
+ # flex: 1 (instead of 1 1 auto)
178
+ # transform: translate(0) scale(1) (removing unnecessary values)
179
+ ```
180
+
181
+ ### Color Optimization
182
+
183
+ ```ruby
184
+ css_with_colors = <<-CSS
185
+ .colors {
186
+ color: #FF0000;
187
+ background: #00FF00;
188
+ border-color: #0000FF;
189
+ box-shadow: 0 0 10px rgba(255, 0, 0, 0.5);
190
+ }
191
+
192
+ .ie-filter {
193
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FF0000', endColorstr='#00FF00');
194
+ }
195
+ CSS
196
+
197
+ result = CSSminify2.compress_enhanced(css_with_colors, {
198
+ advanced_color_optimization: true
199
+ })
200
+
201
+ puts result
202
+ # Output:
203
+ # - #FF0000 → red (shorter)
204
+ # - #00FF00 → lime
205
+ # - #0000FF → blue
206
+ # - IE filter properties are protected from color conversion
207
+ ```
208
+
209
+ ## Framework Integration Examples
210
+
211
+ ### Rails Application
212
+
213
+ ```ruby
214
+ # app/lib/enhanced_css_compressor.rb
215
+ class EnhancedCSSCompressor
216
+ def initialize(options = {})
217
+ @base_config = {
218
+ merge_duplicate_selectors: true,
219
+ optimize_shorthand_properties: true,
220
+ advanced_color_optimization: true
221
+ }
222
+
223
+ # Environment-specific overrides
224
+ @config = case Rails.env
225
+ when 'development'
226
+ @base_config.merge(
227
+ strict_error_handling: true # Catch issues early
228
+ )
229
+ when 'production'
230
+ @base_config.merge(
231
+ compress_css_variables: true, # More aggressive in production
232
+ strict_error_handling: false # Graceful fallbacks
233
+ )
234
+ else
235
+ @base_config
236
+ end
237
+ end
238
+
239
+ def compress(css)
240
+ stats = CSSminify2.compress_with_stats(css, @config)
241
+
242
+ # Log compression metrics
243
+ Rails.logger.info "CSS compressed: #{stats[:statistics][:compression_ratio].round(2)}% reduction"
244
+
245
+ if stats[:statistics][:fallback_used]
246
+ Rails.logger.warn "CSS compression used fallback mode"
247
+ end
248
+
249
+ stats[:compressed_css]
250
+ rescue => e
251
+ Rails.logger.error "CSS compression failed: #{e.message}"
252
+ # Ultimate fallback to basic compression
253
+ CSSminify2.compress(css)
254
+ end
255
+ end
256
+
257
+ # config/application.rb
258
+ config.assets.css_compressor = EnhancedCSSCompressor.new
259
+ ```
260
+
261
+ ### Sinatra Application
262
+
263
+ ```ruby
264
+ require 'sinatra'
265
+ require 'cssminify2'
266
+
267
+ class CSSMinifyApp < Sinatra::Base
268
+ configure :production do
269
+ # Enable CSS compression for production
270
+ set :compress_css, true
271
+ set :css_compressor_options, {
272
+ merge_duplicate_selectors: true,
273
+ optimize_shorthand_properties: true,
274
+ compress_css_variables: true
275
+ }
276
+ end
277
+
278
+ get '/styles.css' do
279
+ content_type 'text/css'
280
+
281
+ css = File.read('public/styles.css')
282
+
283
+ if settings.compress_css?
284
+ stats = CSSminify2.compress_with_stats(css, settings.css_compressor_options)
285
+
286
+ # Add compression info as comment in development
287
+ if development?
288
+ compression_info = "/* Compressed: #{stats[:statistics][:compression_ratio].round(2)}% reduction */"
289
+ "#{compression_info}\n#{stats[:compressed_css]}"
290
+ else
291
+ stats[:compressed_css]
292
+ end
293
+ else
294
+ css
295
+ end
296
+ end
297
+ end
298
+ ```
299
+
300
+ ### Jekyll Site
301
+
302
+ ```ruby
303
+ # _plugins/css_optimizer.rb
304
+ require 'cssminify2'
305
+
306
+ module Jekyll
307
+ class CSSOptimizer < Generator
308
+ safe true
309
+ priority :low
310
+
311
+ def generate(site)
312
+ return unless site.config['css_compression']
313
+
314
+ config = site.config['css_compression']
315
+ options = {
316
+ merge_duplicate_selectors: config['merge_selectors'] || false,
317
+ optimize_shorthand_properties: config['optimize_shorthand'] || true,
318
+ compress_css_variables: config['compress_variables'] || false,
319
+ advanced_color_optimization: config['advanced_colors'] || true
320
+ }
321
+
322
+ # Process CSS files
323
+ site.static_files.select { |f| f.extname == '.css' }.each do |file|
324
+ optimize_css_file(file, options, site)
325
+ end
326
+
327
+ # Process CSS in pages and posts
328
+ (site.pages + site.posts.docs).each do |page|
329
+ optimize_inline_css(page, options) if page.content.include?('<style>')
330
+ end
331
+ end
332
+
333
+ private
334
+
335
+ def optimize_css_file(file, options, site)
336
+ css = File.read(file.path)
337
+
338
+ stats = CSSminify2.compress_with_stats(css, options)
339
+ File.write(file.path, stats[:compressed_css])
340
+
341
+ Jekyll.logger.info "CSS:", "#{file.relative_path} compressed #{stats[:statistics][:compression_ratio].round(1)}%"
342
+ end
343
+
344
+ def optimize_inline_css(page, options)
345
+ page.content = page.content.gsub(/<style[^>]*>(.*?)<\/style>/m) do |match|
346
+ style_tag = match
347
+ css_content = $1
348
+
349
+ begin
350
+ compressed = CSSminify2.compress_enhanced(css_content, options)
351
+ style_tag.sub(css_content, compressed)
352
+ rescue => e
353
+ Jekyll.logger.warn "CSS:", "Failed to compress inline CSS in #{page.relative_path}: #{e.message}"
354
+ match # Return original on error
355
+ end
356
+ end
357
+ end
358
+ end
359
+ end
360
+ ```
361
+
362
+ ## Build Tool Examples
363
+
364
+ ### Rake Task
365
+
366
+ ```ruby
367
+ # lib/tasks/css_compression.rake
368
+ namespace :css do
369
+ desc "Compress CSS files with enhanced features"
370
+ task :compress do
371
+ require 'cssminify2'
372
+
373
+ css_files = Dir.glob('app/assets/stylesheets/**/*.css')
374
+ total_original = 0
375
+ total_compressed = 0
376
+
377
+ css_files.each do |file|
378
+ next if file.end_with?('.min.css') # Skip already minified files
379
+
380
+ css = File.read(file)
381
+
382
+ stats = CSSminify2.compress_with_stats(css, {
383
+ merge_duplicate_selectors: true,
384
+ optimize_shorthand_properties: true,
385
+ compress_css_variables: true,
386
+ advanced_color_optimization: true
387
+ })
388
+
389
+ # Create minified version
390
+ min_file = file.sub('.css', '.min.css')
391
+ File.write(min_file, stats[:compressed_css])
392
+
393
+ # Track totals
394
+ total_original += stats[:statistics][:original_size]
395
+ total_compressed += stats[:statistics][:compressed_size]
396
+
397
+ puts "#{file} → #{min_file} (#{stats[:statistics][:compression_ratio].round(2)}%)"
398
+ end
399
+
400
+ overall_ratio = ((total_original - total_compressed).to_f / total_original * 100).round(2)
401
+ puts "\nOverall compression: #{overall_ratio}% (#{total_original} → #{total_compressed} bytes)"
402
+ end
403
+
404
+ desc "Validate CSS compression results"
405
+ task :validate do
406
+ require 'cssminify2'
407
+
408
+ css_files = Dir.glob('app/assets/stylesheets/**/*.css')
409
+ issues = []
410
+
411
+ css_files.each do |file|
412
+ css = File.read(file)
413
+
414
+ begin
415
+ # Test with strict validation
416
+ CSSminify2.compress_enhanced(css, {
417
+ merge_duplicate_selectors: true,
418
+ optimize_shorthand_properties: true,
419
+ strict_error_handling: true
420
+ })
421
+ puts "✓ #{file}"
422
+ rescue => e
423
+ issues << "✗ #{file}: #{e.message}"
424
+ puts "✗ #{file}: #{e.message}"
425
+ end
426
+ end
427
+
428
+ if issues.any?
429
+ puts "\n#{issues.length} files have issues:"
430
+ issues.each { |issue| puts " #{issue}" }
431
+ exit 1
432
+ else
433
+ puts "\n✓ All CSS files validated successfully"
434
+ end
435
+ end
436
+ end
437
+ ```
438
+
439
+ ### Guard Integration
440
+
441
+ ```ruby
442
+ # Guardfile
443
+ require 'cssminify2'
444
+
445
+ guard :shell do
446
+ watch(%r{^app/assets/stylesheets/(.+\.css)$}) do |m|
447
+ input_file = m[0]
448
+ output_file = input_file.sub('.css', '.min.css')
449
+
450
+ css = File.read(input_file)
451
+
452
+ begin
453
+ stats = CSSminify2.compress_with_stats(css, {
454
+ merge_duplicate_selectors: true,
455
+ optimize_shorthand_properties: true,
456
+ compress_css_variables: ENV['RAILS_ENV'] == 'production'
457
+ })
458
+
459
+ File.write(output_file, stats[:compressed_css])
460
+
461
+ puts "CSS compressed: #{input_file} → #{output_file} (#{stats[:statistics][:compression_ratio].round(2)}%)"
462
+
463
+ if stats[:statistics][:fallback_used]
464
+ puts " ⚠️ Fallback compression was used"
465
+ end
466
+
467
+ rescue => e
468
+ puts "CSS compression failed for #{input_file}: #{e.message}"
469
+ end
470
+ end
471
+ end
472
+ ```
473
+
474
+ ### Docker Build Integration
475
+
476
+ ```dockerfile
477
+ # Dockerfile
478
+ FROM ruby:3.1
479
+
480
+ WORKDIR /app
481
+ COPY Gemfile Gemfile.lock ./
482
+ RUN bundle install
483
+
484
+ COPY . .
485
+
486
+ # Compress CSS as part of build process
487
+ RUN ruby -e "
488
+ require 'cssminify2'
489
+
490
+ Dir.glob('public/css/**/*.css').each do |file|
491
+ next if file.end_with?('.min.css')
492
+
493
+ css = File.read(file)
494
+ stats = CSSminify2.compress_with_stats(css, {
495
+ merge_duplicate_selectors: true,
496
+ optimize_shorthand_properties: true,
497
+ compress_css_variables: true,
498
+ advanced_color_optimization: true
499
+ })
500
+
501
+ File.write(file.sub('.css', '.min.css'), stats[:compressed_css])
502
+ puts \"Compressed: #{file} (#{stats[:statistics][:compression_ratio].round(2)}%)\"
503
+ end
504
+ "
505
+
506
+ CMD ['ruby', 'app.rb']
507
+ ```
508
+
509
+ ## Error Handling Examples
510
+
511
+ ### Graceful Fallbacks
512
+
513
+ ```ruby
514
+ def safe_css_compression(css, options = {})
515
+ begin
516
+ # Try enhanced compression first
517
+ stats = CSSminify2.compress_with_stats(css, options.merge(
518
+ strict_error_handling: true
519
+ ))
520
+
521
+ {
522
+ css: stats[:compressed_css],
523
+ method: 'enhanced',
524
+ stats: stats[:statistics],
525
+ success: true
526
+ }
527
+ rescue CSSminify2Enhanced::MalformedCSSError => e
528
+ puts "CSS validation failed: #{e.message}"
529
+ puts "Errors: #{e.css_errors.join(', ')}"
530
+
531
+ # Try enhanced compression without strict validation
532
+ begin
533
+ result = CSSminify2.compress_enhanced(css, options.merge(
534
+ strict_error_handling: false
535
+ ))
536
+
537
+ {
538
+ css: result,
539
+ method: 'enhanced_fallback',
540
+ warnings: ['CSS validation failed, used non-strict mode'],
541
+ success: true
542
+ }
543
+ rescue => fallback_error
544
+ puts "Enhanced compression failed: #{fallback_error.message}"
545
+
546
+ # Final fallback to basic compression
547
+ basic_result = CSSminify2.compress(css)
548
+
549
+ {
550
+ css: basic_result,
551
+ method: 'basic_fallback',
552
+ warnings: ['Enhanced compression failed, used basic compression'],
553
+ success: true
554
+ }
555
+ end
556
+ rescue => e
557
+ puts "Unexpected error: #{e.message}"
558
+
559
+ # Always ensure we return something
560
+ {
561
+ css: css, # Return original CSS if all else fails
562
+ method: 'none',
563
+ error: e.message,
564
+ success: false
565
+ }
566
+ end
567
+ end
568
+
569
+ # Usage
570
+ result = safe_css_compression(problematic_css, {
571
+ merge_duplicate_selectors: true,
572
+ optimize_shorthand_properties: true
573
+ })
574
+
575
+ puts "Compression method used: #{result[:method]}"
576
+ puts "Result: #{result[:css]}"
577
+ puts "Warnings: #{result[:warnings].join(', ')}" if result[:warnings]
578
+ ```
579
+
580
+ ### Development vs Production Error Handling
581
+
582
+ ```ruby
583
+ class EnvironmentAwareCSSCompressor
584
+ def initialize(environment = Rails.env)
585
+ @environment = environment
586
+ @config = compression_config_for_environment
587
+ end
588
+
589
+ def compress(css)
590
+ if development_or_test?
591
+ # Strict validation in development to catch issues early
592
+ compress_with_strict_validation(css)
593
+ else
594
+ # Graceful fallbacks in production
595
+ compress_with_fallbacks(css)
596
+ end
597
+ end
598
+
599
+ private
600
+
601
+ def development_or_test?
602
+ %w[development test].include?(@environment)
603
+ end
604
+
605
+ def compression_config_for_environment
606
+ base_config = {
607
+ optimize_shorthand_properties: true,
608
+ merge_duplicate_selectors: true
609
+ }
610
+
611
+ case @environment
612
+ when 'development'
613
+ base_config.merge(strict_error_handling: true)
614
+ when 'test'
615
+ base_config.merge(
616
+ strict_error_handling: true,
617
+ compress_css_variables: true # Test more aggressive compression
618
+ )
619
+ when 'production'
620
+ base_config.merge(
621
+ compress_css_variables: true,
622
+ advanced_color_optimization: true,
623
+ strict_error_handling: false
624
+ )
625
+ else
626
+ base_config
627
+ end
628
+ end
629
+
630
+ def compress_with_strict_validation(css)
631
+ CSSminify2.compress_enhanced(css, @config)
632
+ rescue CSSminify2Enhanced::MalformedCSSError => e
633
+ error_message = "CSS Validation Error:\n"
634
+ error_message += "#{e.message}\n"
635
+ error_message += "Specific issues:\n"
636
+ e.css_errors.each { |error| error_message += " - #{error}\n" }
637
+ error_message += "\nPlease fix these CSS issues before proceeding."
638
+
639
+ raise StandardError, error_message
640
+ end
641
+
642
+ def compress_with_fallbacks(css)
643
+ # Production: try enhanced, fallback gracefully
644
+ CSSminify2.compress_enhanced(css, @config)
645
+ rescue => e
646
+ Rails.logger.warn "Enhanced CSS compression failed: #{e.message}"
647
+
648
+ # Log the error but continue with basic compression
649
+ Rails.logger.warn "Falling back to basic CSS compression"
650
+ CSSminify2.compress(css)
651
+ end
652
+ end
653
+ ```
654
+
655
+ ## Performance Optimization Examples
656
+
657
+ ### Benchmarking and Profiling
658
+
659
+ ```ruby
660
+ require 'benchmark'
661
+ require 'cssminify2'
662
+
663
+ def benchmark_css_compression(css_files)
664
+ puts "CSS Compression Benchmarks"
665
+ puts "=" * 50
666
+
667
+ css_files.each do |file|
668
+ css = File.read(file)
669
+ puts "\nFile: #{file} (#{css.length} characters)"
670
+
671
+ Benchmark.bm(20) do |x|
672
+ basic_result = nil
673
+ enhanced_result = nil
674
+
675
+ x.report("Basic compression:") do
676
+ basic_result = CSSminify2.compress(css)
677
+ end
678
+
679
+ x.report("Enhanced (safe):") do
680
+ enhanced_result = CSSminify2.compress_enhanced(css, {
681
+ optimize_shorthand_properties: true
682
+ })
683
+ end
684
+
685
+ x.report("Enhanced (full):") do
686
+ CSSminify2.compress_enhanced(css, {
687
+ merge_duplicate_selectors: true,
688
+ optimize_shorthand_properties: true,
689
+ compress_css_variables: true,
690
+ advanced_color_optimization: true
691
+ })
692
+ end
693
+
694
+ # Show compression ratios
695
+ basic_ratio = ((css.length - basic_result.length).to_f / css.length * 100).round(2)
696
+ enhanced_ratio = ((css.length - enhanced_result.length).to_f / css.length * 100).round(2)
697
+
698
+ puts " Basic ratio: #{basic_ratio}%"
699
+ puts " Enhanced ratio: #{enhanced_ratio}%"
700
+ puts " Improvement: #{(enhanced_ratio - basic_ratio).round(2)}%"
701
+ end
702
+ end
703
+ end
704
+
705
+ # Run benchmarks
706
+ css_files = Dir.glob('test/fixtures/**/*.css')
707
+ benchmark_css_compression(css_files)
708
+ ```
709
+
710
+ ### Memory-Efficient Processing
711
+
712
+ ```ruby
713
+ class MemoryEfficientCSSProcessor
714
+ def self.process_large_file(input_file, output_file, options = {})
715
+ # Read file size
716
+ file_size = File.size(input_file)
717
+ puts "Processing #{input_file} (#{file_size} bytes)"
718
+
719
+ if file_size > 1_000_000 # 1MB threshold
720
+ process_in_chunks(input_file, output_file, options)
721
+ else
722
+ process_normally(input_file, output_file, options)
723
+ end
724
+ end
725
+
726
+ private
727
+
728
+ def self.process_in_chunks(input_file, output_file, options)
729
+ css = File.read(input_file)
730
+
731
+ # Split CSS into logical sections (at rule boundaries)
732
+ sections = css.split(/(?<=\})\s*(?=\.|#|@|\w)/)
733
+
734
+ compressed_sections = []
735
+
736
+ sections.each_slice(50) do |section_batch| # Process 50 rules at a time
737
+ batch_css = section_batch.join('')
738
+
739
+ begin
740
+ compressed = CSSminify2.compress_enhanced(batch_css, options)
741
+ compressed_sections << compressed
742
+ rescue => e
743
+ puts "Warning: Batch compression failed, using basic compression: #{e.message}"
744
+ compressed = CSSminify2.compress(batch_css)
745
+ compressed_sections << compressed
746
+ end
747
+ end
748
+
749
+ # Combine all compressed sections
750
+ final_result = compressed_sections.join('')
751
+ File.write(output_file, final_result)
752
+
753
+ puts "Processed in #{sections.length} sections, #{compressed_sections.length} batches"
754
+ end
755
+
756
+ def self.process_normally(input_file, output_file, options)
757
+ css = File.read(input_file)
758
+
759
+ stats = CSSminify2.compress_with_stats(css, options)
760
+ File.write(output_file, stats[:compressed_css])
761
+
762
+ puts "Compressed: #{stats[:statistics][:compression_ratio].round(2)}% reduction"
763
+ end
764
+ end
765
+
766
+ # Usage
767
+ MemoryEfficientCSSProcessor.process_large_file(
768
+ 'large-framework.css',
769
+ 'large-framework.min.css',
770
+ {
771
+ merge_duplicate_selectors: true,
772
+ optimize_shorthand_properties: true,
773
+ compress_css_variables: true
774
+ }
775
+ )
776
+ ```
777
+
778
+ ### Caching and Persistent Compressors
779
+
780
+ ```ruby
781
+ class CachingCSSCompressor
782
+ def initialize(cache_dir = 'tmp/css_cache')
783
+ @cache_dir = cache_dir
784
+ @compressor = CSSminify2Enhanced::Compressor.new(
785
+ CSSminify2Enhanced::Configuration.aggressive
786
+ )
787
+
788
+ FileUtils.mkdir_p(@cache_dir)
789
+ end
790
+
791
+ def compress(css)
792
+ # Generate cache key from CSS content
793
+ cache_key = Digest::SHA256.hexdigest(css)
794
+ cache_file = File.join(@cache_dir, "#{cache_key}.css")
795
+
796
+ # Return cached result if available
797
+ if File.exist?(cache_file)
798
+ puts "Using cached compression for #{cache_key[0..7]}..."
799
+ return File.read(cache_file)
800
+ end
801
+
802
+ # Compress and cache result
803
+ result = @compressor.compress(css)
804
+ File.write(cache_file, result)
805
+
806
+ puts "Compressed and cached #{cache_key[0..7]}..."
807
+ result
808
+ end
809
+
810
+ def cache_stats
811
+ cached_files = Dir.glob(File.join(@cache_dir, '*.css'))
812
+ total_size = cached_files.sum { |f| File.size(f) }
813
+
814
+ {
815
+ cached_files: cached_files.length,
816
+ total_cache_size: total_size,
817
+ cache_directory: @cache_dir
818
+ }
819
+ end
820
+
821
+ def clear_cache
822
+ FileUtils.rm_rf(@cache_dir)
823
+ FileUtils.mkdir_p(@cache_dir)
824
+ puts "CSS compression cache cleared"
825
+ end
826
+ end
827
+
828
+ # Usage
829
+ compressor = CachingCSSCompressor.new
830
+
831
+ # Process multiple files with caching
832
+ css_files = Dir.glob('assets/**/*.css')
833
+ css_files.each do |file|
834
+ css = File.read(file)
835
+ compressed = compressor.compress(css) # Will use cache on repeat calls
836
+
837
+ output_file = file.sub('.css', '.min.css')
838
+ File.write(output_file, compressed)
839
+ end
840
+
841
+ puts "Cache stats: #{compressor.cache_stats}"
842
+ ```
843
+
844
+ These examples demonstrate the flexibility and power of CSSminify2's enhanced features across different use cases, from simple compression to complex build pipeline integration.