class Darkroom
Main class providing fast, lightweight, and straightforward web asset management.
Constants
- DEFAULT_INTERNAL_PATTERN
- DEFAULT_MINIFIED_PATTERN
- DISALLOWED_PATH_CHARS
- INVALID_PATH
- MIN_PROCESS_INTERVAL
- PRISTINE
- TRAILING_SLASHES
- VERSION
Attributes
Public Class Methods
Returns the delegate associated with a file extension.
-
extension
- File extension of the desired delegate.
# File lib/darkroom/darkroom.rb, line 51 def self.delegate(extension) @@delegates[extension] end
Creates a new instance.
-
load_paths
- Path(s) where assets are located on disk. -
host
- Host(s) to prepend to paths (useful when serving from a CDN in production). If multiple hosts are specified, they will be round-robined within each thread for each call to#asset_path
. -
hosts
- Alias ofhost
parameter. -
prefix
- Prefix to prepend to asset paths (e.g./assets
). -
pristine
- Path(s) that should not include prefix and for which unversioned form should be provided by default (e.g./favicon.ico
). -
minify
- Boolean specifying whether or not to minify assets. -
minified_pattern
- Regex used against asset paths to determine if they are already minified and should therefore be skipped over for minification. -
internal_pattern
- Regex used against asset paths to determine if they should be marked as internal and therefore made inaccessible externally. -
min_process_interval
- Minimum time required between one run of asset processing and another.
# File lib/darkroom/darkroom.rb, line 72 def initialize(*load_paths, host: nil, hosts: nil, prefix: nil, pristine: nil, minify: false, minified_pattern: DEFAULT_MINIFIED_PATTERN, internal_pattern: DEFAULT_INTERNAL_PATTERN, min_process_interval: MIN_PROCESS_INTERVAL) @load_paths = load_paths.map { |load_path| load_path.chomp('/') } @hosts = (Array(host) + Array(hosts)).map! { |host| host.sub(TRAILING_SLASHES, '') } @minify = minify @internal_pattern = internal_pattern @minified_pattern = minified_pattern @prefix = prefix&.sub(TRAILING_SLASHES, '') @prefix = nil if @prefix && @prefix.empty? @pristine = PRISTINE.dup.merge(Array(pristine)) @min_process_interval = min_process_interval @last_processed_at = 0 @process_key = 0 @mutex = Mutex.new @manifest = {} @manifest_unversioned = {} @manifest_versioned = {} @errors = [] Thread.current[:darkroom_host_index] = -1 unless @hosts.empty? end
Registers an asset delegate.
-
delegate
- An HTTP MIME type string, a Hash of Delegate parameters, or a Delegate instance. -
extensions
- File extension(s) to associate with this delegate.
# File lib/darkroom/darkroom.rb, line 29 def self.register(*extensions, delegate) case delegate when String delegate = Asset::Delegate.new(content_type: delegate.freeze) when Hash delegate = Asset::Delegate.new(**delegate) end extensions.each do |extension| @@delegates[extension] = delegate end @@glob = "**/*{#{@@delegates.keys.sort.join(',')}}" delegate end
Public Instance Methods
Returns an Asset
object, given its external path. An external path includes any prefix and and can be either the versioned or unversioned form of the asset path (i.e. how an HTTP request for the asset comes in). For example, to get the Asset
object with path /js/app.js
when prefix is /assets
:
darkroom.asset('/assets/js/app.<hash>.js') darkroom.asset('/assets/js/app.js')
-
path
- External path of the asset.
# File lib/darkroom/darkroom.rb, line 185 def asset(path) @manifest_versioned[path] || @manifest_unversioned[path] end
Returns an asset's subresource integrity string. Raises an AssetNotFoundError
if the asset doesn't exist.
-
path
- Internal path of the asset. -
algorithm
- Hash algorithm to use to generate the integrity string (seeAsset#integrity
).
# File lib/darkroom/darkroom.rb, line 219 def asset_integrity(path, algorithm = nil) asset = @manifest[path] or raise(AssetNotFoundError.new(path)) algorithm ? asset.integrity(algorithm) : asset.integrity end
Returns the external asset path, given its internal path. An external path includes any prefix and and can be either the versioned or unversioned form of the asset path (i.e. how an HTTP request for the asset comes in). For example, to get the external path for the Asset
object with path /js/app.js
when prefix is /assets
:
darkroom.asset_path('/js/app.js') # => /assets/js/app.<hash>.js darkroom.asset_path('/js/app.js', versioned: false) # => /assets/js/app.js
Raises an AssetNotFoundError
if the asset doesn't exist.
-
path
- Internal path of the asset. -
versioned
- Boolean indicating whether the versioned or unversioned path should be returned.
# File lib/darkroom/darkroom.rb, line 203 def asset_path(path, versioned: !@pristine.include?(path)) asset = @manifest[path] or raise(AssetNotFoundError.new(path)) host = @hosts.empty? ? '' : @hosts[ Thread.current[:darkroom_host_index] = (Thread.current[:darkroom_host_index] + 1) % @hosts.size ] "#{host}#{versioned ? asset.path_versioned : asset.path_unversioned}" end
Writes assets to disk. This is useful when deploying to a production environment where assets will be uploaded to and served from a CDN or proxy server.
-
dir
- Directory to write the assets to. -
clear
- Boolean indicating whether or not the existing contents of the directory should be deleted before performing the dump. -
include_pristine
- Boolean indicating whether or not to include pristine assets (when dumping for the purpose of uploading to a CDN, assets such as /robots.txt and /favicon.ico don't need to be included).
# File lib/darkroom/darkroom.rb, line 245 def dump(dir, clear: false, include_pristine: true) require('fileutils') dir = File.expand_path(dir) FileUtils.mkdir_p(dir) Dir.each_child(dir) { |child| FileUtils.rm_rf(File.join(dir, child)) } if clear @manifest_versioned.each do |path, asset| next if asset.internal? next if @pristine.include?(asset.path) && !include_pristine file_path = File.join(dir, @pristine.include?(asset.path) ? asset.path_unversioned : path ) FileUtils.mkdir_p(File.dirname(file_path)) File.write(file_path, asset.content) end end
Returns boolean indicating whether or not there were any errors encountered the last time assets were processed.
# File lib/darkroom/darkroom.rb, line 171 def error? !!@error end
Returns high-level object info string.
# File lib/darkroom/darkroom.rb, line 269 def inspect "#<#{self.class}: "\ "@errors=#{@errors.inspect}, "\ "@hosts=#{@hosts.inspect}, "\ "@internal_pattern=#{@internal_pattern.inspect}, "\ "@last_processed_at=#{@last_processed_at.inspect}, "\ "@load_paths=#{@load_paths.inspect}, "\ "@min_process_interval=#{@min_process_interval.inspect}, "\ "@minified_pattern=#{@minified_pattern.inspect}, "\ "@minify=#{@minify.inspect}, "\ "@prefix=#{@prefix.inspect}, "\ "@pristine=#{@pristine.inspect}, "\ "@process_key=#{@process_key.inspect}"\ '>' end
Returns the asset from the manifest hash associated with the given path.
-
path
- Internal path of the asset.
# File lib/darkroom/darkroom.rb, line 230 def manifest(path) @manifest[path] end
Walks all load paths and refreshes any assets that have been modified on disk since the last call to this method.
# File lib/darkroom/darkroom.rb, line 105 def process return if Time.now.to_f - @last_processed_at < @min_process_interval if @mutex.locked? @mutex.synchronize {} return end @mutex.synchronize do @process_key += 1 @errors.clear found = {} @load_paths.each do |load_path| Dir.glob(File.join(load_path, @@glob)).sort.each do |file| path = file.sub(load_path, '') if index = (path =~ INVALID_PATH) @errors << InvalidPathError.new(path, index) elsif found.key?(path) @errors << DuplicateAssetError.new(path, found[path], load_path) else found[path] = load_path @manifest[path] ||= Asset.new(path, file, self, prefix: (@prefix unless @pristine.include?(path)), internal: @internal_pattern && path =~ @internal_pattern, minify: @minify && path !~ @minified_pattern, ) end end end @manifest.select! { |path, _| found.key?(path) } @manifest_unversioned.clear @manifest_versioned.clear @manifest.each do |path, asset| asset.process unless asset.internal? @manifest_unversioned[asset.path_unversioned] = asset @manifest_versioned[asset.path_versioned] = asset end @errors += asset.errors end ensure @last_processed_at = Time.now.to_f @error = @errors.empty? ? nil : ProcessingError.new(@errors) end end
Does the same thing as process
, but raises an exception if any errors were encountered.
# File lib/darkroom/darkroom.rb, line 161 def process! process raise(@error) if @error end