class BundleFu class << self attr_accessor :content_store def init @content_store = {} end def bundle_files(filenames=[]) output = "" filenames.each{ |filename| filename_no_root = filename.sub(/^#{ActionController::Base.relative_url_root}/, '') output << "/* --------- #{filename} - #{filename_no_root} --- ------ */ " output << "\n" begin content = (File.read(File.join(RAILS_ROOT, "public", filename_no_root))) rescue output << "/* FILE READ ERROR! */" next end output << (yield(filename, content)||"") } output end def bundle_js_files(filenames=[], options={}) output = bundle_files(filenames) { |filename, content| if options[:compress] if Object.const_defined?("Packr") content else JSMinimizer.minimize_content(content) end else content end } if Object.const_defined?("Packr") # use Packr plugin (http://blog.jcoglan.com/packr/) Packr.new.pack(output, options[:packr_options] || {:shrink_vars => false, :base62 => false}) else output end end def bundle_css_files(filenames=[], options = {}) bundle_files(filenames) { |filename, content| BundleFu::CSSUrlRewriter.rewrite_urls(filename, content) } end end self.init module InstanceMethods # valid options: # :name - The name of the css and js files you wish to output # returns true if a regen occured. False if not. def bundle(options={}, &block) # allow bypassing via the querystring session[:bundle_fu] = (params[:bundle_fu]=="true") if params.has_key?(:bundle_fu) options = { :css_path => ($bundle_css_path || "/stylesheets/cache"), :js_path => ($bundle_js_path || "/javascripts/cache"), :name => ($bundle_default_name || "bundle"), :compress => true, :bundle_fu => ( session[:bundle_fu].nil? ? ($bundle_fu.nil? ? true : $bundle_fu) : session[:bundle_fu] ) }.merge(options) # allow them to bypass via parameter options[:bundle_fu] = false if options[:bypass] paths = { :css => options[:css_path], :js => options[:js_path] } content = capture(&block) content_changed = false new_files = nil abs_filelist_paths = [:css, :js].inject({}) { | hash, filetype | hash[filetype] = File.join(RAILS_ROOT, "public", paths[filetype], "#{options[:name]}.#{filetype}.filelist"); hash } # only rescan file list if content_changed, or if a filelist cache file is missing unless content == BundleFu.content_store[options[:name]] && File.exists?(abs_filelist_paths[:css]) && File.exists?(abs_filelist_paths[:js]) BundleFu.content_store[options[:name]] = content new_files = {:js => [], :css => []} content.scan(/(href|src) *= *["']([^"^'^\?]+)/i).each{ |property, value| case property when "src" new_files[:js] << value when "href" new_files[:css] << value end } end [:css, :js].each { |filetype| output_filename = File.join(paths[filetype], "#{options[:name]}.#{filetype}") abs_path = File.join(RAILS_ROOT, "public", output_filename) abs_filelist_path = abs_filelist_paths[filetype] filelist = FileList.open( abs_filelist_path ) # check against newly parsed filelist. If we didn't parse the filelist from the output, then check against the updated mtimes. new_filelist = new_files ? BundleFu::FileList.new(new_files[filetype]) : filelist.clone.update_mtimes unless new_filelist == filelist FileUtils.mkdir_p(File.join(RAILS_ROOT, "public", paths[filetype])) # regenerate everything if new_filelist.filenames.empty? # delete the javascript/css bundle file if it's empty, but keep the filelist cache FileUtils.rm_f(abs_path) else # call bundle_css_files or bundle_js_files to bundle all files listed. output it's contents to a file output = BundleFu.send("bundle_#{filetype}_files", new_filelist.filenames, options) File.open( abs_path, "w") {|f| f.puts output } if output end new_filelist.save_as(abs_filelist_path) end if File.exists?(abs_path) && options[:bundle_fu] tag = filetype==:css ? stylesheet_link_tag(output_filename) : javascript_include_tag(output_filename) if Rails::version < "2.2.0" concat( tag , block.binding) else #concat doesn't need block.binding in Rails >= 2.2.0 concat( tag ) end end } unless options[:bundle_fu] if Rails::version < "2.2.0" concat( content, block.binding ) else #concat doesn't need block.binding in Rails >= 2.2.0 concat( content ) end end end end end