Class: Middleman::Extensions::Gzip

Inherits:
Middleman::Extension show all
Defined in:
middleman-core/lib/middleman-core/extensions/gzip.rb

Overview

This extension Gzips assets and pages when building. Gzipped assets and pages can be served directly by Apache or Nginx with the proper configuration, and pre-zipping means that we can use a more aggressive compression level at no CPU cost per request.

Use Nginx's gzip_static directive, or AddEncoding and mod_rewrite in Apache to serve your Gzipped files whenever the normal (non-.gz) filename is requested.

Pass the :exts options to customize which file extensions get zipped (defaults to .css, .htm, .html, .js, .mjs, and .xhtml

Defined Under Namespace

Classes: NumberHelpers

Constant Summary

Constants included from Contracts

Contracts::PATH_MATCHER

Instance Attribute Summary

Attributes inherited from Middleman::Extension

#app, #options

Instance Method Summary collapse

Methods inherited from Middleman::Extension

activated_extension, #add_exposed_to_context, #after_configuration, after_extension_activated, #after_extension_activated, #before_build, #before_configuration, clear_after_extension_callbacks, config, define_setting, expose_to_application, expose_to_config, expose_to_template, global_config, helpers, #manipulate_resource_list, option, #ready, resources

Methods included from Contracts

#Contract

Constructor Details

#initialize(app, options_hash = ::Middleman::EMPTY_HASH) ⇒ Gzip

Returns a new instance of Gzip.


25
26
27
28
29
30
31
32
33
34
# File 'middleman-core/lib/middleman-core/extensions/gzip.rb', line 25

def initialize(app, options_hash = ::Middleman::EMPTY_HASH)
  super

  require 'zlib'
  require 'stringio'
  require 'find'
  require 'set'

  @set_of_exts = Set.new options.exts
end

Instance Method Details

#after_build(builder) ⇒ Object


36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'middleman-core/lib/middleman-core/extensions/gzip.rb', line 36

def after_build(builder)
  num_threads = 4
  paths = ::Middleman::Util.all_files_under(app.config[:build_dir])
  total_savings = 0

  # Fill a queue with inputs
  in_queue = Queue.new
  paths.each do |path|
    in_queue << path if should_gzip?(path)
  end
  num_paths = in_queue.size

  # Farm out gzip tasks to threads and put the results in in_queue
  out_queue = Queue.new
  num_threads.times.each do
    Thread.new do
      path = in_queue.pop

      while path
        out_queue << gzip_file(path.to_s)
        path = in_queue.pop
      end
    end
  end

  # Insert a nil for each thread to stop it
  num_threads.times do
    in_queue << nil
  end

  old_locale = I18n.locale
  I18n.locale = :en # use the english localizations for printing out file sizes to make sure the localizations exist

  num_paths.times do
    output_filename, old_size, new_size = out_queue.pop

    next unless output_filename

    total_savings += (old_size - new_size)
    size_change_word = (old_size - new_size).positive? ? 'smaller' : 'larger'
    builder.trigger :created, "#{output_filename} (#{NumberHelpers.new.number_to_human_size((old_size - new_size).abs)} #{size_change_word})"
  end

  builder.trigger :gzip, '', "Total gzip savings: #{NumberHelpers.new.number_to_human_size(total_savings)}"
  I18n.locale = old_locale
end

#gzip_file(path) ⇒ Object


84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'middleman-core/lib/middleman-core/extensions/gzip.rb', line 84

def gzip_file(path)
  input_file = File.open(path, 'rb').read
  output_filename = options.overwrite ? path : "#{path}.gz"
  input_file_time = File.mtime(path)

  # Check if the right file's already there
  return [nil, nil, nil] if !options.overwrite && File.exist?(output_filename) && File.mtime(output_filename) == input_file_time

  File.open(output_filename, 'wb') do |f|
    gz = Zlib::GzipWriter.new(f, Zlib::BEST_COMPRESSION)
    gz.mtime = input_file_time.to_i
    gz.write input_file
    gz.close
  end

  # Make the file times match, both for Nginx's gzip_static extension
  # and so we can ID existing files. Also, so even if the GZ files are
  # wiped out by build --clean and recreated, we won't rsync them over
  # again because they'll end up with the same mtime.
  File.utime(File.atime(output_filename), input_file_time, output_filename)

  old_size = File.size(path)
  new_size = File.size(output_filename)

  [output_filename, old_size, new_size]
end