class Breakfast::Manifest

Constants

FINGERPRINT_REGEX
MANIFEST_REGEX
SPROCKETS_MANIFEST_REGEX

Attributes

base_dir[R]
cache[R]
manifest_path[R]

Public Class Methods

new(base_dir:) click to toggle source
# File lib/breakfast/manifest.rb, line 12
def initialize(base_dir:)
  FileUtils.mkdir_p(base_dir)

  @base_dir = Pathname.new(base_dir)
  @manifest_path = find_manifest_or_create
  @cache = update_cache!
end

Public Instance Methods

asset(path) click to toggle source
# File lib/breakfast/manifest.rb, line 20
def asset(path)
  cache[path]
end
clean!() click to toggle source

Remove any files not directly referenced by the manifest.

# File lib/breakfast/manifest.rb, line 67
def clean!
  files_to_keep = cache.keys.concat(cache.values)

  if (sprockets_manifest = Dir.entries("#{base_dir}").detect { |entry| entry =~ SPROCKETS_MANIFEST_REGEX })
    files_to_keep.concat(JSON.parse(File.read("#{base_dir}/#{sprockets_manifest}")).fetch("files", {}).keys)
  end

  Dir["#{base_dir}/**/*"].each do |path|
    next if File.directory?(path) || files_to_keep.include?(Pathname(path).relative_path_from(base_dir).to_s)

    FileUtils.rm(path)
  end
end
digest!() click to toggle source

The digest! method will run through all of the compiled assets and create a copy of each asset with a digest fingerprint. This fingerprint will change whenever the file contents change. This allows us to use HTTP headers to cache these assets as we will be able to reliably know when they change.

These fingerprinted files are copies of the original. The originals are not removed and still available should the need arise to serve a non-fingerprinted asset.

Example manifest: {

app.js => app-76c6ee161ba431e823301567b175acda.js,
images/logo.png => images/logo-869269cdf1773ff0dec91bafb37310ea.png,

}

Resulting File Structure:

  • /

    • app.js

    • app-76c6ee161ba431e823301567b175acda.js

    • images/

      • logo.png

      • logo-869269cdf1773ff0dec91bafb37310ea.png

# File lib/breakfast/manifest.rb, line 47
def digest!
  assets = asset_paths.map do |path|
    digest = Digest::MD5.new
    digest.update(File.read("#{base_dir}/#{path}"))

    digest_path = "#{path.sub_ext('')}-#{digest.hexdigest}#{File.extname(path)}"

    FileUtils.cp("#{base_dir}/#{path}", "#{base_dir}/#{digest_path}")

    [path, digest_path]
  end

  File.open(manifest_path, "w") do |manifest|
    manifest.write(assets.to_h.to_json)
  end

  update_cache!
end
nuke!() click to toggle source

Remove manifest, any fingerprinted files.

# File lib/breakfast/manifest.rb, line 82
def nuke!
  Dir["#{base_dir}/**/*"]
    .select { |path| path =~ FINGERPRINT_REGEX }
    .each { |file| FileUtils.rm(file) }

  FileUtils.rm(manifest_path)
end

Private Instance Methods

asset_paths() click to toggle source
# File lib/breakfast/manifest.rb, line 109
def asset_paths
  Dir["#{base_dir}/**/*"]
    .reject { |path| File.directory?(path) || path =~ FINGERPRINT_REGEX }
    .map { |file| Pathname(file).relative_path_from(base_dir) }
end
find_manifest_or_create() click to toggle source

Creates a or finds a manifest file in a given directory. The manifest file is is prefixed with a dot ('.') and given a random string to ensure the file is not served or easily discoverable.

# File lib/breakfast/manifest.rb, line 99
def find_manifest_or_create
  if (manifest = Dir.entries("#{base_dir}").detect { |entry| entry =~ MANIFEST_REGEX })
    "#{base_dir}/#{manifest}"
  else
    manifest = "#{base_dir}/.breakfast-manifest-#{SecureRandom.hex(16)}.json"
    File.open(manifest, "w") { |manifest| manifest.write({}.to_json) }
    manifest
  end
end
update_cache!() click to toggle source
# File lib/breakfast/manifest.rb, line 92
def update_cache!
  @cache = JSON.parse(File.read(manifest_path))
end