Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

first

  • Loading branch information...
commit 0a12aa7488ccb8ef8ca9db65350ce72bc772af64 0 parents
@jamesmacaulay authored
6 .gitignore
@@ -0,0 +1,6 @@
+.bundle
+db/*.sqlite3
+log/*.log
+tmp/
+
+Gemfile.lock
3  Gemfile
@@ -0,0 +1,3 @@
+source :rubygems
+
+gemspec
20 MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2011 Shopify
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOa AND
+NONINFRINGEMENT. IN NO EVENT SaALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41 README.markdown
@@ -0,0 +1,41 @@
+# Backport of Rails 3.1.x Sprockets integration to Rails 3.0.x
+
+## Usage
+
+In your `Gemfile`:
+
+ gem "sprockets_rails3_backport"
+
+In your `routes.rb`:
+
+ MyApp::Application.routes.draw do
+ if (app = Rails.application).config.assets.compile
+ mount app.assets => app.config.assets.prefix
+ end
+
+ # ...
+ end
+
+Here are the various `config.assets` options and their defaults:
+
+ config.assets.paths = []
+ config.assets.precompile = [ Proc.new{ |path| !['.js', '.css'].include?(File.extname(path)) },
+ /(?:\/|\\|\A)application\.(css|js)$/ ]
+ config.assets.prefix = "/assets"
+ config.assets.version = ''
+ config.assets.debug = false
+ config.assets.compile = true
+ config.assets.digest = false
+ config.assets.manifest = nil
+ config.assets.cache_store = false
+ config.assets.js_compressor = nil
+ config.assets.css_compressor = nil
+ config.assets.initialize_on_precompile = true
+
+
+## Differences from Rails 3.1.3
+
+* no `config.assets.enabled`
+* `config.assets.cache_store` defaults to false, so you probably want to set it yourself
+
+
137 lib/action_view/asset_paths.rb
@@ -0,0 +1,137 @@
+require 'zlib'
+require 'active_support/core_ext/file'
+
+module ActionView
+
+ class AssetPaths #:nodoc:
+ attr_reader :config, :controller
+
+ def initialize(config, controller = nil)
+ @config = config
+ @controller = controller
+ end
+
+ # Add the extension +ext+ if not present. Return full or scheme-relative URLs otherwise untouched.
+ # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
+ # roots. Rewrite the asset path for cache-busting asset ids. Include
+ # asset host, if configured, with the correct request protocol.
+ #
+ # When :relative (default), the protocol will be determined by the client using current protocol
+ # When :request, the protocol will be the request protocol
+ # Otherwise, the protocol is used (E.g. :http, :https, etc)
+ def compute_public_path(source, dir, options = {})
+ source = source.to_s
+ return source if is_uri?(source)
+
+ source = rewrite_extension(source, dir, options[:ext]) if options[:ext]
+ source = rewrite_asset_path(source, dir, options)
+ source = rewrite_relative_url_root(source, relative_url_root)
+ source = rewrite_host_and_protocol(source, options[:protocol])
+ source
+ end
+
+ # Return the filesystem path for the source
+ def compute_source_path(source, dir, ext)
+ source = rewrite_extension(source, dir, ext) if ext
+ File.join(config.assets_dir, dir, source)
+ end
+
+ def is_uri?(path)
+ path =~ %r{^[-a-z]+://|^cid:|^//}
+ end
+
+ private
+
+ def rewrite_extension(source, dir, ext)
+ raise NotImplementedError
+ end
+
+ def rewrite_asset_path(source, path = nil)
+ raise NotImplementedError
+ end
+
+ def rewrite_relative_url_root(source, relative_url_root)
+ relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source
+ end
+
+ def has_request?
+ controller.respond_to?(:request)
+ end
+
+ def rewrite_host_and_protocol(source, protocol = nil)
+ host = compute_asset_host(source)
+ if host && !is_uri?(host)
+ if (protocol || default_protocol) == :request && !has_request?
+ host = nil
+ else
+ host = "#{compute_protocol(protocol)}#{host}"
+ end
+ end
+ host.nil? ? source : "#{host}#{source}"
+ end
+
+ def compute_protocol(protocol)
+ protocol ||= default_protocol
+ case protocol
+ when :relative
+ "//"
+ when :request
+ unless @controller
+ invalid_asset_host!("The protocol requested was :request. Consider using :relative instead.")
+ end
+ @controller.request.protocol
+ else
+ "#{protocol}://"
+ end
+ end
+
+ def default_protocol
+ @config.default_asset_host_protocol || (has_request? ? :request : :relative)
+ end
+
+ def invalid_asset_host!(help_message)
+ raise ActionController::RoutingError, "This asset host cannot be computed without a request in scope. #{help_message}"
+ end
+
+ # Pick an asset host for this source. Returns +nil+ if no host is set,
+ # the host if no wildcard is set, the host interpolated with the
+ # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
+ # or the value returned from invoking the proc if it's a proc or the value from
+ # invoking call if it's an object responding to call.
+ def compute_asset_host(source)
+ if host = asset_host_config
+ if host.respond_to?(:call)
+ args = [source]
+ arity = arity_of(host)
+ if arity > 1 && !has_request?
+ invalid_asset_host!("Remove the second argument to your asset_host Proc if you do not need the request.")
+ end
+ args << current_request if (arity > 1 || arity < 0) && has_request?
+ host.call(*args)
+ else
+ (host =~ /%d/) ? host % (Zlib.crc32(source) % 4) : host
+ end
+ end
+ end
+
+ def relative_url_root
+ config.relative_url_root
+ end
+
+ def asset_host_config
+ config.asset_host
+ end
+
+ # Returns the current request if one exists.
+ def current_request
+ controller.request if has_request?
+ end
+
+ # Returns the arity of a callable
+ def arity_of(callable)
+ callable.respond_to?(:arity) ? callable.arity : callable.method(:call).arity
+ end
+
+ end
+
+end
5 lib/extensions/application_ext.rb
@@ -0,0 +1,5 @@
+module Rails
+ class Application < Engine
+ attr_accessor :assets
+ end
+end
95 lib/sprockets/assets.rake
@@ -0,0 +1,95 @@
+require "fileutils"
+
+namespace :assets do
+ 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 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 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
68 lib/sprockets/bootstrap.rb
@@ -0,0 +1,68 @@
+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 { 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
+
+ ## I can't figure out how to do this properly in Rails 3.0, so you
+ ## need to add the mount explicitly in your routes.rb (see README)
+ #
+ # 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
+
+ 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
21 lib/sprockets/compressors.rb
@@ -0,0 +1,21 @@
+module Sprockets
+ class NullCompressor
+ def compress(content)
+ content
+ end
+ end
+
+ class LazyCompressor
+ def initialize(&block)
+ @block = block
+ end
+
+ def compressor
+ @compressor ||= @block.call || NullCompressor.new
+ end
+
+ def compress(content)
+ compressor.compress(content)
+ end
+ end
+end
7 lib/sprockets/helpers.rb
@@ -0,0 +1,7 @@
+
+module Sprockets
+ module Helpers
+ autoload :RailsHelper, "sprockets/helpers/rails_helper"
+ autoload :IsolatedHelper, "sprockets/helpers/isolated_helper"
+ end
+end
13 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
171 lib/sprockets/helpers/rails_helper.rb
@@ -0,0 +1,171 @@
+require 'action_view/asset_paths'
+
+module Sprockets
+ module Helpers
+ module RailsHelper
+ extend ActiveSupport::Concern
+ include ActionView::Helpers::AssetTagHelper
+
+ def asset_paths
+ @asset_paths ||= begin
+ paths = RailsHelper::AssetPaths.new(config, controller)
+ paths.asset_environment = asset_environment
+ paths.asset_digests = asset_digests
+ paths.compile_assets = compile_assets?
+ paths.digest_assets = digest_assets?
+ paths
+ end
+ end
+
+ def javascript_include_tag(*sources)
+ 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|
+ super(dep.to_s, { :src => asset_path(dep, :ext => 'js', :body => true, :digest => digest) }.merge!(options))
+ }
+ else
+ 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
+ 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|
+ super(dep.to_s, { :href => asset_path(dep, :ext => 'css', :body => true, :protocol => :request, :digest => digest) }.merge!(options))
+ }
+ else
+ 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, options = {})
+ source = source.logical_path if source.respond_to?(:logical_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?
+ begin
+ compile_assets? &&
+ (Rails.application.config.assets.debug ||
+ params[:debug_assets] == '1' ||
+ params[:debug_assets] == 'true')
+ rescue NoMethodError
+ false
+ end
+ end
+
+ # Override to specify an alternative prefix for asset path generation.
+ # When combined with a custom +asset_environment+, this can be used to
+ # implement themes that can take advantage of the asset pipeline.
+ #
+ # If you only want to change where the assets are mounted, refer to
+ # +config.assets.prefix+ instead.
+ def asset_prefix
+ 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+.
+ def asset_environment
+ Rails.application.assets
+ end
+
+ class AssetPaths < ::ActionView::AssetPaths #:nodoc:
+ attr_accessor :asset_environment, :asset_prefix, :asset_digests, :compile_assets, :digest_assets
+
+ class AssetNotPrecompiledError < StandardError; end
+
+ # Return the filesystem path for the source
+ def compute_source_path(source, ext)
+ asset_for(source, ext)
+ end
+
+ def asset_for(source, ext)
+ source = source.to_s
+ 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, options = {})
+ if source[0] == ?/
+ source
+ else
+ 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?
+ "#{source}.#{ext}"
+ else
+ source
+ end
+ end
+ end
+ end
+ end
+end
76 lib/sprockets/railtie.rb
@@ -0,0 +1,76 @@
+# require "action_controller/railtie"
+require "extensions/application_ext"
+
+module Sprockets
+ autoload :Bootstrap, "sprockets/bootstrap"
+ autoload :Helpers, "sprockets/helpers"
+ 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.action_controller.default_asset_host_protocol = :relative
+ config.assets = ActiveSupport::OrderedOptions.new
+ # config.assets.enabled = false
+ config.assets.paths = []
+ config.assets.precompile = [ Proc.new{ |path| !['.js', '.css'].include?(File.extname(path)) },
+ /(?:\/|\\|\A)application\.(css|js)$/ ]
+ config.assets.prefix = "/assets"
+ config.assets.version = ''
+ config.assets.debug = false
+ config.assets.compile = true
+ config.assets.digest = false
+ config.assets.manifest = nil
+ config.assets.cache_store = false
+ config.assets.js_compressor = nil
+ config.assets.css_compressor = nil
+ config.assets.initialize_on_precompile = true
+
+ rake_tasks do
+ load "sprockets/assets.rake"
+ end
+
+ 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.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
+ end
+
+ # We need to configure this after initialization to ensure we collect
+ # paths from all engines. This hook is invoked exactly before routes
+ # are compiled, and so that other Railties have an opportunity to
+ # register compressors.
+ config.after_initialize do |app|
+ Sprockets::Bootstrap.new(app).run
+ end
+ end
+end
61 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
1  lib/sprockets_rails3_backport.rb
@@ -0,0 +1 @@
+require "sprockets/railtie"
20 sprockets_rails3_backport.gemspec
@@ -0,0 +1,20 @@
+lib = File.expand_path('../lib/', __FILE__)
+$:.unshift lib unless $:.include?(lib)
+
+Gem::Specification.new do |s|
+ s.name = "sprockets_rails3_backport"
+ s.version = '0.0.1'
+ s.platform = Gem::Platform::RUBY
+ s.authors = ["James MacAulay"]
+ s.email = ["james@shopify.com"]
+ s.homepage = "http://github.com/jamesmacaulay/sprockets_rails3_backport"
+ s.summary = "Rails 3.1.x Sprockets functionality for Rails 3.0.x"
+
+ s.required_rubygems_version = ">= 1.3.6"
+
+ s.add_dependency('rails', '~> 3.0.0')
+ s.add_dependency('sprockets', '2.1.2')
+
+ s.files = Dir.glob("lib/**/*") + %w(MIT-LICENSE README.markdown)
+ s.require_path = 'lib'
+end
Please sign in to comment.
Something went wrong with that request. Please try again.