This repository has been archived by the owner on Jun 10, 2018. It is now read-only.
forked from rails/sprockets
Merged
Rake Task #256
Changes from 10 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
1aa2398
Add basic bundle rake task
josh 715559c
Rake filename convention
josh 590e8b1
Improve rake task logging
josh c561085
Lazy load environment
josh a2a7318
Add manifest
josh 949f199
Test manifest
josh 3be2425
Doc manifest
josh 0b1a17f
Test rake task
josh 5f2935b
Doc rake task
josh 2bec696
Default manifest name to manifest.json
josh adfd778
Add pattern matching to logical path iteration
josh 59fcd87
Rename t.bundles to t.assets
josh d02e755
Rename to output
josh 6ff0d7e
bundle -> assets
josh File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
require 'rake' | ||
require 'rake/tasklib' | ||
|
||
require 'sprockets' | ||
require 'logger' | ||
|
||
module Rake | ||
# Simple Sprockets compilation Rake task macro. | ||
# | ||
# Rake::SprocketsTask.new do |t| | ||
# t.environment = Sprockets::Environment.new | ||
# t.bundle_dir = "./public/assets" | ||
# t.bundles = %w( application.js application.css ) | ||
# end | ||
# | ||
class SprocketsTask < Rake::TaskLib | ||
# Name of the task. Defaults to "bundle". | ||
# | ||
# The name will also be used to suffix the clean and clobber | ||
# tasks, "clean_bundle" and "clobber_bundle". | ||
attr_accessor :name | ||
|
||
# `Environment` instance used for finding assets. | ||
# | ||
# You'll most likely want to reassign `environment` to your own. | ||
# | ||
# Rake::SprocketsTask.new do |t| | ||
# t.environment = Foo::Assets | ||
# end | ||
# | ||
def environment | ||
if !@environment.is_a?(Sprockets::Base) && @environment.respond_to?(:call) | ||
@environment = @environment.call | ||
else | ||
@environment | ||
end | ||
end | ||
attr_writer :environment | ||
|
||
# Directory to write compiled assets too. As well as the manifest file. | ||
# | ||
# t.bundle_dir = "./public/assets" | ||
# | ||
attr_accessor :bundle_dir | ||
|
||
# Array of logical paths to compile. | ||
# | ||
# t.bundles = %w( application.js jquery.js application.css ) | ||
# | ||
attr_accessor :bundles | ||
|
||
# Logger to use during rake tasks. Defaults to using stderr. | ||
# | ||
# t.logger = Logger.new($stdout) | ||
# | ||
attr_accessor :logger | ||
|
||
# Returns logger level Integer. | ||
def log_level | ||
@logger.level | ||
end | ||
|
||
# Set logger level with constant or symbol. | ||
# | ||
# t.log_level = Logger::INFO | ||
# t.log_level = :debug | ||
# | ||
def log_level=(level) | ||
if level.is_a?(Integer) | ||
@logger.level = level | ||
else | ||
@logger.level = Logger.const_get(level.to_s.upcase) | ||
end | ||
end | ||
|
||
def initialize(name = :bundle) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure what the default task name should be. |
||
@name = name | ||
@environment = lambda { Sprockets::Environment.new(Dir.pwd) } | ||
@logger = Logger.new($stderr) | ||
@logger.level = Logger::WARN | ||
|
||
yield self if block_given? | ||
|
||
define | ||
end | ||
|
||
# Define tasks | ||
def define | ||
bundles.each do |logical_path| | ||
task "#{name}:#{logical_path}" do | ||
with_logger do | ||
manifest.compile logical_path | ||
end | ||
end | ||
end | ||
|
||
desc name == :bundle ? "Compile asset bundles" : "Compile #{name} bundles" | ||
task name => bundles.map { |path| "#{name}:#{path}" } | ||
|
||
desc name == :bundle ? "Remove all asset bundles" : "Remove all #{name} bundles" | ||
task "clobber_#{name}" do | ||
with_logger do | ||
manifest.clobber | ||
end | ||
end | ||
|
||
task :clobber => ["clobber_#{name}"] | ||
|
||
desc name == :bundle ? "Clean old asset bundles" : "Clean old #{name} bundles" | ||
task "clean_#{name}" do | ||
with_logger do | ||
manifest.clean | ||
end | ||
end | ||
|
||
task :clean => ["clean_#{name}"] | ||
end | ||
|
||
private | ||
# Returns cached indexed environment | ||
def index | ||
@index ||= environment.index | ||
end | ||
|
||
# Returns manifest for tasks | ||
def manifest | ||
@manifest ||= Sprockets::Manifest.new(index, "#{bundle_dir}/manifest.json") | ||
end | ||
|
||
# Sub out environment logger with our rake task logger that | ||
# writes to stderr. | ||
def with_logger | ||
old_logger = index.logger | ||
index.logger = @logger | ||
yield | ||
ensure | ||
index.logger = old_logger | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
require 'json' | ||
require 'time' | ||
|
||
module Sprockets | ||
# The Manifest logs the contents of assets compiled to a single | ||
# directory. It records basic attributes about the asset for fast | ||
# lookup without having to compile. A pointer from each logical path | ||
# indicates with fingerprinted asset is the current one. | ||
# | ||
# The JSON is part of the public API and should be considered | ||
# stable. This should make it easy to read from other programming | ||
# languages and processes that don't have sprockets loaded. See | ||
# `#assets` and `#files` for more infomation about the structure. | ||
class Manifest | ||
attr_reader :environment, :path, :dir | ||
|
||
# Create new Manifest associated with an `environment`. `path` is | ||
# a full path to the manifest json file. The file may or may not | ||
# already exist. The dirname of the `path` will be used to write | ||
# compiled assets to. Otherwise, if the path is a directory, the | ||
# filename will default to "manifest.json" in that directory. | ||
# | ||
# Manifest.new(environment, "./public/assets/manifest.json") | ||
# | ||
def initialize(environment, path) | ||
@environment = environment | ||
|
||
if File.extname(path) == "" | ||
@dir = File.expand_path(path) | ||
@path = File.join(@dir, 'manifest.json') | ||
else | ||
@path = File.expand_path(path) | ||
@dir = File.dirname(path) | ||
end | ||
|
||
if File.exist?(@path) | ||
@data = JSON.parse(File.read(@path)) | ||
else | ||
@data = {} | ||
end | ||
end | ||
|
||
# Returns internal assets mapping. Keys are logical paths which | ||
# map to the latest fingerprinted filename. | ||
# | ||
# Logical path (String): Fingerprint path (String) | ||
# | ||
# { "application.js" => "application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js", | ||
# "jquery.js" => "jquery-ae0908555a245f8266f77df5a8edca2e.js" } | ||
# | ||
def assets | ||
@data['assets'] ||= {} | ||
end | ||
|
||
# Returns internal file directory listing. Keys are filenames | ||
# which map to an attributes array. | ||
# | ||
# Fingerprint path (String): | ||
# logical_path: Logical path (String) | ||
# mtime: ISO8601 mtime (String) | ||
# digest: Base64 hex digest (String) | ||
# | ||
# { "application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js" => | ||
# { 'logical_path' => "application.js", | ||
# 'mtime' => "2011-12-13T21:47:08-06:00", | ||
# 'digest' => "2e8e9a7c6b0aafa0c9bdeec90ea30213" } } | ||
# | ||
def files | ||
@data['files'] ||= {} | ||
end | ||
|
||
# Compile and write asset to directory. The asset is written to a | ||
# fingerprinted filename like | ||
# `application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js`. An entry is | ||
# also inserted into the manifest file. | ||
# | ||
# compile("application.js") | ||
# | ||
def compile(logical_path) | ||
if asset = find_asset(logical_path) | ||
files[asset.digest_path] = { | ||
'logical_path' => asset.logical_path, | ||
'mtime' => asset.mtime.iso8601, | ||
'digest' => asset.digest | ||
} | ||
assets[asset.logical_path] = asset.digest_path | ||
|
||
target = File.join(dir, asset.digest_path) | ||
|
||
if File.exist?(target) | ||
logger.debug "Skipping #{target}, already exists" | ||
else | ||
logger.info "Writing #{target}" | ||
asset.write_to target | ||
end | ||
|
||
save | ||
asset | ||
end | ||
end | ||
|
||
# Removes file from directory and from manifest. `filename` must | ||
# be the name with any directory path. | ||
# | ||
# manifest.remove("application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js") | ||
# | ||
def remove(filename) | ||
path = File.join(dir, filename) | ||
logical_path = files[filename]['logical_path'] | ||
|
||
if assets[logical_path] == filename | ||
assets.delete(logical_path) | ||
end | ||
|
||
files.delete(filename) | ||
FileUtils.rm(path) if File.exist?(path) | ||
|
||
save | ||
|
||
logger.warn "Removed #{filename}" | ||
|
||
nil | ||
end | ||
|
||
# Cleanup old assets in the compile directory. By default it will | ||
# keep the latest version plus 2 backups. | ||
def clean(keep = 2) | ||
self.assets.keys.each do |logical_path| | ||
# Get assets sorted by ctime, newest first | ||
assets = backups_for(logical_path) | ||
|
||
# Keep the last N backups | ||
assets = assets[keep..-1] || [] | ||
|
||
# Remove old assets | ||
assets.each { |path, _| remove(path) } | ||
end | ||
end | ||
|
||
# Wipe directive | ||
def clobber | ||
FileUtils.rm_r(@dir) if File.exist?(@dir) | ||
logger.warn "Removed #{@dir}" | ||
nil | ||
end | ||
|
||
protected | ||
# Finds all the backup assets for a logical path. The latest | ||
# version is always excluded. The return array is sorted by the | ||
# assets mtime in descending order (Newest to oldest). | ||
def backups_for(logical_path) | ||
files.select { |filename, attrs| | ||
# Matching logical paths | ||
attrs['logical_path'] == logical_path && | ||
# Excluding whatever asset is the current | ||
assets[logical_path] != filename | ||
}.sort_by { |filename, attrs| | ||
# Sort by timestamp | ||
Time.parse(attrs['mtime']) | ||
}.reverse | ||
end | ||
|
||
# Basic wrapper around Environment#find_asset. Logs compile time. | ||
def find_asset(logical_path) | ||
asset = nil | ||
ms = benchmark do | ||
asset = environment.find_asset(logical_path) | ||
end | ||
logger.warn "Compiled #{logical_path} (#{ms}ms)" | ||
asset | ||
end | ||
|
||
# Persist manfiest back to FS | ||
def save | ||
FileUtils.mkdir_p dir | ||
File.open(path, 'w') do |f| | ||
f.write JSON.generate(@data) | ||
end | ||
end | ||
|
||
private | ||
def logger | ||
environment.logger | ||
end | ||
|
||
def benchmark | ||
start_time = Time.now.to_f | ||
yield | ||
((Time.now.to_f - start_time) * 1000).to_i | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hrm,
bundle_dir
andbundles
?