jammit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+