Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Initial commit; good to go with Rails 3!

  • Loading branch information...
commit 22a93a9d30b3e58786a83bf4dbc83a42a17ec9fb 0 parents
Josh Delsman authored
20 MIT-LICENSE
... ... @@ -0,0 +1,20 @@
  1 +Copyright (c) 2010 Josh Delsman
  2 +
  3 +Permission is hereby granted, free of charge, to any person obtaining
  4 +a copy of this software and associated documentation files (the
  5 +"Software"), to deal in the Software without restriction, including
  6 +without limitation the rights to use, copy, modify, merge, publish,
  7 +distribute, sublicense, and/or sell copies of the Software, and to
  8 +permit persons to whom the Software is furnished to do so, subject to
  9 +the following conditions:
  10 +
  11 +The above copyright notice and this permission notice shall be
  12 +included in all copies or substantial portions of the Software.
  13 +
  14 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  15 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  16 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  17 +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  18 +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  19 +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  20 +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
82 README
... ... @@ -0,0 +1,82 @@
  1 +Assemblage
  2 +==========
  3 +
  4 +Based on the concept {sported by GitHub}[http://github.com/blog/551-optimizing-asset-bundling-and-serving-with-rails], this plugin serves the purpose of making it much easier to package your JavaScript and CSS files together into "bundles", so that they are easy for your clients to download once and cache in their browsers. This is a recommended practice to increase performance of your Rails application dramatically.
  5 +
  6 +Getting Started
  7 +===============
  8 +
  9 +There is a simple methodology that needs to be adopted by your application when organising your JavaScript and CSS files:
  10 +
  11 + public/javascripts
  12 + |-- admin
  13 + | |-- date.js
  14 + | `-- datePicker.js
  15 + |-- common
  16 + | |-- application.js
  17 + | |-- jquery.tablesorter.js
  18 + | `-- jquery.editable.js
  19 + |-- dev
  20 + |-- jquery-1.4.1.js
  21 + `-- jquery-ui-1.7.2.js
  22 +
  23 +The directory structure is similar in public/stylesheets, as well. This essentially namespaces your directories so they are easier to organise. You need to only place files that are used in that specific namespace in the directory. If you use the library in more than one namespace, it should be placed in a "common" folder.
  24 +
  25 +The "dev" folder is used locally, and is replaced with a cached version somewhere else on the Internet in production. More on that later...
  26 +
  27 +View Helper Methods
  28 +===================
  29 +
  30 +You will need to include a couple of helpers in your layout files:
  31 +
  32 + <%= javascript_dev ['jquery-1.4.1', "http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js"] %>
  33 + <%= javascript_bundle 'common', 'clients' %>
  34 + <%= stylesheet_bundle 'common', 'clients' %>
  35 +
  36 +In this instance, there are three helpers: `javascript_dev`, `javascript_bundle` and `stylesheet_bundle`.
  37 +
  38 +* <tt>javascript_dev</tt> - In development, the files included in the "dev" directory are included. Otherwise, it sets the cached version using the URL.
  39 +* <tt>javascript_bundle</tt> - In development, this method does a recursive search for all files in the namespaces listed and includes the raw, uncompressed files. Otherwise, it will look for "bundle_namespace.js" and include this file, if it exists.
  40 +* <tt>stylesheet_bundle</tt> - Exactly the same as above, but for CSS files.
  41 +
  42 +Compression
  43 +===========
  44 +
  45 +JavaScript compression is handled using the Closure Compiler by Google, which is included in the bin/ directory. CSS compression is handled by YUI Compressor, also included.
  46 +
  47 +Rake Tasks
  48 +==========
  49 +
  50 +Included Rake tasks are:
  51 +
  52 +* <tt>rake assemble:all</tt> - Compresses and bundles both CSS & JavaScript
  53 +* <tt>rake assemble:js</tt> - Only assembles namespaced JavaScript
  54 +* <tt>rake assemble:css</tt> - Only assembles namespaced CSS
  55 +
  56 +Deployment
  57 +==========
  58 +
  59 +You can handle these tasks on deployment by doing the following:
  60 +
  61 + namespace :deploy do
  62 + desc "Assemble JavaScript and CSS files"
  63 + task :assemble, :roles => :web, :except => { :no_release => true } do
  64 + run "cd #{current_path}; rake assemble:all"
  65 + end
  66 + end
  67 +
  68 + after "deploy:update_code", "deploy:assemble"
  69 +
  70 +Issues & Contributions
  71 +======================
  72 +
  73 +For all issues and bug/feature requests, please use the GitHub issue tracker:
  74 +
  75 +http://github.com/voxxit/assemblage
  76 +
  77 +Props
  78 +=====
  79 +
  80 +Thanks to {Kyle Neath}[http://github.com/kneath] for the inspiration to turn his idea into a useful plugin for all!
  81 +
  82 +Copyright (c) 2010 Josh Delsman, released under the MIT license
10 Rakefile
... ... @@ -0,0 +1,10 @@
  1 +require 'rake/testtask'
  2 +
  3 +desc 'Default: run unit tests.'
  4 +task :default => :test
  5 +
  6 +desc 'Test the assemblage plugin.'
  7 +Rake::TestTask.new(:test) do |t|
  8 + t.libs << 'test'
  9 + t.pattern = 'test/**/*_test.rb'
  10 +end
BIN  bin/closure-compiler.jar
Binary file not shown
BIN  bin/yui-compressor.jar
Binary file not shown
3  init.rb
... ... @@ -0,0 +1,3 @@
  1 +require 'assemblage'
  2 +
  3 +ActionView::Base.send(:include, Assemblage::ViewHelpers)
99 lib/assemblage.rb
... ... @@ -0,0 +1,99 @@
  1 +require 'find'
  2 +
  3 +module Assemblage
  4 + module ViewHelpers
  5 + def bundle_files?
  6 + Rails.env.production? || Rails.env.staging? || params[:bundle] || cookies[:bundle] == "yes"
  7 + end
  8 +
  9 + def javascript_bundle(*sources)
  10 + sources = sources.to_a
  11 + bundle_files? ? javascript_include_bundles(sources) : javascript_include_files(sources)
  12 + end
  13 +
  14 + # This method assumes you have manually bundled js using a rake command
  15 + # or similar. So, there better be bundle_* files!
  16 + def javascript_include_bundles(bundles)
  17 + output = ""
  18 +
  19 + bundles.each do |bundle|
  20 + output << javascript_src_tag("bundle_#{bundle}", {}) + "\n"
  21 + end
  22 +
  23 + output
  24 + end
  25 +
  26 + def javascript_include_files(bundles)
  27 + output = ""
  28 +
  29 + bundles.each do |bundle|
  30 + files = recursive_file_list("public/javascripts/#{bundle}", ".js")
  31 +
  32 + files.each do |file|
  33 + file = file.gsub('public/javascripts/', '')
  34 +
  35 + output << javascript_src_tag(file, {}) + "\n"
  36 + end
  37 + end
  38 +
  39 + output
  40 + end
  41 +
  42 + def javascript_dev(*sources)
  43 + output = ""
  44 + sources = sources.to_a
  45 +
  46 + sources.each do |pair|
  47 + output << javascript_src_tag(Rails.env.development? ? "dev/#{pair[0]}" : pair[1], {})
  48 + end
  49 +
  50 + output
  51 + end
  52 +
  53 + def stylesheet_bundle(*sources)
  54 + sources = sources.to_a
  55 + bundle_files? ? stylesheet_include_bundles(sources) : stylesheet_include_files(sources)
  56 + end
  57 +
  58 + # This method assumes you have manually bundled css using a rake command
  59 + # or similar. So, there better be bundle_* files!
  60 + def stylesheet_include_bundles(bundles)
  61 + stylesheet_link_tag(bundles.collect{ |b| "bundle_#{b}"})
  62 + end
  63 +
  64 + def stylesheet_include_files(bundles)
  65 + output = ""
  66 +
  67 + bundles.each do |bundle|
  68 + files = recursive_file_list("public/stylesheets/#{bundle}", ".css")
  69 +
  70 + files.each do |file|
  71 + file = file.gsub('public/stylesheets/', '')
  72 +
  73 + output << stylesheet_link_tag(file)
  74 + end
  75 + end
  76 +
  77 + output
  78 + end
  79 +
  80 + def recursive_file_list(basedir, extname)
  81 + files = []
  82 + basedir = Rails.root.join(basedir)
  83 +
  84 + Find.find(basedir) do |path|
  85 + if FileTest.directory?(path)
  86 + if File.basename(path)[0] == ?.
  87 + Find.prune
  88 + else
  89 + next
  90 + end
  91 + end
  92 +
  93 + files << path.gsub(Rails.root, '') if File.extname(path) == extname
  94 + end
  95 +
  96 + files.sort
  97 + end
  98 + end
  99 +end
100 lib/tasks/assemble.rake
... ... @@ -0,0 +1,100 @@
  1 +require 'find'
  2 +
  3 +namespace :assemble do
  4 + task :all => [ :js, :css ]
  5 +
  6 + task :js do
  7 + paths = get_top_level_directories("public/javascripts")
  8 + targets = []
  9 +
  10 + paths.each do |bundle_directory|
  11 + bundle_name = bundle_directory.basename
  12 + files = recursive_file_list(bundle_directory, ".js")
  13 +
  14 + next if files.empty? || bundle_name == "dev"
  15 +
  16 + target = execute_closure(files, bundle_name)
  17 +
  18 + targets << target
  19 + end
  20 +
  21 + targets.each do |target|
  22 + puts "=> Assembled JavaScript at: #{target}"
  23 + end
  24 + end
  25 +
  26 + task :css do
  27 + paths = get_top_level_directories("public/stylesheets")
  28 + targets = []
  29 +
  30 + paths.each do |bundle_directory|
  31 + bundle_name = bundle_directory.basename
  32 + files = recursive_file_list(bundle_directory, ".css")
  33 +
  34 + next if files.empty? || bundle_name == 'dev'
  35 +
  36 + bundle = ""
  37 +
  38 + files.each do |file_path|
  39 + bundle << File.read(file_path) << "\n"
  40 + end
  41 +
  42 + target = execute_yui_compressor(bundle, bundle_name)
  43 +
  44 + targets << target
  45 + end
  46 +
  47 + targets.each do |target|
  48 + puts "=> Assembled CSS at: #{target}"
  49 + end
  50 + end
  51 +
  52 + private
  53 +
  54 + def execute_closure(files, bundle_name)
  55 + jar = File.join(File.dirname(__FILE__), "..", "..", "bin", "closure-compiler.jar")
  56 + target = Rails.root.join("public/javascripts/bundle_#{bundle_name}.js")
  57 +
  58 + `java -jar #{jar} #{files.join(" --js ")} --js_output_file #{target}`
  59 +
  60 + return target
  61 + end
  62 +
  63 + def execute_yui_compressor(bundle, bundle_name)
  64 + jar = File.join(File.dirname(__FILE__), "..", "..", "bin", "yui-compressor.jar")
  65 + target = Rails.root.join("public/stylesheets/bundle_#{bundle_name}.css")
  66 + temp_file = "/tmp/bundle_raw.css"
  67 +
  68 + File.open(temp_file, 'w') { |f| f.write(bundle) }
  69 +
  70 + `java -jar #{jar} --line-break 0 #{temp_file} -o #{target}`
  71 +
  72 + return target
  73 + end
  74 +
  75 + def recursive_file_list(basedir, ext)
  76 + files = []
  77 +
  78 + Find.find(basedir) do |path|
  79 + if FileTest.directory?(path)
  80 + if File.basename(path)[0] == ?. # Skip dot directories
  81 + Find.prune
  82 + else
  83 + next
  84 + end
  85 + end
  86 +
  87 + files << path if File.extname(path) == ext
  88 + end
  89 +
  90 + files.sort
  91 + end
  92 +
  93 + def get_top_level_directories(base_path)
  94 + Dir.entries(Rails.root.join(base_path)).collect do |path|
  95 + path = Rails.root.join("#{base_path}/#{path}")
  96 +
  97 + File.basename(path)[0] == ?. || !File.directory?(path) ? nil : Pathname.new(path) # not dot directories or files
  98 + end - [nil]
  99 + end
  100 +end
8 test/assemblage_test.rb
... ... @@ -0,0 +1,8 @@
  1 +require 'test_helper'
  2 +
  3 +class AssemblageTest < ActiveSupport::TestCase
  4 + # Replace this with your real tests.
  5 + test "the truth" do
  6 + assert true
  7 + end
  8 +end
3  test/test_helper.rb
... ... @@ -0,0 +1,3 @@
  1 +require 'rubygems'
  2 +require 'test/unit'
  3 +require 'active_support'

0 comments on commit 22a93a9

Please sign in to comment.
Something went wrong with that request. Please try again.