jammit 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2009 Jeremy Ashkenas, DocumentCloud
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,21 @@
1
+ ==
2
+ _ _ __ __ __ __ ___ _____
3
+ _ | |/_\ | \/ | \/ |_ _|_ _|
4
+ | || / _ \| |\/| | |\/| || | | |
5
+ \__/_/ \_\_| |_|_| |_|___| |_|
6
+
7
+
8
+ Jammit is an industrial strength asset packaging library for Rails,
9
+ providing both the CSS and JavaScript concatenation and compression
10
+ that you'd expect, as well as ahead-of-time gzipping, built-in JavaScript
11
+ template support, and optional Data-URI / MHTML image embedding.
12
+
13
+ For documentation, usage, and examples, see:
14
+ http://documentcloud.github.com/jammit/
15
+
16
+ To suggest a feature or report a bug:
17
+ http://github.com/documentcloud/jammit/issues/
18
+
19
+ For internal source docs, see:
20
+ http://documentcloud.github.com/jammit/doc/
21
+
@@ -0,0 +1,38 @@
1
+ require 'rake/testtask'
2
+
3
+ desc 'Run all tests'
4
+ task :test do
5
+ $LOAD_PATH.unshift(File.expand_path('test'))
6
+ require 'redgreen' if Gem.available?('redgreen')
7
+ require 'test/unit'
8
+ Dir['test/**/test_*.rb'].each {|test| require test }
9
+ end
10
+
11
+ desc 'Generate YARD Documentation'
12
+ task :doc do
13
+ sh "mv README TEMPME"
14
+ sh "yardoc"
15
+ sh "mv TEMPME README"
16
+ end
17
+
18
+ desc "Deploy the Github Page"
19
+ task :deploy do
20
+ sh "git co gh-pages && git merge master && git push github gh-pages && git co master"
21
+ end
22
+
23
+ namespace :gem do
24
+
25
+ desc 'Build and install the jammit gem'
26
+ task :install do
27
+ sh "gem build jammit.gemspec"
28
+ sh "sudo gem install #{Dir['*.gem'].join(' ')} --local --no-ri --no-rdoc"
29
+ end
30
+
31
+ desc 'Uninstall the jammit gem'
32
+ task :uninstall do
33
+ sh "sudo gem uninstall -x jammit"
34
+ end
35
+
36
+ end
37
+
38
+ task :default => :test
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "#{File.dirname(__FILE__)}/../lib/jammit/command_line.rb"
4
+
5
+ Jammit::CommandLine.new
@@ -0,0 +1,47 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'jammit'
3
+ s.version = '0.1.0' # Keep version in sync with jammit.rb
4
+ s.date = '2009-11-06'
5
+
6
+ s.homepage = "http://documentcloud.github.com/jammit/"
7
+ s.summary = "Industrial Strength Asset Packaging for Rails"
8
+ s.description = <<-EOS
9
+ Jammit is an industrial strength asset packaging library for Rails,
10
+ providing both the CSS and JavaScript concatenation and compression
11
+ that you'd expect, as well as ahead-of-time gzipping, built-in JavaScript
12
+ template support, and optional Data-URI / MHTML image embedding.
13
+ EOS
14
+
15
+ s.authors = ['Jeremy Ashkenas']
16
+ s.email = 'jeremy@documentcloud.org'
17
+ s.rubyforge_project = 'jammit'
18
+
19
+ s.require_paths = ['lib']
20
+ s.executables = ['jammit']
21
+
22
+ s.has_rdoc = true
23
+ s.extra_rdoc_files = ['README']
24
+ s.rdoc_options << '--title' << 'Jammit' <<
25
+ '--exclude' << 'test' <<
26
+ '--main' << 'README' <<
27
+ '--all'
28
+
29
+ s.add_dependency 'rails', ['>= 2.0.0']
30
+ s.add_dependency 'yui-compressor', ['>= 0.9.1']
31
+
32
+ s.files = %w(
33
+ bin/jammit
34
+ jammit.gemspec
35
+ lib/jammit.rb
36
+ lib/jammit/command_line.rb
37
+ lib/jammit/compressor.rb
38
+ lib/jammit/controller.rb
39
+ lib/jammit/helper.rb
40
+ lib/jammit/jst.js
41
+ lib/jammit/packager.rb
42
+ lib/jammit/routes.rb
43
+ LICENSE
44
+ Rakefile
45
+ README
46
+ )
47
+ end
@@ -0,0 +1,117 @@
1
+ $LOAD_PATH.push File.expand_path(File.dirname(__FILE__))
2
+
3
+ # @Jammit@ is the central namespace for all Jammit classes, and provides access
4
+ # to all of the configuration options.
5
+ module Jammit
6
+
7
+ VERSION = "0.1.0"
8
+
9
+ ROOT = File.expand_path(File.dirname(__FILE__) + '/..')
10
+
11
+ DEFAULT_CONFIG_PATH = "config/assets.yml"
12
+
13
+ DEFAULT_PACKAGE_PATH = "assets"
14
+
15
+ DEFAULT_JST_SCRIPT = "#{ROOT}/lib/jammit/jst.js"
16
+
17
+ DEFAULT_JST_COMPILER = "template"
18
+
19
+ # Jammit raises a @PackageNotFound@ exception when a non-existent package is
20
+ # requested by a browser -- rendering a 404.
21
+ class PackageNotFound < NameError; end
22
+
23
+ class << self
24
+ attr_reader :configuration, :template_function, :embed_images, :package_path,
25
+ :package_assets, :mhtml_enabled, :include_jst_script
26
+ end
27
+
28
+ # The minimal required configuration.
29
+ @configuration = {}
30
+ @package_path = DEFAULT_PACKAGE_PATH
31
+
32
+ # Load the complete asset configuration from the specified @config_path@.
33
+ def self.load_configuration(config_path)
34
+ return unless config_path && File.exists?(config_path)
35
+ @config_path = config_path
36
+ @configuration = conf = YAML.load_file(@config_path).symbolize_keys
37
+ @package_path = conf[:package_path] || DEFAULT_PACKAGE_PATH
38
+ @embed_images = conf[:embed_images]
39
+ @mhtml_enabled = @embed_images && @embed_images != "datauri"
40
+ set_package_assets(conf[:package_assets])
41
+ set_template_function(conf[:template_function])
42
+ self
43
+ end
44
+
45
+ # Force a reload by resetting the Packager and reloading the configuration.
46
+ # In development, this will be called before every request to the
47
+ # @Jammit::Controller@.
48
+ def self.reload!
49
+ Thread.current[:jammit_packager] = nil
50
+ load_configuration(@config_path)
51
+ end
52
+
53
+ # Keep a global (thread-local) reference to a @Jammit::Packager@, to avoid
54
+ # recomputing asset lists unnecessarily.
55
+ def self.packager
56
+ Thread.current[:jammit_packager] ||= Packager.new
57
+ end
58
+
59
+ # Generate the base filename for a version of a given package.
60
+ def self.filename(package, extension, suffix=nil)
61
+ suffix_part = suffix ? "-#{suffix}" : ''
62
+ "#{package}#{suffix_part}.#{extension}"
63
+ end
64
+
65
+ # Generates the server-absolute URL to an asset package.
66
+ def self.asset_url(package, extension, suffix=nil, mtime=nil)
67
+ timestamp = mtime ? "?#{mtime.to_i}" : ''
68
+ "/#{package_path}/#{filename(package, extension, suffix)}#{timestamp}"
69
+ end
70
+
71
+
72
+ private
73
+
74
+ def self.set_package_assets(value)
75
+ package_env = !defined?(RAILS_ENV) || RAILS_ENV != 'development'
76
+ @package_assets = case value
77
+ when 'always' then true
78
+ when false then false
79
+ when true then package_env
80
+ when nil then package_env
81
+ end
82
+ end
83
+
84
+ def self.set_template_function(value)
85
+ @template_function = case value
86
+ when false then ''
87
+ when true then DEFAULT_JST_COMPILER
88
+ when nil then DEFAULT_JST_COMPILER
89
+ else value
90
+ end
91
+ @include_jst_script = @template_function == DEFAULT_JST_COMPILER
92
+ end
93
+
94
+ end
95
+
96
+ # Standard Library Dependencies:
97
+ require 'zlib'
98
+ require 'base64'
99
+ require 'fileutils'
100
+
101
+ # Gem Dependencies:
102
+ require 'rubygems'
103
+ require 'yui/compressor'
104
+ require 'activesupport'
105
+
106
+ # Load initial configuration before the rest of Jammit.
107
+ Jammit.load_configuration(Jammit::DEFAULT_CONFIG_PATH)
108
+
109
+ # Jammit Core:
110
+ require 'jammit/compressor'
111
+ require 'jammit/packager'
112
+
113
+ # Jammit Rails Integration:
114
+ if defined?(RAILS_ENV)
115
+ require 'jammit/controller' # Rails will auto-load 'jammit/helper' for us.
116
+ require 'jammit/routes'
117
+ end
@@ -0,0 +1,74 @@
1
+ require 'optparse'
2
+ require File.expand_path(File.dirname(__FILE__) + '/../jammit')
3
+
4
+ module Jammit
5
+
6
+ # The @CommandLine@ is able to compress, pre-package, and pre-gzip all the
7
+ # assets specified in the configuration file, in order to avoid an initial
8
+ # round of slow requests after a fresh deployment.
9
+ class CommandLine
10
+
11
+ BANNER = <<-EOS
12
+
13
+ Usage: jammit OPTIONS
14
+
15
+ Run jammit inside a Rails application to compresses all JS, CSS,
16
+ and JST according to config/assets.yml, saving the packaged
17
+ files and corresponding gzipped versions.
18
+
19
+ If you're using "embed_images", and you wish to precompile the
20
+ MHTML stylesheet variants, you must specify the "base-url".
21
+
22
+ Options:
23
+ EOS
24
+
25
+ # The @Jammit::CommandLine@ runs from the contents of @ARGV@.
26
+ def initialize
27
+ parse_options
28
+ ensure_configuration_file
29
+ Jammit.load_configuration(@options[:config_path])
30
+ Jammit.packager.precache_all(@options[:output_folder], @options[:base_url])
31
+ end
32
+
33
+
34
+ private
35
+
36
+ # Make sure that we have a readable configuration file. The @jammit@
37
+ # command can't run without one.
38
+ def ensure_configuration_file
39
+ config = @options[:config_path]
40
+ return true if File.exists?(config) && File.readable?(config)
41
+ puts "Could not find the asset configuration file \"#{config}\""
42
+ exit(1)
43
+ end
44
+
45
+ # Uses @OptionParser@ to grab the options: *--output*, *--config*, and
46
+ # *--base-url*...
47
+ def parse_options
48
+ @options = {
49
+ :config_path => Jammit::DEFAULT_CONFIG_PATH,
50
+ :output_folder => nil,
51
+ :base_url => nil
52
+ }
53
+ @option_parser = OptionParser.new do |opts|
54
+ opts.on('-o', '--output PATH', 'output folder for packages (default: "public/assets")') do |output_folder|
55
+ @options[:output_folder] = output_folder
56
+ end
57
+ opts.on('-c', '--config PATH', 'path to assets.yml (default: "config/assets.yml")') do |config_path|
58
+ @options[:config_path] = config_path
59
+ end
60
+ opts.on('-u', '--base-url URL', 'base URL for MHTML (ex: "http://example.com")') do |base_url|
61
+ @options[:base_url] = base_url
62
+ end
63
+ opts.on_tail('-v', '--version', 'display Jammit version') do
64
+ puts "Jammit version #{Jammit::VERSION}"
65
+ exit
66
+ end
67
+ end
68
+ @option_parser.banner = BANNER
69
+ @option_parser.parse!(ARGV)
70
+ end
71
+
72
+ end
73
+
74
+ end
@@ -0,0 +1,125 @@
1
+ module Jammit
2
+
3
+ # Uses the YUI Compressor to compress JavaScript and CSS. (Which means that
4
+ # Java must be installed.) Also knows how to create a concatenated JST file.
5
+ # If "embed_images" is turned on, creates "mhtml" and "datauri" versions of
6
+ # all stylesheets, with all enabled images inlined into the css.
7
+ class Compressor
8
+
9
+ # Mapping from extension to mime-type of all embeddable images.
10
+ IMAGE_MIME_TYPES = {
11
+ '.png' => 'image/png',
12
+ '.jpg' => 'image/jpeg',
13
+ '.jpeg' => 'image/jpeg',
14
+ '.gif' => 'image/gif',
15
+ '.tif' => 'image/tiff',
16
+ '.tiff' => 'image/tiff'
17
+ }
18
+
19
+ # Detect all image URLs that are inside of an "embed" folder.
20
+ IMAGE_DETECTOR = /url\(['"]?(\/[^\s)]*embed\/[^\s)]+\.(png|jpg|jpeg|gif|tif|tiff))['"]?\)/
21
+
22
+ # MHTML file constants.
23
+ MHTML_START = "/*\r\nContent-Type: multipart/related; boundary=\"JAMMIT_MHTML_SEPARATOR\"\r\n\r\n"
24
+ MHTML_SEPARATOR = "--JAMMIT_MHTML_SEPARATOR\r\n"
25
+ MHTML_END = "*/\r\n"
26
+
27
+ # JST file constants.
28
+ JST_START = "(function(){window.JST = window.JST || {};"
29
+ JST_END = "})();"
30
+
31
+ # Creating a compressor initializes the internal YUI Compressor from
32
+ # the "yui-compressor" gem.
33
+ def initialize
34
+ @yui_js = YUI::JavaScriptCompressor.new(:munge => true)
35
+ @yui_css = YUI::CssCompressor.new
36
+ end
37
+
38
+ # Concatenate together a list of JavaScript paths, and pass them through the
39
+ # YUI Compressor (with munging enabled).
40
+ def compress_js(paths)
41
+ @yui_js.compress(concatenate(paths))
42
+ end
43
+
44
+ # Concatenate and compress a list of CSS stylesheets. When compressing a
45
+ # :datauri or :mhtml variant, post-processes the result to embed
46
+ # referenced images.
47
+ def compress_css(paths, variant=nil, asset_url=nil)
48
+ compressed_css = @yui_css.compress(concatenate(paths))
49
+ case variant
50
+ when nil then compressed_css
51
+ when :datauri then with_data_uris(compressed_css)
52
+ when :mhtml then with_mhtml(compressed_css, asset_url)
53
+ end
54
+ end
55
+
56
+ # Compiles a single JST file by writing out a javascript that adds
57
+ # template properties to a top-level "window.JST" object. Adds a
58
+ # JST-compilation function to the top of the package, unless you've
59
+ # specified your own preferred function, or turned it off.
60
+ # JST templates are named with the basename of their file.
61
+ def compile_jst(paths)
62
+ compiled = paths.map do |path|
63
+ template_name = File.basename(path, File.extname(path))
64
+ contents = File.read(path).gsub(/\n/, '').gsub("'", '\\\\\'')
65
+ "window.JST.#{template_name} = #{Jammit.template_function}('#{contents}');"
66
+ end
67
+ compiler = Jammit.include_jst_script ? File.read(DEFAULT_JST_SCRIPT) : '';
68
+ [JST_START, compiler, compiled, JST_END].flatten.join("\n")
69
+ end
70
+
71
+
72
+ private
73
+
74
+ # Re-write all enabled image URLs in a stylesheet with their corresponding
75
+ # Data-URI Base-64 encoded image contents.
76
+ def with_data_uris(css)
77
+ css.gsub(IMAGE_DETECTOR) do |url|
78
+ image_path = "public#{$1}"
79
+ valid_image(image_path) ? "url(\"data:#{mime_type(image_path)};base64,#{encoded_contents(image_path)}\")" : url
80
+ end
81
+ end
82
+
83
+ # Re-write all enabled image URLs in a stylesheet with the MHTML equivalent.
84
+ # The newlines ("\r\n") in the following method are critical. Without them
85
+ # your MHTML will look identical, but won't work.
86
+ def with_mhtml(css, asset_url)
87
+ paths = {}
88
+ css = css.gsub(IMAGE_DETECTOR) do |url|
89
+ image_path = "public#{$1}"
90
+ valid = valid_image(image_path)
91
+ paths[$1] ||= image_path if valid
92
+ valid ? "url(mhtml:#{asset_url}!#{$1})" : url
93
+ end
94
+ mhtml = paths.map do |identifier, path|
95
+ mime, contents = mime_type(path), encoded_contents(path)
96
+ [MHTML_SEPARATOR, "Content-Location: #{identifier}\r\n", "Content-Type: #{mime}\r\n", "Content-Transfer-Encoding: base64\r\n\r\n", contents, "\r\n"]
97
+ end
98
+ [MHTML_START, mhtml, MHTML_END, css].flatten.join('')
99
+ end
100
+
101
+ # An image is valid if it exists, and is less than 32K.
102
+ # IE does not support Data-URIs larger than 32K, and you probably shouldn't
103
+ # be embedding images that large in any case.
104
+ def valid_image(image_path)
105
+ File.exists?(image_path) && File.size(image_path) < 32.kilobytes
106
+ end
107
+
108
+ # Return the Base64-encoded contents of an image on a single line.
109
+ def encoded_contents(image_path)
110
+ Base64.encode64(File.read(image_path)).gsub(/\n/, '')
111
+ end
112
+
113
+ # Grab the mime-type of an image, by filename.
114
+ def mime_type(image_path)
115
+ IMAGE_MIME_TYPES[File.extname(image_path)]
116
+ end
117
+
118
+ # Concatenate together a list of asset files.
119
+ def concatenate(paths)
120
+ [paths].flatten.map {|p| File.read(p) }.join("\n")
121
+ end
122
+
123
+ end
124
+
125
+ end
@@ -0,0 +1,91 @@
1
+ module Jammit
2
+
3
+ # The JammitController is added to your Rails application when the Gem is
4
+ # loaded. It takes responsibility for /assets, and dynamically packages any
5
+ # missing or uncached asset packages.
6
+ class Controller < ActionController::Base
7
+
8
+ VALID_FORMATS = [:css, :js, :jst]
9
+
10
+ SUFFIX_STRIPPER = /-(datauri|mhtml)\Z/
11
+
12
+ NOT_FOUND_PATH = "#{RAILS_ROOT}/public/404.html"
13
+
14
+ after_filter :cache_package if perform_caching
15
+
16
+ # The "package" action receives all requests for asset packages that haven't
17
+ # yet been cached. The package will be built, cached, and gzipped.
18
+ def package
19
+ parse_request
20
+ case @extension
21
+ when :js then render :js => Jammit.packager.pack_javascripts(@package)
22
+ when :css then render :text => generate_stylesheets, :content_type => 'text/css'
23
+ when :jst then render :js => Jammit.packager.pack_templates(@package)
24
+ end
25
+ rescue Jammit::PackageNotFound
26
+ package_not_found
27
+ end
28
+
29
+
30
+ private
31
+
32
+ # Tells the Jammit::Packager to cache and gzip an asset package. We can't
33
+ # just use the built-in "cache_page" because we need to ensure that
34
+ # the timestamp that ends up in the MHTML is also on the cached file.
35
+ def cache_package
36
+ dir = File.join(page_cache_directory, Jammit.package_path)
37
+ Jammit.packager.cache(@package, @extension, @contents || response.body, dir, @variant, @mtime)
38
+ end
39
+
40
+ # Generate the complete, timestamped, MHTML url -- if we're rendering a
41
+ # dynamic MHTML package, we'll need to put one URL in the response, and a
42
+ # different one into the cached package.
43
+ def prefix_url(path)
44
+ host = request.port == 80 ? request.host : request.host_with_port
45
+ "#{request.protocol}#{host}#{path}"
46
+ end
47
+
48
+ # If we're generating MHTML/CSS, return a stylesheet with the absolute
49
+ # request URL to the client, and cache a version with the timestamped cache
50
+ # URL swapped in.
51
+ def generate_stylesheets
52
+ return Jammit.packager.pack_stylesheets(@package, @variant) unless @variant == :mhtml
53
+ @mtime = Time.now
54
+ request_url = prefix_url(request.request_uri)
55
+ cached_url = prefix_url(Jammit.asset_url(@package, @extension, @variant, @mtime))
56
+ css = Jammit.packager.pack_stylesheets(@package, @variant, request_url)
57
+ @contents = css.gsub(request_url, cached_url) if perform_caching
58
+ css
59
+ end
60
+
61
+ # Extracts the package name, extension (:css, :js, :jst), and variant
62
+ # (:datauri, :mhtml) from the incoming URL.
63
+ def parse_request
64
+ pack = params[:package]
65
+ @extension = params[:extension].to_sym
66
+ raise PackageNotFound unless VALID_FORMATS.include?(@extension)
67
+ if Jammit.embed_images
68
+ suffix_match = pack.match(SUFFIX_STRIPPER)
69
+ @variant = Jammit.embed_images && suffix_match && suffix_match[1].to_sym
70
+ pack.sub!(SUFFIX_STRIPPER, '')
71
+ end
72
+ @package = pack.to_sym
73
+ end
74
+
75
+ # Render the 404 page, if one exists, for any packages that don't.
76
+ def package_not_found
77
+ return render(:file => NOT_FOUND_PATH, :status => 404) if File.exists?(NOT_FOUND_PATH)
78
+ render :text => "<h1>404: \"#{@package}\" asset package not found.</h1>", :status => 404
79
+ end
80
+
81
+ end
82
+
83
+ end
84
+
85
+ ::JammitController = Jammit::Controller
86
+
87
+ if RAILS_ENV == 'development'
88
+ class ApplicationController < ActionController::Base
89
+ before_filter { Jammit.reload! }
90
+ end
91
+ end
@@ -0,0 +1,66 @@
1
+ module Jammit
2
+
3
+ # The Jammit::Helper module, which is made available to every view, provides
4
+ # helpers for writing out HTML tags for asset packages. In development you
5
+ # get the ordered list of source files -- in any other environment, a link
6
+ # to the cached packages.
7
+ module Helper
8
+
9
+ DATA_URI_START = "<!--[if (!IE)|(gte IE 8)]><!-->"
10
+ DATA_URI_END = "<!--<![endif]-->"
11
+ MHTML_START = "<!--[if lte IE 7]>"
12
+ MHTML_END = "<![endif]-->"
13
+
14
+ # If embed_images is turned on, writes out links to the Data-URI and MHTML
15
+ # versions of the stylesheet package, otherwise the package is regular
16
+ # compressed CSS, and in development the stylesheet URLs are passed verbatim.
17
+ def include_stylesheets(*packages)
18
+ return individual_stylesheets(packages) unless Jammit.package_assets
19
+ return embedded_image_stylesheets(packages) if Jammit.embed_images
20
+ return packaged_stylesheets(packages)
21
+ end
22
+
23
+ # Writes out the URL to the bundled and compressed javascript package,
24
+ # except in development, where it references the individual scripts.
25
+ def include_javascripts(*packages)
26
+ tags = packages.map do |pack|
27
+ Jammit.package_assets ? Jammit.asset_url(pack, :js) : Jammit.packager.individual_urls(pack.to_sym, :js)
28
+ end
29
+ javascript_include_tag(tags.flatten)
30
+ end
31
+
32
+ # Writes out the URL to the concatenated and compiled JST file -- we always
33
+ # have to pre-process it, even in development.
34
+ def include_templates(*packages)
35
+ javascript_include_tag(packages.map {|pack| Jammit.asset_url(pack, :jst) })
36
+ end
37
+
38
+
39
+ private
40
+
41
+ # HTML tags, in order, for all of the individual stylesheets.
42
+ def individual_stylesheets(packages)
43
+ stylesheet_link_tag(packages.map {|p| Jammit.packager.individual_urls(p.to_sym, :css) }.flatten)
44
+ end
45
+
46
+ # HTML tags for the stylesheet packages.
47
+ def packaged_stylesheets(packages)
48
+ stylesheet_link_tag(packages.map {|p| Jammit.asset_url(p, :css) })
49
+ end
50
+
51
+ # HTML tags for the 'datauri', and 'mhtml' versions of the packaged
52
+ # stylesheets, using conditional comments to load the correct variant.
53
+ def embedded_image_stylesheets(packages)
54
+ css_tags = stylesheet_link_tag(packages.map {|p| Jammit.asset_url(p, :css, :datauri) })
55
+ ie_tags = Jammit.mhtml_enabled ?
56
+ stylesheet_link_tag(packages.map {|p| Jammit.asset_url(p, :css, :mhtml) }) :
57
+ packaged_stylesheets(packages)
58
+ [DATA_URI_START, css_tags, DATA_URI_END, MHTML_START, ie_tags, MHTML_END].join("\n")
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+
65
+ # Include the Jammit asset helpers in all views, a-la ApplicationHelper.
66
+ ::ActionView::Base.send(:include, Jammit::Helper)
@@ -0,0 +1 @@
1
+ var template = function(str){var fn = new Function('obj', 'var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push(\''+str.replace(/[\r\t\n]/g, " ").split("<%").join("\t").replace(/((^|%>)[^\t]*)'/g, "$1\r").replace(/\t=(.*?)%>/g, "',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');"); return fn;};
@@ -0,0 +1,113 @@
1
+ module Jammit
2
+
3
+ # The Jammit::Packager resolves the configuration file into lists of real
4
+ # assets that get merged into individual asset packages. Given the compiled
5
+ # contents of an asset package, the Packager knows how to cache that package
6
+ # with the correct timestamps.
7
+ class Packager
8
+
9
+ # In Rails, the difference between a path and an asset URL is "public".
10
+ PATH_TO_URL = /\A\/?public/
11
+
12
+ # Creating a new Packager will rebuild the list of assets from the
13
+ # Jammit.configuration. When assets.yml is being changed on the fly,
14
+ # create a new Packager.
15
+ def initialize
16
+ @compressor = Compressor.new
17
+ @config = {
18
+ :css => (Jammit.configuration[:stylesheets] || {}).symbolize_keys,
19
+ :js => (Jammit.configuration[:javascripts] || {}).symbolize_keys,
20
+ :jst => (Jammit.configuration[:templates] || {}).symbolize_keys
21
+ }
22
+ @packages = {
23
+ :css => create_packages(@config[:css]),
24
+ :js => create_packages(@config[:js]),
25
+ :jst => create_packages(@config[:jst])
26
+ }
27
+ end
28
+
29
+ # Ask the packager to precache all defined assets, along with their gzip'd
30
+ # versions. In order to prebuild the MHTML stylesheets, we need to know the
31
+ # base_url, because IE only supports MHTML with absolute references.
32
+ def precache_all(output_dir=nil, base_url=nil)
33
+ output_dir ||= "public/#{Jammit.package_path}"
34
+ @config[:js].keys.each {|p| cache(p, 'js', pack_javascripts(p), output_dir) }
35
+ @config[:jst].keys.each {|p| cache(p, 'jst', pack_templates(p), output_dir) }
36
+ @config[:css].keys.each do |p|
37
+ cache(p, 'css', pack_stylesheets(p), output_dir)
38
+ if Jammit.embed_images
39
+ cache(p, 'css', pack_stylesheets(p, :datauri), output_dir, :datauri)
40
+ if Jammit.mhtml_enabled && base_url
41
+ mtime = Time.now
42
+ asset_url = "#{base_url}#{Jammit.asset_url(p, :css, :mhtml, mtime)}"
43
+ cache(p, 'css', pack_stylesheets(p, :mhtml, asset_url), output_dir, :mhtml, mtime)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ # Caches a single prebuilt asset package and gzips it at the highest
50
+ # compression level. Ensures that the modification time of both both
51
+ # variants is identical, for web server caching modules, as well as MHTML.
52
+ def cache(package, extension, contents, output_dir, suffix=nil, mtime=Time.now)
53
+ FileUtils.mkdir_p(output_dir) unless File.exists?(output_dir)
54
+ filename = File.join(output_dir, Jammit.filename(package, extension, suffix))
55
+ zip_name = "#{filename}.gz"
56
+ File.open(filename, 'wb+') {|f| f.write(contents) }
57
+ Zlib::GzipWriter.open(zip_name, Zlib::BEST_COMPRESSION) {|f| f.write(contents) }
58
+ File.utime(mtime, mtime, filename, zip_name)
59
+ end
60
+
61
+ # Get the list of individual assets for a package.
62
+ def individual_urls(package, extension)
63
+ package_for(package, extension)[:urls]
64
+ end
65
+
66
+ # Return the compressed contents of a stylesheet package.
67
+ def pack_stylesheets(package, variant=nil, asset_url=nil)
68
+ @compressor.compress_css(package_for(package, :css)[:paths], variant, asset_url)
69
+ end
70
+
71
+ # Return the compressed contents of a javascript package.
72
+ def pack_javascripts(package)
73
+ @compressor.compress_js(package_for(package, :js)[:paths])
74
+ end
75
+
76
+ # Return the compiled contents of a JST package.
77
+ def pack_templates(package)
78
+ @compressor.compile_jst(package_for(package, :jst)[:paths])
79
+ end
80
+
81
+
82
+ private
83
+
84
+ # Look up a package asset list by name, raising an exception if the
85
+ # package has gone missing.
86
+ def package_for(package, extension)
87
+ pack = @packages[extension] && @packages[extension][package]
88
+ pack || not_found(package, extension)
89
+ end
90
+
91
+ # Compiles the list of assets that goes into each package. Runs an ordered
92
+ # list of Dir.globs, taking the merged unique result.
93
+ def create_packages(config)
94
+ packages = {}
95
+ return packages if !config
96
+ config.each do |name, globs|
97
+ globs ||= []
98
+ packages[name] = {}
99
+ paths = globs.map {|glob| Dir[glob] }.flatten.uniq
100
+ packages[name][:paths] = paths
101
+ packages[name][:urls] = paths.map {|path| path.sub(PATH_TO_URL, '') }
102
+ end
103
+ packages
104
+ end
105
+
106
+ # Raise a PackageNotFound exception for missing packages...
107
+ def not_found(package, extension)
108
+ raise PackageNotFound, "assets.yml does not contain a \"#{package}\" #{extension.to_s.upcase} package"
109
+ end
110
+
111
+ end
112
+
113
+ end
@@ -0,0 +1,16 @@
1
+ module Jammit
2
+
3
+ module Routes
4
+
5
+ # Jammit uses a single route in order to slow down Rails' routing speed
6
+ # by the absolute minimum. In your config/routes.rb file, call:
7
+ # Jammit::Routes.draw(map)
8
+ # Passing in the routing "map" object.
9
+ def self.draw(map)
10
+ map.jammit "/#{Jammit.package_path}/:package.:extension",
11
+ :controller => 'jammit', :action => 'package'
12
+ end
13
+
14
+ end
15
+
16
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jammit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jeremy Ashkenas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-06 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rails
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.0.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: yui-compressor
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.9.1
34
+ version:
35
+ description: " Jammit is an industrial strength asset packaging library for Rails,\n providing both the CSS and JavaScript concatenation and compression\n that you'd expect, as well as ahead-of-time gzipping, built-in JavaScript\n template support, and optional Data-URI / MHTML image embedding.\n"
36
+ email: jeremy@documentcloud.org
37
+ executables:
38
+ - jammit
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README
43
+ files:
44
+ - bin/jammit
45
+ - jammit.gemspec
46
+ - lib/jammit.rb
47
+ - lib/jammit/command_line.rb
48
+ - lib/jammit/compressor.rb
49
+ - lib/jammit/controller.rb
50
+ - lib/jammit/helper.rb
51
+ - lib/jammit/jst.js
52
+ - lib/jammit/packager.rb
53
+ - lib/jammit/routes.rb
54
+ - LICENSE
55
+ - Rakefile
56
+ - README
57
+ has_rdoc: true
58
+ homepage: http://documentcloud.github.com/jammit/
59
+ licenses: []
60
+
61
+ post_install_message:
62
+ rdoc_options:
63
+ - --title
64
+ - Jammit
65
+ - --exclude
66
+ - test
67
+ - --main
68
+ - README
69
+ - --all
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: "0"
77
+ version:
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: "0"
83
+ version:
84
+ requirements: []
85
+
86
+ rubyforge_project: jammit
87
+ rubygems_version: 1.3.5
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: Industrial Strength Asset Packaging for Rails
91
+ test_files: []
92
+