lib/roda/plugins/assets.rb in roda-2.3.0 vs lib/roda/plugins/assets.rb in roda-2.4.0
- old
+ new
@@ -197,20 +197,26 @@
# :compiled_js_route :: Route under :prefix for compiled javscript assets (default: :compiled_js_dir)
# :compiled_name :: Compiled file name prefix (default: 'app')
# :compiled_path:: Path inside public folder in which compiled files are stored (default: :prefix)
# :concat_only :: Whether to just concatenate instead of concatentating
# and compressing files (default: false)
+ # :css_compressor :: Compressor to use for compressing CSS, either :yui, :none, or nil (the default, which will try
+ # :yui if available, but not fail if it is not available)
# :css_dir :: Directory name containing your css source, inside :path (default: 'css')
# :css_headers :: A hash of additional headers for your rendered css files
# :css_opts :: Template options to pass to the render plugin (via :template_opts) when rendering css assets
# :css_route :: Route under :prefix for css assets (default: :css_dir)
# :dependencies :: A hash of dependencies for your asset files. Keys should be paths to asset files,
# values should be arrays of paths your asset files depends on. This is used to
# detect changes in your asset files.
# :group_subdirs :: Whether a hash used in :css and :js options requires the assets for the
# related group are contained in a subdirectory with the same name (default: true)
+ # :gzip :: Store gzipped compiled assets files, and serve those to clients who accept gzip encoding.
# :headers :: A hash of additional headers for both js and css rendered files
+ # :js_compressor :: Compressor to use for compressing javascript, either :yui, :closure, :uglifier, :minjs,
+ # :none, or nil (the default, which will try :yui, :closure, :uglifier, then :minjs, but
+ # not fail if any of them is not available)
# :js_dir :: Directory name containing your javascript source, inside :path (default: 'js')
# :js_headers :: A hash of additional headers for your rendered javascript files
# :js_opts :: Template options to pass to the render plugin (via :template_opts) when rendering javascript assets
# :js_route :: Route under :prefix for javascript assets (default: :js_dir)
# :path :: Path to your asset source directory (default: 'assets'). Relative
@@ -241,11 +247,18 @@
SLASH = '/'.freeze
NEWLINE = "\n".freeze
EMPTY_STRING = ''.freeze
JS_SUFFIX = '.js'.freeze
CSS_SUFFIX = '.css'.freeze
+ HTTP_ACCEPT_ENCODING = 'HTTP_ACCEPT_ENCODING'.freeze
+ CONTENT_ENCODING = 'Content-Encoding'.freeze
+ GZIP = 'gzip'.freeze
+ DOTGZ = '.gz'.freeze
+ # Internal exception raised when a compressor cannot be found
+ CompressorNotFound = Class.new(RodaError)
+
# Load the render and caching plugins plugins, since the assets plugin
# depends on them.
def self.load_dependencies(app, _opts = nil)
app.plugin :render
app.plugin :caching
@@ -418,34 +431,101 @@
key = "#{type}#{suffix}"
unique_id = o[:compiled][key] = asset_digest(content)
path = "#{o[:"compiled_#{type}_path"]}#{suffix}.#{unique_id}.#{type}"
::FileUtils.mkdir_p(File.dirname(path))
::File.open(path, 'wb'){|f| f.write(content)}
+
+ if o[:gzip]
+ require 'zlib'
+ Zlib::GzipWriter.open("#{path}.gz") do |gz|
+ gz.write(content)
+ end
+ end
+
nil
end
# Compress the given content for the given type using yuicompressor,
# but handle cases where yuicompressor isn't installed or can't find
# a java runtime. This method can be overridden by the application
# to use a different compressor.
def compress_asset(content, type)
- require 'yuicompressor'
- # :nocov:
- content = YUICompressor.send("compress_#{type}", content, :munge => true)
- # :nocov:
- rescue LoadError, Errno::ENOENT
- # yuicompressor or java not available, just use concatenated, uncompressed output
+ case compressor = assets_opts[:"#{type}_compressor"]
+ when :none
+ return content
+ when nil
+ # default, try different compressors
+ else
+ return send("compress_#{type}_#{compressor}", content)
+ end
+
+ compressors = if type == :js
+ [:yui, :closure, :uglifier, :minjs]
+ else
+ [:yui]
+ end
+
+ compressors.each do |comp|
+ begin
+ if c = send("compress_#{type}_#{comp}", content)
+ return c
+ end
+ rescue LoadError, CompressorNotFound
+ end
+ end
+
content
end
+ # Compress the CSS using YUI Compressor, requires java runtime
+ def compress_css_yui(content)
+ compress_yui(content, :compress_css)
+ end
+
+ # Compress the JS using Google Closure Compiler, requires java runtime
+ def compress_js_closure(content)
+ require 'closure-compiler'
+
+ begin
+ ::Closure::Compiler.new.compile(content)
+ rescue ::Closure::Error => e
+ raise CompressorNotFound, "#{e.class}: #{e.message}", e.backtrace
+ end
+ end
+
+ # Compress the JS using MinJS, a pure ruby compressor
+ def compress_js_minjs(content)
+ require 'minjs'
+ ::Minjs::Compressor.new(:debug => false).compress(content)
+ end
+
+ # Compress the JS using Uglifier, requires javascript runtime
+ def compress_js_uglifier(content)
+ require 'uglifier'
+ Uglifier.compile(content)
+ end
+
+ # Compress the CSS using YUI Compressor, requires java runtime
+ def compress_js_yui(content)
+ compress_yui(content, :compress_js)
+ end
+
+ # Compress the CSS/JS using YUI Compressor, requires java runtime
+ def compress_yui(content, meth)
+ require 'yuicompressor'
+ ::YUICompressor.send(meth, content, :munge => true)
+ rescue ::Errno::ENOENT => e
+ raise CompressorNotFound, "#{e.class}: #{e.message}", e.backtrace
+ end
+
# Return a unique id for the given content. By default, uses the
# SHA1 hash of the content. This method can be overridden to use
# a different digest type or to return a static string if you don't
# want to use a unique value.
def asset_digest(content)
require 'digest/sha1'
- Digest::SHA1.hexdigest(content)
+ ::Digest::SHA1.hexdigest(content)
end
end
module InstanceMethods
# Return a string containing html tags for the given asset type.
@@ -510,9 +590,15 @@
# this will return a 304 response.
def render_asset(file, type)
o = self.class.assets_opts
if o[:compiled]
file = "#{o[:"compiled_#{type}_path"]}#{file}"
+
+ if o[:gzip] && env[HTTP_ACCEPT_ENCODING] =~ /\bgzip\b/
+ @_response[CONTENT_ENCODING] = GZIP
+ file << DOTGZ
+ end
+
check_asset_request(file, type, ::File.stat(file).mtime)
::File.read(file)
else
file = "#{o[:"#{type}_path"]}#{file}"
check_asset_request(file, type, asset_last_modified(file))