aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/sprockets
diff options
context:
space:
mode:
authorOlli Jokinen <olli.jokinen@enemy.fi>2011-12-01 15:32:59 +0200
committerOlli Jokinen <olli.jokinen@enemy.fi>2011-12-01 15:32:59 +0200
commitb4e1903d23a760028d58bc3bb20a1d491bfd4a4b (patch)
treea40bdce1bd4800124ab6eaed2a6be017bf9cfd3d /actionpack/lib/sprockets
parentfae9ad9c712decef70b379f5aa1faa0149902831 (diff)
parent1e51cd957e3c90f4be35f1f0c4c380d8f7d40d66 (diff)
downloadrails-b4e1903d23a760028d58bc3bb20a1d491bfd4a4b.tar.gz
rails-b4e1903d23a760028d58bc3bb20a1d491bfd4a4b.tar.bz2
rails-b4e1903d23a760028d58bc3bb20a1d491bfd4a4b.zip
Merge remote-tracking branch 'upstream/master'
Diffstat (limited to 'actionpack/lib/sprockets')
-rw-r--r--actionpack/lib/sprockets/assets.rake101
-rw-r--r--actionpack/lib/sprockets/bootstrap.rb37
-rw-r--r--actionpack/lib/sprockets/compressors.rb76
-rw-r--r--actionpack/lib/sprockets/helpers.rb3
-rw-r--r--actionpack/lib/sprockets/helpers/isolated_helper.rb13
-rw-r--r--actionpack/lib/sprockets/helpers/rails_helper.rb110
-rw-r--r--actionpack/lib/sprockets/railtie.rb79
-rw-r--r--actionpack/lib/sprockets/static_compiler.rb61
8 files changed, 358 insertions, 122 deletions
diff --git a/actionpack/lib/sprockets/assets.rake b/actionpack/lib/sprockets/assets.rake
index 50278cffcd..a61a121d55 100644
--- a/actionpack/lib/sprockets/assets.rake
+++ b/actionpack/lib/sprockets/assets.rake
@@ -1,24 +1,95 @@
+require "fileutils"
+
namespace :assets do
- # Ensures the RAILS_GROUPS environment variable is set
- task :ensure_env do
- ENV["RAILS_GROUPS"] ||= "assets"
+ def ruby_rake_task(task)
+ env = ENV['RAILS_ENV'] || 'production'
+ groups = ENV['RAILS_GROUPS'] || 'assets'
+ args = [$0, task,"RAILS_ENV=#{env}","RAILS_GROUPS=#{groups}"]
+ args << "--trace" if Rake.application.options.trace
+ ruby(*args)
+ end
+
+ # We are currently running with no explicit bundler group
+ # and/or no explicit environment - we have to reinvoke rake to
+ # execute this task.
+ def invoke_or_reboot_rake_task(task)
+ if ENV['RAILS_GROUPS'].to_s.empty? || ENV['RAILS_ENV'].to_s.empty?
+ ruby_rake_task task
+ else
+ Rake::Task[task].invoke
+ end
end
desc "Compile all the assets named in config.assets.precompile"
- task :precompile => :ensure_env do
- Rake::Task["environment"].invoke
- Sprockets::Helpers::RailsHelper
-
- assets = Rails.application.config.assets.precompile
- # Always perform caching so that asset_path appends the timestamps to file references.
- Rails.application.config.action_controller.perform_caching = true
- Rails.application.assets.precompile(*assets)
+ task :precompile do
+ invoke_or_reboot_rake_task "assets:precompile:all"
+ end
+
+ namespace :precompile do
+ def internal_precompile(digest=nil)
+ unless Rails.application.config.assets.enabled
+ warn "Cannot precompile assets if sprockets is disabled. Please set config.assets.enabled to true"
+ exit
+ end
+
+ # Ensure that action view is loaded and the appropriate
+ # sprockets hooks get executed
+ _ = ActionView::Base
+
+ config = Rails.application.config
+ config.assets.compile = true
+ config.assets.digest = digest unless digest.nil?
+ config.assets.digests = {}
+
+ env = Rails.application.assets
+ target = File.join(Rails.public_path, config.assets.prefix)
+ compiler = Sprockets::StaticCompiler.new(env,
+ target,
+ config.assets.precompile,
+ :manifest_path => config.assets.manifest,
+ :digest => config.assets.digest,
+ :manifest => digest.nil?)
+ compiler.compile
+ end
+
+ task :all do
+ Rake::Task["assets:precompile:primary"].invoke
+ # We need to reinvoke in order to run the secondary digestless
+ # asset compilation run - a fresh Sprockets environment is
+ # required in order to compile digestless assets as the
+ # environment has already cached the assets on the primary
+ # run.
+ ruby_rake_task "assets:precompile:nondigest" if Rails.application.config.assets.digest
+ end
+
+ task :primary => ["assets:environment", "tmp:cache:clear"] do
+ internal_precompile
+ end
+
+ task :nondigest => ["assets:environment", "tmp:cache:clear"] do
+ internal_precompile(false)
+ end
end
desc "Remove compiled assets"
- task :clean => :environment do
- assets = Rails.application.config.assets
- public_asset_path = Rails.public_path + assets.prefix
- rm_rf public_asset_path, :secure => true
+ task :clean do
+ invoke_or_reboot_rake_task "assets:clean:all"
+ end
+
+ namespace :clean do
+ task :all => ["assets:environment", "tmp:cache:clear"] do
+ config = Rails.application.config
+ public_asset_path = File.join(Rails.public_path, config.assets.prefix)
+ rm_rf public_asset_path, :secure => true
+ end
+ end
+
+ task :environment do
+ if Rails.application.config.assets.initialize_on_precompile
+ Rake::Task["environment"].invoke
+ else
+ Rails.application.initialize!(:assets)
+ Sprockets::Bootstrap.new(Rails.application).run
+ end
end
end
diff --git a/actionpack/lib/sprockets/bootstrap.rb b/actionpack/lib/sprockets/bootstrap.rb
new file mode 100644
index 0000000000..395b264fe7
--- /dev/null
+++ b/actionpack/lib/sprockets/bootstrap.rb
@@ -0,0 +1,37 @@
+module Sprockets
+ class Bootstrap
+ def initialize(app)
+ @app = app
+ end
+
+ # TODO: Get rid of config.assets.enabled
+ def run
+ app, config = @app, @app.config
+ return unless app.assets
+
+ config.assets.paths.each { |path| app.assets.append_path(path) }
+
+ if config.assets.compress
+ # temporarily hardcode default JS compressor to uglify. Soon, it will work
+ # the same as SCSS, where a default plugin sets the default.
+ unless config.assets.js_compressor == false
+ app.assets.js_compressor = LazyCompressor.new { Sprockets::Compressors.registered_js_compressor(config.assets.js_compressor || :uglifier) }
+ end
+
+ unless config.assets.css_compressor == false
+ app.assets.css_compressor = LazyCompressor.new { Sprockets::Compressors.registered_css_compressor(config.assets.css_compressor) }
+ end
+ end
+
+ if config.assets.compile
+ app.routes.prepend do
+ mount app.assets => config.assets.prefix
+ end
+ end
+
+ if config.assets.digest
+ app.assets = app.assets.index
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/sprockets/compressors.rb b/actionpack/lib/sprockets/compressors.rb
index 6544953df4..cb3e13314b 100644
--- a/actionpack/lib/sprockets/compressors.rb
+++ b/actionpack/lib/sprockets/compressors.rb
@@ -1,21 +1,83 @@
module Sprockets
- class NullCompressor
+ module Compressors
+ @@css_compressors = {}
+ @@js_compressors = {}
+ @@default_css_compressor = nil
+ @@default_js_compressor = nil
+
+ def self.register_css_compressor(name, klass, options = {})
+ @@default_css_compressor = name.to_sym if options[:default] || @@default_css_compressor.nil?
+ @@css_compressors[name.to_sym] = {:klass => klass.to_s, :require => options[:require]}
+ end
+
+ def self.register_js_compressor(name, klass, options = {})
+ @@default_js_compressor = name.to_sym if options[:default] || @@default_js_compressor.nil?
+ @@js_compressors[name.to_sym] = {:klass => klass.to_s, :require => options[:require]}
+ end
+
+ def self.registered_css_compressor(name)
+ if name.respond_to?(:to_sym)
+ compressor = @@css_compressors[name.to_sym] || @@css_compressors[@@default_css_compressor]
+ require compressor[:require] if compressor[:require]
+ compressor[:klass].constantize.new
+ else
+ name
+ end
+ end
+
+ def self.registered_js_compressor(name)
+ if name.respond_to?(:to_sym)
+ compressor = @@js_compressors[name.to_sym] || @@js_compressors[@@default_js_compressor]
+ require compressor[:require] if compressor[:require]
+ compressor[:klass].constantize.new
+ else
+ name
+ end
+ end
+
+ # The default compressors must be registered in default plugins (ex. Sass-Rails)
+ register_css_compressor(:scss, 'Sass::Rails::Compressor', :require => 'sass/rails/compressor', :default => true)
+ register_js_compressor(:uglifier, 'Uglifier', :require => 'uglifier', :default => true)
+
+ # Automaticaly register some compressors
+ register_css_compressor(:yui, 'YUI::CssCompressor', :require => 'yui/compressor')
+ register_js_compressor(:closure, 'Closure::Compiler', :require => 'closure-compiler')
+ register_js_compressor(:yui, 'YUI::JavaScriptCompressor', :require => 'yui/compressor')
+ end
+
+ # An asset compressor which does nothing.
+ #
+ # This compressor simply returns the asset as-is, without any compression
+ # whatsoever. It is useful in development mode, when compression isn't
+ # needed but using the same asset pipeline as production is desired.
+ class NullCompressor #:nodoc:
def compress(content)
content
end
end
- class LazyCompressor
+ # An asset compressor which only initializes the underlying compression
+ # engine when needed.
+ #
+ # This postpones the initialization of the compressor until
+ # <code>#compress</code> is called the first time.
+ class LazyCompressor #:nodoc:
+ # Initializes a new LazyCompressor.
+ #
+ # The block should return a compressor when called, i.e. an object
+ # which responds to <code>#compress</code>.
def initialize(&block)
@block = block
end
- def compressor
- @compressor ||= @block.call || NullCompressor.new
- end
-
def compress(content)
compressor.compress(content)
end
+
+ private
+
+ def compressor
+ @compressor ||= (@block.call || NullCompressor.new)
+ end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/sprockets/helpers.rb b/actionpack/lib/sprockets/helpers.rb
index a952a55c5e..fee48386e0 100644
--- a/actionpack/lib/sprockets/helpers.rb
+++ b/actionpack/lib/sprockets/helpers.rb
@@ -1,5 +1,6 @@
module Sprockets
module Helpers
- autoload :RailsHelper, "sprockets/helpers/rails_helper"
+ autoload :RailsHelper, "sprockets/helpers/rails_helper"
+ autoload :IsolatedHelper, "sprockets/helpers/isolated_helper"
end
end
diff --git a/actionpack/lib/sprockets/helpers/isolated_helper.rb b/actionpack/lib/sprockets/helpers/isolated_helper.rb
new file mode 100644
index 0000000000..3adb928c45
--- /dev/null
+++ b/actionpack/lib/sprockets/helpers/isolated_helper.rb
@@ -0,0 +1,13 @@
+module Sprockets
+ module Helpers
+ module IsolatedHelper
+ def controller
+ nil
+ end
+
+ def config
+ Rails.application.config.action_controller
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/sprockets/helpers/rails_helper.rb b/actionpack/lib/sprockets/helpers/rails_helper.rb
index ec3d36d5ad..1ebe7f68f7 100644
--- a/actionpack/lib/sprockets/helpers/rails_helper.rb
+++ b/actionpack/lib/sprockets/helpers/rails_helper.rb
@@ -8,12 +8,11 @@ module Sprockets
def asset_paths
@asset_paths ||= begin
- config = self.config if respond_to?(:config)
- config ||= Rails.application.config
- controller = self.controller if respond_to?(:controller)
paths = RailsHelper::AssetPaths.new(config, controller)
paths.asset_environment = asset_environment
- paths.asset_prefix = asset_prefix
+ paths.asset_digests = asset_digests
+ paths.compile_assets = compile_assets?
+ paths.digest_assets = digest_assets?
paths
end
end
@@ -22,56 +21,60 @@ module Sprockets
options = sources.extract_options!
debug = options.key?(:debug) ? options.delete(:debug) : debug_assets?
body = options.key?(:body) ? options.delete(:body) : false
+ digest = options.key?(:digest) ? options.delete(:digest) : digest_assets?
sources.collect do |source|
if debug && asset = asset_paths.asset_for(source, 'js')
asset.to_a.map { |dep|
- javascript_include_tag(dep, :debug => false, :body => true)
- }.join("\n").html_safe
+ super(dep.to_s, { :src => asset_path(dep, :ext => 'js', :body => true, :digest => digest) }.merge!(options))
+ }
else
- tag_options = {
- 'type' => "text/javascript",
- 'src' => asset_path(source, 'js', body)
- }.merge(options.stringify_keys)
-
- content_tag 'script', "", tag_options
+ super(source.to_s, { :src => asset_path(source, :ext => 'js', :body => body, :digest => digest) }.merge!(options))
end
end.join("\n").html_safe
end
def stylesheet_link_tag(*sources)
options = sources.extract_options!
- debug = options.key?(:debug) ? options.delete(:debug) : debug_assets?
- body = options.key?(:body) ? options.delete(:body) : false
+ debug = options.key?(:debug) ? options.delete(:debug) : debug_assets?
+ body = options.key?(:body) ? options.delete(:body) : false
+ digest = options.key?(:digest) ? options.delete(:digest) : digest_assets?
sources.collect do |source|
if debug && asset = asset_paths.asset_for(source, 'css')
asset.to_a.map { |dep|
- stylesheet_link_tag(dep, :debug => false, :body => true)
- }.join("\n").html_safe
+ super(dep.to_s, { :href => asset_path(dep, :ext => 'css', :body => true, :protocol => :request, :digest => digest) }.merge!(options))
+ }
else
- tag_options = {
- 'rel' => "stylesheet",
- 'type' => "text/css",
- 'media' => "screen",
- 'href' => asset_path(source, 'css', body, :request)
- }.merge(options.stringify_keys)
-
- tag 'link', tag_options
+ super(source.to_s, { :href => asset_path(source, :ext => 'css', :body => body, :protocol => :request, :digest => digest) }.merge!(options))
end
end.join("\n").html_safe
end
- def asset_path(source, default_ext = nil, body = false, protocol = nil)
+ def asset_path(source, options = {})
source = source.logical_path if source.respond_to?(:logical_path)
- path = asset_paths.compute_public_path(source, 'assets', default_ext, true, protocol)
- body ? "#{path}?body=1" : path
+ path = asset_paths.compute_public_path(source, asset_prefix, options.merge(:body => true))
+ options[:body] ? "#{path}?body=1" : path
+ end
+
+ def image_path(source)
+ asset_path(source)
+ end
+ alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
+
+ def javascript_path(source)
+ asset_path(source)
end
+ alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with an javascript_path named route
+
+ def stylesheet_path(source)
+ asset_path(source)
+ end
+ alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with an stylesheet_path named route
private
def debug_assets?
- params[:debug_assets] == '1' ||
- params[:debug_assets] == 'true'
+ compile_assets? && (Rails.application.config.assets.debug || params[:debug_assets])
rescue NoMethodError
false
end
@@ -86,6 +89,18 @@ module Sprockets
Rails.application.config.assets.prefix
end
+ def asset_digests
+ Rails.application.config.assets.digests
+ end
+
+ def compile_assets?
+ Rails.application.config.assets.compile
+ end
+
+ def digest_assets?
+ Rails.application.config.assets.digest
+ end
+
# Override to specify an alternative asset environment for asset
# path generation. The environment should already have been mounted
# at the prefix returned by +asset_prefix+.
@@ -94,11 +109,9 @@ module Sprockets
end
class AssetPaths < ::ActionView::AssetPaths #:nodoc:
- attr_accessor :asset_environment, :asset_prefix
+ attr_accessor :asset_environment, :asset_prefix, :asset_digests, :compile_assets, :digest_assets
- def compute_public_path(source, dir, ext=nil, include_host=true, protocol=nil)
- super(source, asset_prefix, ext, include_host, protocol)
- end
+ class AssetNotPrecompiledError < StandardError; end
# Return the filesystem path for the source
def compute_source_path(source, ext)
@@ -110,28 +123,43 @@ module Sprockets
return nil if is_uri?(source)
source = rewrite_extension(source, nil, ext)
asset_environment[source]
+ rescue Sprockets::FileOutsidePaths
+ nil
+ end
+
+ def digest_for(logical_path)
+ if digest_assets && asset_digests && (digest = asset_digests[logical_path])
+ return digest
+ end
+
+ if compile_assets
+ if digest_assets && asset = asset_environment[logical_path]
+ return asset.digest_path
+ end
+ return logical_path
+ else
+ raise AssetNotPrecompiledError.new("#{logical_path} isn't precompiled")
+ end
end
- def rewrite_asset_path(source, dir)
+ def rewrite_asset_path(source, dir, options = {})
if source[0] == ?/
source
else
- asset_environment.path(source, performing_caching?, dir)
+ source = digest_for(source) unless options[:digest] == false
+ source = File.join(dir, source)
+ source = "/#{source}" unless source =~ /^\//
+ source
end
end
def rewrite_extension(source, dir, ext)
- if ext && File.extname(source).empty?
+ if ext && File.extname(source) != ".#{ext}"
"#{source}.#{ext}"
else
source
end
end
-
- # When included in Sprockets::Context, we need to ask the top-level config as the controller is not available
- def performing_caching?
- config.action_controller.present? ? config.action_controller.perform_caching : config.perform_caching
- end
end
end
end
diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb
index c8438e6043..a7eb03acaf 100644
--- a/actionpack/lib/sprockets/railtie.rb
+++ b/actionpack/lib/sprockets/railtie.rb
@@ -1,35 +1,48 @@
+require "action_controller/railtie"
+
module Sprockets
- autoload :Helpers, "sprockets/helpers"
+ autoload :Bootstrap, "sprockets/bootstrap"
+ autoload :Helpers, "sprockets/helpers"
+ autoload :Compressors, "sprockets/compressors"
autoload :LazyCompressor, "sprockets/compressors"
autoload :NullCompressor, "sprockets/compressors"
+ autoload :StaticCompiler, "sprockets/static_compiler"
# TODO: Get rid of config.assets.enabled
class Railtie < ::Rails::Railtie
- config.default_asset_host_protocol = :relative
-
rake_tasks do
load "sprockets/assets.rake"
end
- initializer "sprockets.environment" do |app|
+ initializer "sprockets.environment", :group => :all do |app|
config = app.config
next unless config.assets.enabled
require 'sprockets'
app.assets = Sprockets::Environment.new(app.root.to_s) do |env|
- env.static_root = File.join(app.root.join('public'), config.assets.prefix)
- env.logger = ::Rails.logger
+ env.logger = ::Rails.logger
+ env.version = ::Rails.env + "-#{config.assets.version}"
if config.assets.cache_store != false
env.cache = ActiveSupport::Cache.lookup_store(config.assets.cache_store) || ::Rails.cache
end
end
+ if config.assets.manifest
+ path = File.join(config.assets.manifest, "manifest.yml")
+ else
+ path = File.join(Rails.public_path, config.assets.prefix, "manifest.yml")
+ end
+
+ if File.exist?(path)
+ config.assets.digests = YAML.load_file(path)
+ end
+
ActiveSupport.on_load(:action_view) do
include ::Sprockets::Helpers::RailsHelper
-
app.assets.context_class.instance_eval do
+ include ::Sprockets::Helpers::IsolatedHelper
include ::Sprockets::Helpers::RailsHelper
end
end
@@ -40,57 +53,7 @@ module Sprockets
# are compiled, and so that other Railties have an opportunity to
# register compressors.
config.after_initialize do |app|
- next unless app.assets
- config = app.config
-
- config.assets.paths.each { |path| app.assets.append_path(path) }
-
- if config.assets.compress
- # temporarily hardcode default JS compressor to uglify. Soon, it will work
- # the same as SCSS, where a default plugin sets the default.
- unless config.assets.js_compressor == false
- app.assets.js_compressor = LazyCompressor.new { expand_js_compressor(config.assets.js_compressor || :uglifier) }
- end
-
- unless config.assets.css_compressor == false
- app.assets.css_compressor = LazyCompressor.new { expand_css_compressor(config.assets.css_compressor) }
- end
- end
-
- app.routes.prepend do
- mount app.assets => config.assets.prefix
- end
-
- if config.action_controller.perform_caching
- app.assets = app.assets.index
- end
+ Sprockets::Bootstrap.new(app).run
end
-
- protected
- def expand_js_compressor(sym)
- case sym
- when :closure
- require 'closure-compiler'
- Closure::Compiler.new
- when :uglifier
- require 'uglifier'
- Uglifier.new
- when :yui
- require 'yui/compressor'
- YUI::JavaScriptCompressor.new
- else
- sym
- end
- end
-
- def expand_css_compressor(sym)
- case sym
- when :yui
- require 'yui/compressor'
- YUI::CssCompressor.new
- else
- sym
- end
- end
end
end
diff --git a/actionpack/lib/sprockets/static_compiler.rb b/actionpack/lib/sprockets/static_compiler.rb
new file mode 100644
index 0000000000..32a9d66e6e
--- /dev/null
+++ b/actionpack/lib/sprockets/static_compiler.rb
@@ -0,0 +1,61 @@
+require 'fileutils'
+
+module Sprockets
+ class StaticCompiler
+ attr_accessor :env, :target, :paths
+
+ def initialize(env, target, paths, options = {})
+ @env = env
+ @target = target
+ @paths = paths
+ @digest = options.key?(:digest) ? options.delete(:digest) : true
+ @manifest = options.key?(:manifest) ? options.delete(:manifest) : true
+ @manifest_path = options.delete(:manifest_path) || target
+ end
+
+ def compile
+ manifest = {}
+ env.each_logical_path do |logical_path|
+ next unless compile_path?(logical_path)
+ if asset = env.find_asset(logical_path)
+ manifest[logical_path] = write_asset(asset)
+ end
+ end
+ write_manifest(manifest) if @manifest
+ end
+
+ def write_manifest(manifest)
+ FileUtils.mkdir_p(@manifest_path)
+ File.open("#{@manifest_path}/manifest.yml", 'wb') do |f|
+ YAML.dump(manifest, f)
+ end
+ end
+
+ def write_asset(asset)
+ path_for(asset).tap do |path|
+ filename = File.join(target, path)
+ FileUtils.mkdir_p File.dirname(filename)
+ asset.write_to(filename)
+ asset.write_to("#{filename}.gz") if filename.to_s =~ /\.(css|js)$/
+ end
+ end
+
+ def compile_path?(logical_path)
+ paths.each do |path|
+ case path
+ when Regexp
+ return true if path.match(logical_path)
+ when Proc
+ return true if path.call(logical_path)
+ else
+ return true if File.fnmatch(path.to_s, logical_path)
+ end
+ end
+ false
+ end
+
+ def path_for(asset)
+ @digest ? asset.digest_path : asset.logical_path
+ end
+ end
+end