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 agressive 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, and .xhtml

Defined Under Namespace

Classes: NumberHelpers

Constant Summary

Constants included from Contracts

Contracts::PATH_MATCHER, Contracts::ResourceList, Contracts::VectorOf

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 = {}) ⇒ Gzip

Returns a new instance of Gzip



21
22
23
24
25
26
27
28
# File 'middleman-core/lib/middleman-core/extensions/gzip.rb', line 21

def initialize(app, options_hash={})
  super

  require 'zlib'
  require 'stringio'
  require 'find'
  require 'thread'
end

Instance Method Details

#after_build(builder) ⇒ Object



30
31
32
33
34
35
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
# File 'middleman-core/lib/middleman-core/extensions/gzip.rb', line 30

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
      while path = in_queue.pop
        out_queue << gzip_file(path.to_s)
      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) > 0 ? '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



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'middleman-core/lib/middleman-core/extensions/gzip.rb', line 75

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
  if !options.overwrite && File.exist?(output_filename) && File.mtime(output_filename) == input_file_time
    return [nil, nil, nil]
  end

  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