Skip to content

Instantly share code, notes, and snippets.

@kneath
Forked from defunkt/bundle.rake
Created November 18, 2009 21:42
Show Gist options
  • Star 24 You must be signed in to star a gist
  • Fork 26 You must be signed in to fork a gist
  • Save kneath/238291 to your computer and use it in GitHub Desktop.
Save kneath/238291 to your computer and use it in GitHub Desktop.
Intelligent asset bundling for Rails (GitHub's asset bundling)

GitHub Javascript Strategy

Unless otherwise necessary (such as mobile development), the GitHub javascript codebase is based off jQuery. You can safely assume it will be included on every page.

File naming

  • All jquery plugins should be prefixed with jquery, such as jquery.facebox
  • All github-specific jquery plugins should be prefixed with jquery.github. Like jquery.github.repo_list.js
  • All page-specific files (that only run on ONE page) should be prefixed with page. page.billing.js

File structure

All javascript for the site should be put into a directory. Feel free to create new directories when it makes sense (gist, iphone, etc). There are a few directories that have special meanings:

  • common - Contains all files that are used and shared between the main site and sub-sites (like gist)
  • rogue - Files that are directly called by individual pages. Use for large libraries that are only used on one (or a very few) pages.
  • dev - Files that are hosted elsewhere (such as google) that you will need if you do not have an internet connection.

Inside of each directory, you should note the difference between plugins and files where possible and split them into different directories (see github).

Deployment

On deploy, each top level directory is bundled into a compressed file. Any files that are inside the directory (and it's subfolders) will be bundled into one file and available at bundle_[directory].js

Template inclusion

If you would like to include a bundle in your Rails templates, you reference bundles or individual files using the javascript_bundle and javascript_dev helpers.

Example

javascript_dev ['jquery', 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js']
javascript_bundle 'common', 'github', 'rogue/s3_upload'

In production, this will include:

  • http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js in production.
  • bundle_common.js
  • bundle_github.js
  • rogue/s3_upload.js

In development, this will include:

  • dev/jquery.js
  • All javascripts inside of the common directory
  • All javascripts inside the github directory
  • rogue/s3_upload.js
RAILS_ROOT ||= ENV["RAILS_ROOT"]
namespace :bundle do
task :all => [ :js, :css ]
task :js do
compression_method = "closure"
require 'lib/js_minimizer' if compression_method != "closure"
closure_path = RAILS_ROOT + '/lib/closure_compressor.jar'
paths = get_top_level_directories('/public/javascripts')
targets = []
paths.each do |bundle_directory|
bundle_name = bundle_directory.gsub(RAILS_ROOT + '/public/javascripts/', "")
files = recursive_file_list(bundle_directory, ".js")
next if files.empty? || bundle_name == 'dev'
target = RAILS_ROOT + "/public/javascripts/bundle_#{bundle_name}.js"
if compression_method == "closure"
`java -jar #{closure_path} --js #{files.join(" --js ")} --js_output_file #{target} 2> /dev/null`
else
File.open(target, 'w+') do |f|
f.puts JSMinimizer.minimize_files(*files)
end
end
targets << target
end
targets.each do |target|
puts "=> bundled js at #{target}"
end
end
task :css do
yuipath = RAILS_ROOT + '/lib/yuicompressor-2.4.1.jar'
paths = get_top_level_directories('/public/stylesheets')
targets = []
paths.each do |bundle_directory|
bundle_name = bundle_directory.gsub(RAILS_ROOT + '/public/stylesheets/', "")
files = recursive_file_list(bundle_directory, ".css")
next if files.empty? || bundle_name == 'dev'
bundle = ''
files.each do |file_path|
bundle << File.read(file_path) << "\n"
end
target = RAILS_ROOT + "/public/stylesheets/bundle_#{bundle_name}.css"
rawpath = "/tmp/bundle_raw.css"
File.open(rawpath, 'w') { |f| f.write(bundle) }
`java -jar #{yuipath} --line-break 0 #{rawpath} -o #{target}`
targets << target
end
targets.each do |target|
puts "=> bundled css at #{target}"
end
end
require 'find'
def recursive_file_list(basedir, ext)
files = []
Find.find(basedir) do |path|
if FileTest.directory?(path)
if File.basename(path)[0] == ?. # Skip dot directories
Find.prune
else
next
end
end
files << path if File.extname(path) == ext
end
files.sort
end
def get_top_level_directories(base_path)
Dir.entries(RAILS_ROOT + base_path).collect do |path|
path = RAILS_ROOT + "#{base_path}/#{path}"
File.basename(path)[0] == ?. || !File.directory?(path) ? nil : path # not dot directories or files
end - [nil]
end
end
require 'find'
module BundleHelper
def bundle_files?
Rails.production? || Rails.staging? || params[:bundle] || cookies[:bundle] == "yes"
end
def javascript_bundle(*sources)
sources = sources.to_a
bundle_files? ? javascript_include_bundles(sources) : javascript_include_files(sources)
end
# This method assumes you have manually bundled js using a rake command
# or similar. So there better be bundle_* files.
def javascript_include_bundles(bundles)
output = ""
bundles.each do |bundle|
output << javascript_src_tag("bundle_#{bundle}", {}) + "\n"
end
output
end
def javascript_include_files(bundles)
output = ""
bundles.each do |bundle|
files = recursive_file_list("public/javascripts/#{bundle}", ".js")
files.each do |file|
file = file.gsub('public/javascripts/', '')
output << javascript_src_tag(file, {}) + "\n"
end
end
output
end
def javascript_dev(*sources)
output = ""
sources = sources.to_a
sources.each do |pair|
output << javascript_src_tag(Rails.development? ? "dev/#{pair[0]}" : pair[1], {})
end
output
end
def stylesheet_bundle(*sources)
sources = sources.to_a
bundle_files? ? stylesheet_include_bundles(sources) : stylesheet_include_files(sources)
end
# This method assumes you have manually bundled css using a rake command
# or similar. So there better be bundle_* files.
def stylesheet_include_bundles(bundles)
stylesheet_link_tag(bundles.collect{ |b| "bundle_#{b}"})
end
def stylesheet_include_files(bundles)
output = ""
bundles.each do |bundle|
files = recursive_file_list("public/stylesheets/#{bundle}", ".css")
files.each do |file|
file = file.gsub('public/stylesheets/', '')
output << stylesheet_link_tag(file)
end
end
output
end
def recursive_file_list(basedir, extname)
files = []
basedir = RAILS_ROOT + "/" + basedir
Find.find(basedir) do |path|
if FileTest.directory?(path)
if File.basename(path)[0] == ?.
Find.prune
else
next
end
end
files << path.gsub(RAILS_ROOT + '/', '') if File.extname(path) == extname
end
files.sort
end
end
namespace :deploy do
desc "Shrink and bundle js and css"
task :bundle, :roles => :web, :except => { :no_release => true } do
run "cd #{current_path}; RAILS_ROOT=#{current_path} rake bundle:all"
end
end
after "deploy:update_code", "deploy:bundle"
config.action_controller.asset_host = Proc.new do |source, request|
non_ssl_host = "http://assets#{source.hash % 4}.github.com"
ssl_host = "https://assets#{source.hash % 4}.github.com"
if request.ssl?
if source =~ /\.js$/
ssl_host
elsif request.headers["USER_AGENT"] =~ /(Safari)/
non_ssl_host
else
ssl_host
end
else
non_ssl_host
end
end
repo = Grit::Repo.new(RAILS_ROOT)
js = repo.log('master', 'public/javascripts', :max_count => 1).first
css = repo.log('master', 'public/stylesheets', :max_count => 1).first
ENV['RAILS_ASSET_ID'] = js.committed_date > css.committed_date ? js.id : css.id
@rosenfeld
Copy link

What about pre-processors, like CoffeeScript or SASS?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment