jammit 0.3.3 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/jammit.gemspec +2 -2
- data/lib/jammit.rb +20 -7
- data/lib/jammit/command_line.rb +1 -1
- data/lib/jammit/compressor.rb +73 -52
- data/lib/jammit/controller.rb +2 -2
- data/lib/jammit/dependencies.rb +1 -0
- data/lib/jammit/helper.rb +3 -2
- data/lib/jammit/jst.js +1 -1
- data/lib/jammit/packager.rb +8 -2
- metadata +2 -2
data/jammit.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'jammit'
|
3
|
-
s.version = '0.
|
4
|
-
s.date = '2009-1-
|
3
|
+
s.version = '0.4.0' # Keep version in sync with jammit.rb
|
4
|
+
s.date = '2009-1-26'
|
5
5
|
|
6
6
|
s.homepage = "http://documentcloud.github.com/jammit/"
|
7
7
|
s.summary = "Industrial Strength Asset Packaging for Rails"
|
data/lib/jammit.rb
CHANGED
@@ -4,11 +4,11 @@ $LOAD_PATH.push File.expand_path(File.dirname(__FILE__))
|
|
4
4
|
# to all of the configuration options.
|
5
5
|
module Jammit
|
6
6
|
|
7
|
-
VERSION = "0.
|
7
|
+
VERSION = "0.4.0"
|
8
8
|
|
9
9
|
ROOT = File.expand_path(File.dirname(__FILE__) + '/..')
|
10
10
|
|
11
|
-
ASSET_ROOT = File.expand_path(defined?(
|
11
|
+
ASSET_ROOT = File.expand_path(defined?(Rails) ? Rails.root : ".") unless defined?(ASSET_ROOT)
|
12
12
|
|
13
13
|
PUBLIC_ROOT = "#{ASSET_ROOT}/public"
|
14
14
|
|
@@ -20,10 +20,16 @@ module Jammit
|
|
20
20
|
|
21
21
|
DEFAULT_JST_COMPILER = "template"
|
22
22
|
|
23
|
+
DEFAULT_JST_NAMESPACE = "window.JST"
|
24
|
+
|
23
25
|
AVAILABLE_COMPRESSORS = [:yui, :closure]
|
24
26
|
|
25
27
|
DEFAULT_COMPRESSOR = :yui
|
26
28
|
|
29
|
+
# Extension matchers for JavaScript and JST, which need to be disambiguated.
|
30
|
+
JS_EXT = /\.js\Z/
|
31
|
+
JST_EXT = /\.jst\Z/
|
32
|
+
|
27
33
|
# Jammit raises a @PackageNotFound@ exception when a non-existent package is
|
28
34
|
# requested by a browser -- rendering a 404.
|
29
35
|
class PackageNotFound < NameError; end
|
@@ -37,7 +43,7 @@ module Jammit
|
|
37
43
|
class OutputNotWritable < StandardError; end
|
38
44
|
|
39
45
|
class << self
|
40
|
-
attr_reader :configuration, :template_function, :
|
46
|
+
attr_reader :configuration, :template_function, :template_namespace, :embed_assets, :package_path,
|
41
47
|
:package_assets, :compress_assets, :mhtml_enabled, :include_jst_script,
|
42
48
|
:javascript_compressor, :compressor_options, :css_compressor_options
|
43
49
|
end
|
@@ -48,19 +54,21 @@ module Jammit
|
|
48
54
|
|
49
55
|
# Load the complete asset configuration from the specified @config_path@.
|
50
56
|
def self.load_configuration(config_path)
|
51
|
-
|
52
|
-
raise ConfigurationNotFound, "could not find the \"#{config_path}\" configuration file" unless
|
57
|
+
exists = config_path && File.exists?(config_path)
|
58
|
+
raise ConfigurationNotFound, "could not find the \"#{config_path}\" configuration file" unless exists
|
59
|
+
conf = YAML.load(ERB.new(File.read(config_path)).result)
|
53
60
|
@config_path = config_path
|
54
61
|
@configuration = conf = conf.symbolize_keys
|
55
62
|
@package_path = conf[:package_path] || DEFAULT_PACKAGE_PATH
|
56
|
-
@
|
63
|
+
@embed_assets = conf[:embed_assets] || conf[:embed_images]
|
57
64
|
@compress_assets = !(conf[:compress_assets] == false)
|
58
|
-
@mhtml_enabled = @
|
65
|
+
@mhtml_enabled = @embed_assets && @embed_assets != "datauri"
|
59
66
|
@compressor_options = (conf[:compressor_options] || {}).symbolize_keys
|
60
67
|
@css_compressor_options = (conf[:css_compressor_options] || {}).symbolize_keys
|
61
68
|
set_javascript_compressor(conf[:javascript_compressor])
|
62
69
|
set_package_assets(conf[:package_assets])
|
63
70
|
set_template_function(conf[:template_function])
|
71
|
+
set_template_namespace(conf[:template_namespace])
|
64
72
|
check_java_version
|
65
73
|
self
|
66
74
|
end
|
@@ -113,6 +121,11 @@ module Jammit
|
|
113
121
|
@include_jst_script = @template_function == DEFAULT_JST_COMPILER
|
114
122
|
end
|
115
123
|
|
124
|
+
# Set the root JS object in which to stash all compiled JST.
|
125
|
+
def self.set_template_namespace(value)
|
126
|
+
@template_namespace = value == true || value.nil? ? DEFAULT_JST_NAMESPACE : value.to_s
|
127
|
+
end
|
128
|
+
|
116
129
|
# The YUI Compressor requires Java > 1.4, and Closure requires Java > 1.6.
|
117
130
|
def self.check_java_version
|
118
131
|
java = @compressor_options[:java] || 'java'
|
data/lib/jammit/command_line.rb
CHANGED
@@ -16,7 +16,7 @@ Run jammit inside a Rails application to compresses all JS, CSS,
|
|
16
16
|
and JST according to config/assets.yml, saving the packaged
|
17
17
|
files and corresponding gzipped versions.
|
18
18
|
|
19
|
-
If you're using "
|
19
|
+
If you're using "embed_assets", and you wish to precompile the
|
20
20
|
MHTML stylesheet variants, you must specify the "base-url".
|
21
21
|
|
22
22
|
Options:
|
data/lib/jammit/compressor.rb
CHANGED
@@ -3,24 +3,33 @@ module Jammit
|
|
3
3
|
# Uses the YUI Compressor or Closure Compiler to compress JavaScript.
|
4
4
|
# Always uses YUI to compress CSS (Which means that Java must be installed.)
|
5
5
|
# Also knows how to create a concatenated JST file.
|
6
|
-
# If "
|
7
|
-
# all stylesheets, with all enabled
|
6
|
+
# If "embed_assets" is turned on, creates "mhtml" and "datauri" versions of
|
7
|
+
# all stylesheets, with all enabled assets inlined into the css.
|
8
8
|
class Compressor
|
9
9
|
|
10
|
-
# Mapping from extension to mime-type of all embeddable
|
11
|
-
|
10
|
+
# Mapping from extension to mime-type of all embeddable assets.
|
11
|
+
EMBED_MIME_TYPES = {
|
12
12
|
'.png' => 'image/png',
|
13
13
|
'.jpg' => 'image/jpeg',
|
14
14
|
'.jpeg' => 'image/jpeg',
|
15
15
|
'.gif' => 'image/gif',
|
16
16
|
'.tif' => 'image/tiff',
|
17
|
-
'.tiff' => 'image/tiff'
|
17
|
+
'.tiff' => 'image/tiff',
|
18
|
+
'.ttf' => 'font/truetype',
|
19
|
+
'.otf' => 'font/opentype'
|
18
20
|
}
|
19
21
|
|
22
|
+
# Font extensions for which we allow embedding:
|
23
|
+
EMBED_EXTS = EMBED_MIME_TYPES.keys
|
24
|
+
EMBED_FONTS = ['.ttf', '.otf']
|
20
25
|
|
21
|
-
|
22
|
-
|
23
|
-
|
26
|
+
# Maximum size for embeddable images (an IE8 limitation).
|
27
|
+
MAX_IMAGE_SIZE = 32.kilobytes
|
28
|
+
|
29
|
+
# CSS asset-embedding regexes for URL rewriting.
|
30
|
+
EMBED_DETECTOR = /url\(['"]?([^\s)]+\.[a-z]+)(\?\d+)?['"]?\)/
|
31
|
+
EMBEDDABLE = /[\A\/]embed\//
|
32
|
+
EMBED_REPLACER = /url\(__EMBED__([^\s)]+)(\?\d+)?\)/
|
24
33
|
|
25
34
|
# MHTML file constants.
|
26
35
|
MHTML_START = "/*\r\nContent-Type: multipart/related; boundary=\"JAMMIT_MHTML_SEPARATOR\"\r\n\r\n"
|
@@ -28,7 +37,7 @@ module Jammit
|
|
28
37
|
MHTML_END = "*/\r\n"
|
29
38
|
|
30
39
|
# JST file constants.
|
31
|
-
JST_START = "(function(){
|
40
|
+
JST_START = "(function(){"
|
32
41
|
JST_END = "})();"
|
33
42
|
|
34
43
|
COMPRESSORS = {
|
@@ -45,24 +54,28 @@ module Jammit
|
|
45
54
|
# the "yui-compressor" gem, or the internal Closure Compiler from the
|
46
55
|
# "closure-compiler" gem.
|
47
56
|
def initialize
|
48
|
-
@css_compressor = YUI::CssCompressor.new(Jammit.css_compressor_options)
|
49
|
-
flavor = Jammit.javascript_compressor
|
50
|
-
@options = DEFAULT_OPTIONS[flavor].merge(Jammit.compressor_options)
|
57
|
+
@css_compressor = YUI::CssCompressor.new(Jammit.css_compressor_options || {})
|
58
|
+
flavor = Jammit.javascript_compressor || Jammit::DEFAULT_COMPRESSOR
|
59
|
+
@options = DEFAULT_OPTIONS[flavor].merge(Jammit.compressor_options || {})
|
51
60
|
@js_compressor = COMPRESSORS[flavor].new(@options)
|
52
61
|
end
|
53
62
|
|
54
63
|
# Concatenate together a list of JavaScript paths, and pass them through the
|
55
|
-
# YUI Compressor (with munging enabled).
|
64
|
+
# YUI Compressor (with munging enabled). JST can optionally be included.
|
56
65
|
def compress_js(paths)
|
57
|
-
|
66
|
+
if (jst_paths = paths.grep(JST_EXT)).empty?
|
67
|
+
js = concatenate(paths)
|
68
|
+
else
|
69
|
+
js = concatenate(paths - jst_paths) + compile_jst(jst_paths)
|
70
|
+
end
|
58
71
|
Jammit.compress_assets ? @js_compressor.compress(js) : js
|
59
72
|
end
|
60
73
|
|
61
74
|
# Concatenate and compress a list of CSS stylesheets. When compressing a
|
62
75
|
# :datauri or :mhtml variant, post-processes the result to embed
|
63
|
-
# referenced
|
76
|
+
# referenced assets.
|
64
77
|
def compress_css(paths, variant=nil, asset_url=nil)
|
65
|
-
css =
|
78
|
+
css = concatenate_and_tag_assets(paths, variant)
|
66
79
|
css = @css_compressor.compress(css) if Jammit.compress_assets
|
67
80
|
case variant
|
68
81
|
when nil then return css
|
@@ -73,52 +86,54 @@ module Jammit
|
|
73
86
|
end
|
74
87
|
|
75
88
|
# Compiles a single JST file by writing out a javascript that adds
|
76
|
-
# template properties to a top-level
|
89
|
+
# template properties to a top-level template namespace object. Adds a
|
77
90
|
# JST-compilation function to the top of the package, unless you've
|
78
91
|
# specified your own preferred function, or turned it off.
|
79
92
|
# JST templates are named with the basename of their file.
|
80
93
|
def compile_jst(paths)
|
81
|
-
|
94
|
+
namespace = Jammit.template_namespace
|
95
|
+
compiled = paths.grep(JST_EXT).map do |path|
|
82
96
|
template_name = File.basename(path, File.extname(path))
|
83
97
|
contents = File.read(path).gsub(/\n/, '').gsub("'", '\\\\\'')
|
84
|
-
"
|
98
|
+
"#{namespace}.#{template_name} = #{Jammit.template_function}('#{contents}');"
|
85
99
|
end
|
86
100
|
compiler = Jammit.include_jst_script ? File.read(DEFAULT_JST_SCRIPT) : '';
|
87
|
-
|
101
|
+
setup_namespace = "#{namespace} = #{namespace} || {};"
|
102
|
+
[JST_START, setup_namespace, compiler, compiled, JST_END].flatten.join("\n")
|
88
103
|
end
|
89
104
|
|
90
105
|
|
91
106
|
private
|
92
107
|
|
93
|
-
# In order to support embedded
|
108
|
+
# In order to support embedded assets from relative paths, we need to
|
94
109
|
# expand the paths before contatenating the CSS together and losing the
|
95
|
-
# location of the original stylesheet path. Validate the
|
110
|
+
# location of the original stylesheet path. Validate the assets while we're
|
96
111
|
# at it.
|
97
|
-
def
|
112
|
+
def concatenate_and_tag_assets(paths, variant=nil)
|
98
113
|
stylesheets = [paths].flatten.map do |css_path|
|
99
|
-
File.read(css_path).gsub(
|
114
|
+
File.read(css_path).gsub(EMBED_DETECTOR) do |url|
|
100
115
|
ipath, cpath = Pathname.new($1), Pathname.new(File.expand_path(css_path))
|
101
116
|
is_url = URI.parse($1).absolute?
|
102
|
-
is_url ? url : "url(#{
|
117
|
+
is_url ? url : "url(#{rewrite_asset_path(ipath, cpath, variant)})"
|
103
118
|
end
|
104
119
|
end
|
105
120
|
stylesheets.join("\n")
|
106
121
|
end
|
107
122
|
|
108
|
-
# Re-write all enabled
|
109
|
-
# Data-URI Base-64 encoded
|
123
|
+
# Re-write all enabled asset URLs in a stylesheet with their corresponding
|
124
|
+
# Data-URI Base-64 encoded asset contents.
|
110
125
|
def with_data_uris(css)
|
111
|
-
css.gsub(
|
112
|
-
"url(\"data:#{mime_type($1)};base64,#{encoded_contents($1)}\")"
|
126
|
+
css.gsub(EMBED_REPLACER) do |url|
|
127
|
+
"url(\"data:#{mime_type($1)};charset=utf-8;base64,#{encoded_contents($1)}\")"
|
113
128
|
end
|
114
129
|
end
|
115
130
|
|
116
|
-
# Re-write all enabled
|
131
|
+
# Re-write all enabled asset URLs in a stylesheet with the MHTML equivalent.
|
117
132
|
# The newlines ("\r\n") in the following method are critical. Without them
|
118
133
|
# your MHTML will look identical, but won't work.
|
119
134
|
def with_mhtml(css, asset_url)
|
120
135
|
paths, index = {}, 0
|
121
|
-
css = css.gsub(
|
136
|
+
css = css.gsub(EMBED_REPLACER) do |url|
|
122
137
|
i = paths[$1] ||= "#{index += 1}-#{File.basename($1)}"
|
123
138
|
"url(mhtml:#{asset_url}!#{i})"
|
124
139
|
end
|
@@ -129,45 +144,51 @@ module Jammit
|
|
129
144
|
[MHTML_START, mhtml, MHTML_END, css].flatten.join('')
|
130
145
|
end
|
131
146
|
|
132
|
-
# Return a rewritten
|
147
|
+
# Return a rewritten asset URL for a new stylesheet -- the asset should
|
133
148
|
# be tagged for embedding if embeddable, and referenced at the correct level
|
134
149
|
# if relative.
|
135
|
-
def
|
136
|
-
public_path = absolute_path(
|
137
|
-
return "__EMBED__#{public_path}" if
|
138
|
-
|
150
|
+
def rewrite_asset_path(asset_path, css_path, variant)
|
151
|
+
public_path = absolute_path(asset_path, css_path)
|
152
|
+
return "__EMBED__#{public_path}" if embeddable?(public_path, variant)
|
153
|
+
asset_path.absolute? ? asset_path.to_s : relative_path(public_path)
|
139
154
|
end
|
140
155
|
|
141
|
-
# Get the site-absolute public path for an
|
156
|
+
# Get the site-absolute public path for an asset file path that may or may
|
142
157
|
# not be relative, given the path of the stylesheet that contains it.
|
143
|
-
def absolute_path(
|
144
|
-
(
|
145
|
-
Pathname.new(File.join(PUBLIC_ROOT,
|
146
|
-
css_pathname.dirname +
|
158
|
+
def absolute_path(asset_pathname, css_pathname)
|
159
|
+
(asset_pathname.absolute? ?
|
160
|
+
Pathname.new(File.join(PUBLIC_ROOT, asset_pathname)) :
|
161
|
+
css_pathname.dirname + asset_pathname).cleanpath
|
147
162
|
end
|
148
163
|
|
149
|
-
# CSS
|
164
|
+
# CSS assets that are referenced by relative paths, and are *not* being
|
150
165
|
# embedded, must be rewritten relative to the newly-merged stylesheet path.
|
151
166
|
def relative_path(absolute_path)
|
152
167
|
File.join('../', absolute_path.sub(PUBLIC_ROOT, ''))
|
153
168
|
end
|
154
169
|
|
155
|
-
# An
|
170
|
+
# An asset is valid for embedding if it exists, is less than 32K, and is
|
156
171
|
# stored somewhere inside of a folder named "embed".
|
157
172
|
# IE does not support Data-URIs larger than 32K, and you probably shouldn't
|
158
|
-
# be embedding
|
159
|
-
def embeddable?(
|
160
|
-
|
173
|
+
# be embedding assets that large in any case.
|
174
|
+
def embeddable?(asset_path, variant)
|
175
|
+
font = EMBED_FONTS.include?(asset_path.extname)
|
176
|
+
return false unless variant
|
177
|
+
return false unless asset_path.to_s.match(EMBEDDABLE) && asset_path.exist?
|
178
|
+
return false unless EMBED_EXTS.include?(asset_path.extname)
|
179
|
+
return false unless font || asset_path.size < MAX_IMAGE_SIZE
|
180
|
+
return false if font && variant == :mhtml
|
181
|
+
true
|
161
182
|
end
|
162
183
|
|
163
|
-
# Return the Base64-encoded contents of an
|
164
|
-
def encoded_contents(
|
165
|
-
Base64.encode64(File.read(
|
184
|
+
# Return the Base64-encoded contents of an asset on a single line.
|
185
|
+
def encoded_contents(asset_path)
|
186
|
+
Base64.encode64(File.read(asset_path)).gsub(/\n/, '')
|
166
187
|
end
|
167
188
|
|
168
|
-
# Grab the mime-type of an
|
169
|
-
def mime_type(
|
170
|
-
|
189
|
+
# Grab the mime-type of an asset, by filename.
|
190
|
+
def mime_type(asset_path)
|
191
|
+
EMBED_MIME_TYPES[File.extname(asset_path)]
|
171
192
|
end
|
172
193
|
|
173
194
|
# Concatenate together a list of asset files.
|
data/lib/jammit/controller.rb
CHANGED
@@ -63,9 +63,9 @@ module Jammit
|
|
63
63
|
pack = params[:package]
|
64
64
|
@extension = params[:extension].to_sym
|
65
65
|
raise PackageNotFound unless VALID_FORMATS.include?(@extension)
|
66
|
-
if Jammit.
|
66
|
+
if Jammit.embed_assets
|
67
67
|
suffix_match = pack.match(SUFFIX_STRIPPER)
|
68
|
-
@variant = Jammit.
|
68
|
+
@variant = Jammit.embed_assets && suffix_match && suffix_match[1].to_sym
|
69
69
|
pack.sub!(SUFFIX_STRIPPER, '')
|
70
70
|
end
|
71
71
|
@package = pack.to_sym
|
data/lib/jammit/dependencies.rb
CHANGED
data/lib/jammit/helper.rb
CHANGED
@@ -11,13 +11,14 @@ module Jammit
|
|
11
11
|
MHTML_START = "<!--[if lte IE 7]>"
|
12
12
|
MHTML_END = "<![endif]-->"
|
13
13
|
|
14
|
-
# If
|
14
|
+
# If embed_assets is turned on, writes out links to the Data-URI and MHTML
|
15
15
|
# versions of the stylesheet package, otherwise the package is regular
|
16
16
|
# compressed CSS, and in development the stylesheet URLs are passed verbatim.
|
17
17
|
def include_stylesheets(*packages)
|
18
18
|
options = packages.extract_options!
|
19
19
|
return individual_stylesheets(packages, options) unless Jammit.package_assets
|
20
|
-
|
20
|
+
disabled = (options.delete(:embed_assets) == false) || (options.delete(:embed_images) == false)
|
21
|
+
return packaged_stylesheets(packages, options) if disabled || !Jammit.embed_assets
|
21
22
|
return embedded_image_stylesheets(packages, options)
|
22
23
|
end
|
23
24
|
|
data/lib/jammit/jst.js
CHANGED
@@ -1 +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("
|
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, " ").replace(/'(?=[^%]*%>)/g,"\t").split("'").join("\\'").split("\t").join("'").replace(/<%=(.+?)%>/g,"',$1,'").split("<%").join("');").split("%>").join("p.push('")+"');}return p.join('');"); return fn;};
|
data/lib/jammit/packager.rb
CHANGED
@@ -42,7 +42,7 @@ module Jammit
|
|
42
42
|
cacheable(:jst, output_dir).each {|p| cache(p, 'jst', pack_templates(p), output_dir) }
|
43
43
|
cacheable(:css, output_dir).each do |p|
|
44
44
|
cache(p, 'css', pack_stylesheets(p), output_dir)
|
45
|
-
if Jammit.
|
45
|
+
if Jammit.embed_assets
|
46
46
|
cache(p, 'css', pack_stylesheets(p, :datauri), output_dir, :datauri)
|
47
47
|
if Jammit.mhtml_enabled && base_url
|
48
48
|
mtime = Time.now
|
@@ -93,6 +93,7 @@ module Jammit
|
|
93
93
|
# package has gone missing.
|
94
94
|
def package_for(package, extension)
|
95
95
|
pack = @packages[extension] && @packages[extension][package]
|
96
|
+
pack ||= @packages[:js] && @packages[:js][package] if extension == :jst
|
96
97
|
pack || not_found(package, extension)
|
97
98
|
end
|
98
99
|
|
@@ -126,7 +127,12 @@ module Jammit
|
|
126
127
|
packages[name] = {}
|
127
128
|
paths = globs.map {|glob| glob_files(glob) }.flatten.uniq
|
128
129
|
packages[name][:paths] = paths
|
129
|
-
|
130
|
+
if !paths.grep(JS_EXT).empty? && !paths.grep(JST_EXT).empty?
|
131
|
+
packages[name][:urls] = paths.grep(JS_EXT).map {|path| path.sub(PATH_TO_URL, '') }
|
132
|
+
packages[name][:urls] += [Jammit.asset_url(name, :jst)]
|
133
|
+
else
|
134
|
+
packages[name][:urls] = paths.map {|path| path.sub(PATH_TO_URL, '') }
|
135
|
+
end
|
130
136
|
end
|
131
137
|
packages
|
132
138
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jammit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Ashkenas
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-01-
|
12
|
+
date: 2009-01-26 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|