ee_jammit 0.6.6
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.
- checksums.yaml +15 -0
- data/LICENSE +22 -0
- data/README +24 -0
- data/bin/jammit +7 -0
- data/jammit.gemspec +34 -0
- data/lib/jammit.rb +236 -0
- data/lib/jammit/command_line.rb +84 -0
- data/lib/jammit/compressor.rb +266 -0
- data/lib/jammit/controller.rb +97 -0
- data/lib/jammit/cssmin_compressor.rb +10 -0
- data/lib/jammit/dependencies.rb +61 -0
- data/lib/jammit/helper.rb +88 -0
- data/lib/jammit/jsmin_compressor.rb +10 -0
- data/lib/jammit/jst.js +1 -0
- data/lib/jammit/middleware.rb +115 -0
- data/lib/jammit/packager.rb +179 -0
- data/lib/jammit/railtie.rb +14 -0
- data/lib/jammit/routes.rb +23 -0
- data/lib/jammit/sass_compressor.rb +17 -0
- data/lib/jammit/uglifier.rb +3 -0
- data/rails/routes.rb +10 -0
- metadata +104 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
require 'action_controller'
|
|
2
|
+
|
|
3
|
+
module Jammit
|
|
4
|
+
|
|
5
|
+
# The JammitController is added to your Rails application when the Gem is
|
|
6
|
+
# loaded. It takes responsibility for /assets, and dynamically packages any
|
|
7
|
+
# missing or uncached asset packages.
|
|
8
|
+
class Controller < ActionController::Base
|
|
9
|
+
|
|
10
|
+
VALID_FORMATS = [:css, :js]
|
|
11
|
+
|
|
12
|
+
SUFFIX_STRIPPER = /-(datauri|mhtml)\Z/
|
|
13
|
+
|
|
14
|
+
NOT_FOUND_PATH = "#{Jammit.public_root}/404.html"
|
|
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
|
+
template_ext = Jammit.template_extension.to_sym
|
|
21
|
+
case @extension
|
|
22
|
+
when :js
|
|
23
|
+
render :js => (@contents = Jammit.packager.pack_javascripts(@package))
|
|
24
|
+
when template_ext
|
|
25
|
+
render :js => (@contents = Jammit.packager.pack_templates(@package))
|
|
26
|
+
when :css
|
|
27
|
+
render :text => generate_stylesheets, :content_type => 'text/css'
|
|
28
|
+
end
|
|
29
|
+
cache_package if perform_caching && (@extension != template_ext)
|
|
30
|
+
rescue Jammit::PackageNotFound
|
|
31
|
+
package_not_found
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
# Tells the Jammit::Packager to cache and gzip an asset package. We can't
|
|
38
|
+
# just use the built-in "cache_page" because we need to ensure that
|
|
39
|
+
# the timestamp that ends up in the MHTML is also on the cached file.
|
|
40
|
+
def cache_package
|
|
41
|
+
dir = File.join(page_cache_directory, Jammit.package_path)
|
|
42
|
+
Jammit.packager.cache(@package, @extension, @contents, dir, @variant, @mtime)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Generate the complete, timestamped, MHTML url -- if we're rendering a
|
|
46
|
+
# dynamic MHTML package, we'll need to put one URL in the response, and a
|
|
47
|
+
# different one into the cached package.
|
|
48
|
+
def prefix_url(path)
|
|
49
|
+
host = request.port == 80 ? request.host : request.host_with_port
|
|
50
|
+
"#{request.protocol}#{host}#{path}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# If we're generating MHTML/CSS, return a stylesheet with the absolute
|
|
54
|
+
# request URL to the client, and cache a version with the timestamped cache
|
|
55
|
+
# URL swapped in.
|
|
56
|
+
def generate_stylesheets
|
|
57
|
+
return @contents = Jammit.packager.pack_stylesheets(@package, @variant) unless @variant == :mhtml
|
|
58
|
+
@mtime = Time.now
|
|
59
|
+
request_url = prefix_url(request.fullpath)
|
|
60
|
+
cached_url = prefix_url(Jammit.asset_url(@package, @extension, @variant, @mtime))
|
|
61
|
+
css = Jammit.packager.pack_stylesheets(@package, @variant, request_url)
|
|
62
|
+
@contents = css.gsub(request_url, cached_url) if perform_caching
|
|
63
|
+
css
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Extracts the package name, extension (:css, :js), and variant (:datauri,
|
|
67
|
+
# :mhtml) from the incoming URL.
|
|
68
|
+
def parse_request
|
|
69
|
+
pack = params[:package]
|
|
70
|
+
@extension = params[:extension].to_sym
|
|
71
|
+
raise PackageNotFound unless (VALID_FORMATS + [Jammit.template_extension.to_sym]).include?(@extension)
|
|
72
|
+
if Jammit.embed_assets
|
|
73
|
+
suffix_match = pack.match(SUFFIX_STRIPPER)
|
|
74
|
+
@variant = Jammit.embed_assets && suffix_match && suffix_match[1].to_sym
|
|
75
|
+
pack.sub!(SUFFIX_STRIPPER, '')
|
|
76
|
+
end
|
|
77
|
+
@package = pack.to_sym
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Render the 404 page, if one exists, for any packages that don't.
|
|
81
|
+
def package_not_found
|
|
82
|
+
return render(:file => NOT_FOUND_PATH, :status => 404) if File.exists?(NOT_FOUND_PATH)
|
|
83
|
+
render :text => "<h1>404: \"#{@package}\" asset package not found.</h1>", :status => 404
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Make the Jammit::Controller available to Rails as a top-level controller.
|
|
91
|
+
::JammitController = Jammit::Controller
|
|
92
|
+
|
|
93
|
+
if defined?(Rails) && Rails.env.development?
|
|
94
|
+
ActionController::Base.class_eval do
|
|
95
|
+
append_before_filter { Jammit.reload! }
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Standard Library Dependencies:
|
|
2
|
+
require 'uri'
|
|
3
|
+
require 'erb'
|
|
4
|
+
require 'zlib'
|
|
5
|
+
require 'yaml'
|
|
6
|
+
require 'base64'
|
|
7
|
+
require 'pathname'
|
|
8
|
+
require 'fileutils'
|
|
9
|
+
|
|
10
|
+
# Try Uglifier.
|
|
11
|
+
begin
|
|
12
|
+
require 'uglifier'
|
|
13
|
+
require 'jammit/uglifier'
|
|
14
|
+
rescue LoadError
|
|
15
|
+
Jammit.javascript_compressors.delete :uglifier
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Try YUI
|
|
19
|
+
begin
|
|
20
|
+
require 'yui/compressor'
|
|
21
|
+
rescue LoadError
|
|
22
|
+
Jammit.javascript_compressors.delete :yui
|
|
23
|
+
Jammit.css_compressors.delete :yui
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Try Closure.
|
|
27
|
+
begin
|
|
28
|
+
require 'closure-compiler'
|
|
29
|
+
rescue LoadError
|
|
30
|
+
Jammit.javascript_compressors.delete :closure
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Try Sass
|
|
34
|
+
begin
|
|
35
|
+
require 'sass'
|
|
36
|
+
require 'jammit/sass_compressor'
|
|
37
|
+
rescue LoadError
|
|
38
|
+
Jammit.css_compressors.delete :sass
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Load initial configuration before the rest of Jammit.
|
|
42
|
+
Jammit.load_configuration(Jammit::DEFAULT_CONFIG_PATH, true) if defined?(Rails)
|
|
43
|
+
|
|
44
|
+
# Jammit Core:
|
|
45
|
+
require 'jsmin'
|
|
46
|
+
require 'cssmin'
|
|
47
|
+
require 'jammit/jsmin_compressor'
|
|
48
|
+
require 'jammit/cssmin_compressor'
|
|
49
|
+
require 'jammit/compressor'
|
|
50
|
+
require 'jammit/packager'
|
|
51
|
+
require 'jammit/helper'
|
|
52
|
+
require 'jammit/middleware'
|
|
53
|
+
|
|
54
|
+
# Jammit Rails Integration:
|
|
55
|
+
if defined?(Rails)
|
|
56
|
+
require 'jammit/controller'
|
|
57
|
+
require 'jammit/helper'
|
|
58
|
+
require 'jammit/railtie'
|
|
59
|
+
require 'jammit/routes'
|
|
60
|
+
end
|
|
61
|
+
|
|
@@ -0,0 +1,88 @@
|
|
|
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)]><!-->" unless defined?(DATA_URI_START)
|
|
10
|
+
DATA_URI_END = "<!--<![endif]-->" unless defined?(DATA_URI_END)
|
|
11
|
+
MHTML_START = "<!--[if lte IE 7]>" unless defined?(MHTML_START)
|
|
12
|
+
MHTML_END = "<![endif]-->" unless defined?(MHTML_END)
|
|
13
|
+
|
|
14
|
+
# If embed_assets 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
|
+
options = packages.extract_options!
|
|
19
|
+
return html_safe(individual_stylesheets(packages, options)) unless should_package?
|
|
20
|
+
disabled = (options.delete(:embed_assets) == false) || (options.delete(:embed_images) == false)
|
|
21
|
+
return html_safe(packaged_stylesheets(packages, options)) if disabled || !Jammit.embed_assets
|
|
22
|
+
return html_safe(embedded_image_stylesheets(packages, options))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Writes out the URL to the bundled and compressed javascript package,
|
|
26
|
+
# except in development, where it references the individual scripts.
|
|
27
|
+
def include_javascripts(*packages)
|
|
28
|
+
options = packages.extract_options!
|
|
29
|
+
html_safe packages.map {|pack|
|
|
30
|
+
should_package? ? Jammit.asset_url(pack, :js) : Jammit.packager.individual_urls(pack.to_sym, :js)
|
|
31
|
+
}.flatten.map {|pack|
|
|
32
|
+
javascript_include_tag pack, options
|
|
33
|
+
}.join("\n")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Writes out the URL to the concatenated and compiled JST file -- we always
|
|
37
|
+
# have to pre-process it, even in development.
|
|
38
|
+
def include_templates(*packages)
|
|
39
|
+
raise DeprecationError, "Jammit 0.5+ no longer supports separate packages for templates.\nYou can include your JST alongside your JS, and use include_javascripts."
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def should_package?
|
|
46
|
+
Jammit.package_assets && !(Jammit.allow_debugging && params[:debug_assets])
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def html_safe(string)
|
|
50
|
+
string.respond_to?(:html_safe) ? string.html_safe : string
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# HTML tags, in order, for all of the individual stylesheets.
|
|
54
|
+
def individual_stylesheets(packages, options)
|
|
55
|
+
tags_with_options(packages, options) {|p| Jammit.packager.individual_urls(p.to_sym, :css) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# HTML tags for the stylesheet packages.
|
|
59
|
+
def packaged_stylesheets(packages, options)
|
|
60
|
+
tags_with_options(packages, options) {|p| Jammit.asset_url(p, :css) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# HTML tags for the 'datauri', and 'mhtml' versions of the packaged
|
|
64
|
+
# stylesheets, using conditional comments to load the correct variant.
|
|
65
|
+
def embedded_image_stylesheets(packages, options)
|
|
66
|
+
datauri_tags = tags_with_options(packages, options) {|p| Jammit.asset_url(p, :css, :datauri) }
|
|
67
|
+
ie_tags = Jammit.mhtml_enabled ?
|
|
68
|
+
tags_with_options(packages, options) {|p| Jammit.asset_url(p, :css, :mhtml) } :
|
|
69
|
+
packaged_stylesheets(packages, options)
|
|
70
|
+
[DATA_URI_START, datauri_tags, DATA_URI_END, MHTML_START, ie_tags, MHTML_END].join("\n")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Generate the stylesheet tags for a batch of packages, with options, by
|
|
74
|
+
# yielding each package to a block.
|
|
75
|
+
def tags_with_options(packages, options)
|
|
76
|
+
packages.dup.map {|package|
|
|
77
|
+
yield package
|
|
78
|
+
}.flatten.map {|package|
|
|
79
|
+
stylesheet_link_tag package, options
|
|
80
|
+
}.join("\n")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Include the Jammit asset helpers in all views, a-la ApplicationHelper.
|
|
88
|
+
::ActionView::Base.send(:include, Jammit::Helper) if defined?(::ActionView)
|
data/lib/jammit/jst.js
ADDED
|
@@ -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(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/<%=([\s\S]+?)%>/g,function(match,code){return "',"+code.replace(/\\'/g, "'")+",'";}).replace(/<%([\s\S]+?)%>/g,function(match,code){return "');"+code.replace(/\\'/g, "'").replace(/[\r\n\t]/g,' ')+"__p.push('";}).replace(/\r/g,'\\r').replace(/\n/g,'\\n').replace(/\t/g,'\\t')+"');}return __p.join('');");return fn;};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
module Jammit
|
|
2
|
+
|
|
3
|
+
# Rack Middle that allows Jammit to integrate with any Rack compatible web
|
|
4
|
+
# framework. It takes responsibility for /assets, and dynamically packages
|
|
5
|
+
# any missing or uncached asset packages.
|
|
6
|
+
|
|
7
|
+
class Middleware
|
|
8
|
+
|
|
9
|
+
VALID_FORMATS = [:css, :js]
|
|
10
|
+
|
|
11
|
+
SUFFIX_STRIPPER = /-(datauri|mhtml)\Z/
|
|
12
|
+
|
|
13
|
+
PUBLIC_ROOT = defined?(Jammit.public_root) ? Jammit.public_root : PUBLIC_ROOT
|
|
14
|
+
NOT_FOUND_PATH = "#{PUBLIC_ROOT}/404.html"
|
|
15
|
+
|
|
16
|
+
def initialize(app)
|
|
17
|
+
@app = app
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call(env)
|
|
21
|
+
dup._call(env)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def _call(env)
|
|
25
|
+
if matches = %r(^/#{Jammit.package_path}/(.*)\.(.*)).match(env['PATH_INFO'])
|
|
26
|
+
package(matches[1].to_s, matches[2] || "none")
|
|
27
|
+
else
|
|
28
|
+
@app.call(env)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
# The "package" action receives all requests for asset packages that haven't
|
|
35
|
+
# yet been cached. The package will be built, cached, and gzipped.
|
|
36
|
+
def package(package, extension)
|
|
37
|
+
parse_request(package, extension)
|
|
38
|
+
template_ext = Jammit.template_extension.to_sym
|
|
39
|
+
result = []
|
|
40
|
+
|
|
41
|
+
headers = {
|
|
42
|
+
'Cache-Control' => "public, max-age=#{(60*60*24*365.25*10).to_i}",
|
|
43
|
+
'Expires' => (Time.now + 60*60*24*365.25*10).httpdate
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
case @extension
|
|
47
|
+
when :js
|
|
48
|
+
@contents = Jammit.packager.pack_javascripts(@package)
|
|
49
|
+
@contents = @contents.to_js if @contents.respond_to?(:to_js)
|
|
50
|
+
result = [
|
|
51
|
+
200,
|
|
52
|
+
headers.merge({
|
|
53
|
+
'Content-Type' => Rack::Mime.mime_type(".js")
|
|
54
|
+
}),
|
|
55
|
+
[@contents]
|
|
56
|
+
]
|
|
57
|
+
when template_ext
|
|
58
|
+
@contents = Jammit.packager.pack_templates(@package)
|
|
59
|
+
@contents = @contents.to_js if @contents.respond_to?(:to_js)
|
|
60
|
+
[
|
|
61
|
+
200,
|
|
62
|
+
headers.merge({
|
|
63
|
+
'Content-Type' => Rack::Mime.mime_type(".js")
|
|
64
|
+
}),
|
|
65
|
+
[@contents]
|
|
66
|
+
]
|
|
67
|
+
when :css
|
|
68
|
+
[
|
|
69
|
+
200,
|
|
70
|
+
headers.merge({
|
|
71
|
+
'Content-Type' => Rack::Mime.mime_type(".css")
|
|
72
|
+
}),
|
|
73
|
+
[generate_stylesheets]
|
|
74
|
+
]
|
|
75
|
+
end
|
|
76
|
+
rescue Jammit::PackageNotFound
|
|
77
|
+
[404, {'Content-Type' => 'text/plain'}, ['Not found']]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Generate the complete, timestamped, MHTML url -- if we're rendering a
|
|
81
|
+
# dynamic MHTML package, we'll need to put one URL in the response, and a
|
|
82
|
+
# different one into the cached package.
|
|
83
|
+
def prefix_url(path)
|
|
84
|
+
host = request.port == 80 ? request.host : request.host_with_port
|
|
85
|
+
"#{request.protocol}#{host}#{path}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# If we're generating MHTML/CSS, return a stylesheet with the absolute
|
|
89
|
+
# request URL to the client, and cache a version with the timestamped cache
|
|
90
|
+
# URL swapped in.
|
|
91
|
+
def generate_stylesheets
|
|
92
|
+
return @contents = Jammit.packager.pack_stylesheets(@package, @variant) unless @variant == :mhtml
|
|
93
|
+
@mtime = Time.now
|
|
94
|
+
request_url = prefix_url(request.fullpath)
|
|
95
|
+
cached_url = prefix_url(Jammit.asset_url(@package, @extension, @variant, @mtime))
|
|
96
|
+
css = Jammit.packager.pack_stylesheets(@package, @variant, request_url)
|
|
97
|
+
# @contents = css.gsub(request_url, cached_url) if perform_caching
|
|
98
|
+
css
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Extracts the package name, extension (:css, :js), and variant (:datauri,
|
|
102
|
+
# :mhtml) from the incoming URL.
|
|
103
|
+
def parse_request(pack, extension)
|
|
104
|
+
@extension = extension.to_sym
|
|
105
|
+
raise PackageNotFound unless (VALID_FORMATS + [Jammit.template_extension.to_sym]).include?(@extension)
|
|
106
|
+
if Jammit.embed_assets
|
|
107
|
+
suffix_match = pack.match(SUFFIX_STRIPPER)
|
|
108
|
+
@variant = Jammit.embed_assets && suffix_match && suffix_match[1].to_sym
|
|
109
|
+
pack.sub!(SUFFIX_STRIPPER, '')
|
|
110
|
+
end
|
|
111
|
+
@package = pack.to_sym
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,179 @@
|
|
|
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
|
+
# Set force to false to allow packages to only be rebuilt when their source
|
|
10
|
+
# files have changed since the last time their package was built.
|
|
11
|
+
attr_accessor :force, :package_names
|
|
12
|
+
|
|
13
|
+
# Creating a new Packager will rebuild the list of assets from the
|
|
14
|
+
# Jammit.configuration. When assets.yml is being changed on the fly,
|
|
15
|
+
# create a new Packager.
|
|
16
|
+
def initialize
|
|
17
|
+
@force = false
|
|
18
|
+
@package_names = nil
|
|
19
|
+
@config = {
|
|
20
|
+
:css => (Jammit.configuration[:stylesheets] || {}),
|
|
21
|
+
:js => (Jammit.configuration[:javascripts] || {})
|
|
22
|
+
}
|
|
23
|
+
@packages = {
|
|
24
|
+
:css => create_packages(@config[:css]),
|
|
25
|
+
:js => create_packages(@config[:js])
|
|
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
|
+
# Unless forced, will only rebuild assets whose source files have been
|
|
33
|
+
# changed since their last package build.
|
|
34
|
+
def precache_all(output_dir=nil, base_url=nil)
|
|
35
|
+
output_dir ||= File.join(Jammit.public_root, Jammit.package_path)
|
|
36
|
+
cacheable(:js, output_dir).each {|p| cache(p, 'js', pack_javascripts(p), output_dir) }
|
|
37
|
+
cacheable(:css, output_dir).each do |p|
|
|
38
|
+
cache(p, 'css', pack_stylesheets(p), output_dir)
|
|
39
|
+
if Jammit.embed_assets
|
|
40
|
+
cache(p, 'css', pack_stylesheets(p, :datauri), output_dir, :datauri)
|
|
41
|
+
if Jammit.mhtml_enabled
|
|
42
|
+
raise MissingConfiguration, "A --base-url option is required in order to generate MHTML." unless base_url
|
|
43
|
+
mtime = latest_mtime package_for(p, :css)[:paths]
|
|
44
|
+
asset_url = "#{base_url}#{Jammit.asset_url(p, :css, :mhtml, mtime)}"
|
|
45
|
+
cache(p, 'css', pack_stylesheets(p, :mhtml, asset_url), output_dir, :mhtml, mtime)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Caches a single prebuilt asset package and gzips it at the highest
|
|
52
|
+
# compression level. Ensures that the modification time of both both
|
|
53
|
+
# variants is identical, for web server caching modules, as well as MHTML.
|
|
54
|
+
def cache(package, extension, contents, output_dir, suffix=nil, mtime=nil)
|
|
55
|
+
FileUtils.mkdir_p(output_dir) unless File.exists?(output_dir)
|
|
56
|
+
raise OutputNotWritable, "Jammit doesn't have permission to write to \"#{output_dir}\"" unless File.writable?(output_dir)
|
|
57
|
+
mtime ||= latest_mtime package_for(package, extension.to_sym)[:paths]
|
|
58
|
+
files = []
|
|
59
|
+
files << file_name = File.join(output_dir, Jammit.filename(package, extension, suffix))
|
|
60
|
+
File.open(file_name, 'wb+') {|f| f.write(contents) }
|
|
61
|
+
if Jammit.gzip_assets
|
|
62
|
+
files << zip_name = "#{file_name}.gz"
|
|
63
|
+
Zlib::GzipWriter.open(zip_name, Zlib::BEST_COMPRESSION) {|f| f.write(contents) }
|
|
64
|
+
end
|
|
65
|
+
File.utime(mtime, mtime, *files)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Get the list of individual assets for a package.
|
|
69
|
+
def individual_urls(package, extension)
|
|
70
|
+
package_for(package, extension)[:urls]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def compressor
|
|
74
|
+
@compressor ||= Compressor.new
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Return the compressed contents of a stylesheet package.
|
|
78
|
+
def pack_stylesheets(package, variant=nil, asset_url=nil)
|
|
79
|
+
compressor.compress_css(package_for(package, :css)[:paths], variant, asset_url)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Return the compressed contents of a javascript package.
|
|
83
|
+
def pack_javascripts(package)
|
|
84
|
+
compressor.compress_js(package_for(package, :js)[:paths])
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Return the compiled contents of a JST package.
|
|
88
|
+
def pack_templates(package)
|
|
89
|
+
compressor.compile_jst(package_for(package, :js)[:paths])
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
# Look up a package asset list by name, raising an exception if the
|
|
96
|
+
# package has gone missing.
|
|
97
|
+
def package_for(package, extension)
|
|
98
|
+
pack = @packages[extension] && @packages[extension][package]
|
|
99
|
+
pack || not_found(package, extension)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Absolute globs are absolute -- relative globs are relative to ASSET_ROOT.
|
|
103
|
+
# Print a warning if no files were found that match the glob.
|
|
104
|
+
def glob_files(glob)
|
|
105
|
+
absolute = Pathname.new(glob).absolute?
|
|
106
|
+
paths = Dir[absolute ? glob : File.join(ASSET_ROOT, glob)].sort
|
|
107
|
+
Jammit.warn("No assets match '#{glob}'") if paths.empty?
|
|
108
|
+
paths
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# In Rails, the difference between a path and an asset URL is "public".
|
|
112
|
+
def path_to_url
|
|
113
|
+
@path_to_url ||= /\A#{Regexp.escape(ASSET_ROOT)}(\/?#{Regexp.escape(Jammit.public_root.sub(ASSET_ROOT, ''))})?/
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Get the latest mtime of a list of files (plus the config path).
|
|
117
|
+
def latest_mtime(paths)
|
|
118
|
+
paths += [Jammit.config_path]
|
|
119
|
+
paths.map {|p| File.mtime(p) }.max || Time.now
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Return a list of all of the packages that should be cached. If "force" is
|
|
123
|
+
# true, this is all of them -- otherwise only the packages that are missing
|
|
124
|
+
# or whose source files have changed since the last package build.
|
|
125
|
+
def cacheable(extension, output_dir)
|
|
126
|
+
names = @packages[extension].keys
|
|
127
|
+
names = names.select {|n| @package_names.include? n } if @package_names
|
|
128
|
+
config_mtime = File.mtime(Jammit.config_path)
|
|
129
|
+
return names if @force
|
|
130
|
+
return names.select do |name|
|
|
131
|
+
pack = package_for(name, extension)
|
|
132
|
+
cached = [Jammit.filename(name, extension)]
|
|
133
|
+
if extension == :css
|
|
134
|
+
cached.push Jammit.filename(name, extension, :datauri) if Jammit.embed_assets
|
|
135
|
+
cached.push Jammit.filename(name, extension, :mhtml) if Jammit.mhtml_enabled
|
|
136
|
+
end
|
|
137
|
+
cached.map! {|file| File.join(output_dir, file) }
|
|
138
|
+
if cached.any? {|file| !File.exists?(file) }
|
|
139
|
+
true
|
|
140
|
+
else
|
|
141
|
+
since = cached.map {|file| File.mtime(file) }.min
|
|
142
|
+
config_mtime > since || pack[:paths].any? {|src| File.mtime(src) > since }
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Compiles the list of assets that goes into each package. Runs an
|
|
148
|
+
# ordered list of Dir.globs, taking the merged unique result.
|
|
149
|
+
# If there are JST files in this package we need to add an extra
|
|
150
|
+
# path for when package_assets is off (e.g. in a dev environment).
|
|
151
|
+
# This package (e.g. /assets/package-name.jst) will never exist as
|
|
152
|
+
# an actual file but will be dynamically generated by Jammit on
|
|
153
|
+
# every request.
|
|
154
|
+
def create_packages(config)
|
|
155
|
+
packages = {}
|
|
156
|
+
return packages if !config
|
|
157
|
+
config.each do |name, globs|
|
|
158
|
+
globs ||= []
|
|
159
|
+
packages[name] = {}
|
|
160
|
+
paths = globs.flatten.uniq.map {|glob| glob_files(glob) }.flatten.uniq
|
|
161
|
+
packages[name][:paths] = paths
|
|
162
|
+
if !paths.grep(Jammit.template_extension_matcher).empty?
|
|
163
|
+
packages[name][:urls] = paths.grep(JS_EXTENSION).map {|path| path.sub(path_to_url, '') }
|
|
164
|
+
packages[name][:urls] += [Jammit.asset_url(name, Jammit.template_extension)]
|
|
165
|
+
else
|
|
166
|
+
packages[name][:urls] = paths.map {|path| path.sub(path_to_url, '') }
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
packages
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Raise a PackageNotFound exception for missing packages...
|
|
173
|
+
def not_found(package, extension)
|
|
174
|
+
raise PackageNotFound, "assets.yml does not contain a \"#{package}\" #{extension.to_s.upcase} package"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
end
|