jammit-ebtd 0.6.6
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README +24 -0
- data/bin/jammit +7 -0
- data/jammit-ebtd.gemspec +27 -0
- data/lib/jammit-ebtd.rb +241 -0
- data/lib/jammit/command_line.rb +84 -0
- data/lib/jammit/compressor.rb +253 -0
- data/lib/jammit/controller.rb +97 -0
- data/lib/jammit/cssmin_compressor.rb +10 -0
- data/lib/jammit/dependencies.rb +59 -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/packager.rb +201 -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
- metadata +105 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2009-2011 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,24 @@
|
|
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
|
+
Installation:
|
14
|
+
gem install jammit
|
15
|
+
|
16
|
+
For documentation, usage, and examples, see:
|
17
|
+
http://documentcloud.github.com/jammit/
|
18
|
+
|
19
|
+
To suggest a feature or report a bug:
|
20
|
+
http://github.com/documentcloud/jammit/issues/
|
21
|
+
|
22
|
+
For internal source docs, see:
|
23
|
+
http://documentcloud.github.com/jammit/doc/
|
24
|
+
|
data/bin/jammit
ADDED
data/jammit-ebtd.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'jammit-ebtd'
|
3
|
+
s.date = '2012-09-04'
|
4
|
+
s.version = '0.6.6'
|
5
|
+
|
6
|
+
s.homepage = "https://github.com/m16a1/jammit-ebtd"
|
7
|
+
s.summary = "Industrial Strength Asset Packaging for Rails"
|
8
|
+
s.description = <<-EOS
|
9
|
+
Jammit-Ebtd is a fork of the Jammit asset packager, modified for ebaytoday.ru
|
10
|
+
EOS
|
11
|
+
|
12
|
+
s.authors = ['Jeremy Ashkenas']
|
13
|
+
s.email = 'jeremy@documentcloud.org'
|
14
|
+
|
15
|
+
s.require_paths = ['lib']
|
16
|
+
|
17
|
+
s.extra_rdoc_files = ['README']
|
18
|
+
s.rdoc_options << '--title' << 'Jammit' <<
|
19
|
+
'--exclude' << 'test' <<
|
20
|
+
'--main' << 'README' <<
|
21
|
+
'--all'
|
22
|
+
|
23
|
+
s.add_dependency 'cssmin', ['>= 1.0.2']
|
24
|
+
s.add_dependency 'jsmin', ['>= 1.0.1']
|
25
|
+
|
26
|
+
s.files = Dir['lib/**/*', 'bin/*', 'jammit-ebtd.gemspec', 'LICENSE', 'README']
|
27
|
+
end
|
data/lib/jammit-ebtd.rb
ADDED
@@ -0,0 +1,241 @@
|
|
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.6.6"
|
8
|
+
|
9
|
+
ROOT = File.expand_path(File.dirname(__FILE__) + '/..')
|
10
|
+
|
11
|
+
ASSET_ROOT = File.expand_path((defined?(Rails) && Rails.root.to_s.length > 0) ? Rails.root : ENV['RAILS_ROOT'] || ".") unless defined?(ASSET_ROOT)
|
12
|
+
|
13
|
+
DEFAULT_PUBLIC_ROOT = (defined?(Rails) && Rails.public_path.to_s.length > 0) ? Rails.public_path : File.join(ASSET_ROOT, 'public') unless defined?(PUBLIC_ROOT)
|
14
|
+
|
15
|
+
DEFAULT_CONFIG_PATH = File.join(ASSET_ROOT, 'config', 'assets.yml')
|
16
|
+
|
17
|
+
DEFAULT_PACKAGE_PATH = "assets"
|
18
|
+
|
19
|
+
DEFAULT_JST_SCRIPT = File.join(ROOT, 'lib/jammit/jst.js')
|
20
|
+
|
21
|
+
DEFAULT_JST_COMPILER = "template"
|
22
|
+
|
23
|
+
DEFAULT_JST_NAMESPACE = "window.JST"
|
24
|
+
|
25
|
+
JAVASCRIPT_COMPRESSORS = [:jsmin, :yui, :closure, :uglifier]
|
26
|
+
|
27
|
+
DEFAULT_JAVASCRIPT_COMPRESSOR = :jsmin
|
28
|
+
|
29
|
+
CSS_COMPRESSORS = [:cssmin, :yui, :sass]
|
30
|
+
|
31
|
+
DEFAULT_CSS_COMPRESSOR = :cssmin
|
32
|
+
|
33
|
+
# Extension matchers for JavaScript and JST, which need to be disambiguated.
|
34
|
+
JS_EXTENSION = /\.js\Z/
|
35
|
+
DEFAULT_JST_EXTENSION = "jst"
|
36
|
+
|
37
|
+
# Jammit raises a @PackageNotFound@ exception when a non-existent package is
|
38
|
+
# requested by a browser -- rendering a 404.
|
39
|
+
class PackageNotFound < NameError; end
|
40
|
+
|
41
|
+
# Jammit raises a MissingConfiguration exception when you try to load the
|
42
|
+
# configuration of an assets.yml file that doesn't exist, or are missing
|
43
|
+
# a piece of required configuration.
|
44
|
+
class MissingConfiguration < NameError; end
|
45
|
+
|
46
|
+
# Jammit raises an OutputNotWritable exception if the output directory for
|
47
|
+
# cached packages is locked.
|
48
|
+
class OutputNotWritable < StandardError; end
|
49
|
+
|
50
|
+
# Jammit raises a DeprecationError if you try to use an outdated feature.
|
51
|
+
class DeprecationError < StandardError; end
|
52
|
+
|
53
|
+
class << self
|
54
|
+
attr_reader :configuration, :template_function, :template_namespace,
|
55
|
+
:embed_assets, :package_assets, :compress_assets, :gzip_assets,
|
56
|
+
:package_path, :mhtml_enabled, :include_jst_script, :config_path,
|
57
|
+
:javascript_compressor, :compressor_options, :css_compressor,
|
58
|
+
:css_compressor_options, :template_extension,
|
59
|
+
:template_extension_matcher, :allow_debugging,
|
60
|
+
:rewrite_relative_paths, :public_root
|
61
|
+
attr_accessor :javascript_compressors, :css_compressors
|
62
|
+
end
|
63
|
+
|
64
|
+
# The minimal required configuration.
|
65
|
+
@configuration = {}
|
66
|
+
@public_root = DEFAULT_PUBLIC_ROOT
|
67
|
+
@package_path = DEFAULT_PACKAGE_PATH
|
68
|
+
|
69
|
+
@javascript_compressors = JAVASCRIPT_COMPRESSORS
|
70
|
+
@css_compressors = CSS_COMPRESSORS
|
71
|
+
|
72
|
+
# Load the complete asset configuration from the specified @config_path@.
|
73
|
+
# If we're loading softly, don't let missing configuration error out.
|
74
|
+
def self.load_configuration(config_path, soft=false)
|
75
|
+
exists = config_path && File.exists?(config_path)
|
76
|
+
return false if soft && !exists
|
77
|
+
raise MissingConfiguration, "could not find the \"#{config_path}\" configuration file" unless exists
|
78
|
+
conf = YAML.load(ERB.new(File.read(config_path)).result)
|
79
|
+
|
80
|
+
# Optionally overwrite configuration based on the environment.
|
81
|
+
rails_env = (defined?(Rails) ? ::Rails.env : ENV['RAILS_ENV'] || "development")
|
82
|
+
conf.merge! conf.delete rails_env if conf.has_key? rails_env
|
83
|
+
|
84
|
+
@config_path = config_path
|
85
|
+
@configuration = symbolize_keys(conf)
|
86
|
+
@package_path = conf[:package_path] || DEFAULT_PACKAGE_PATH
|
87
|
+
@embed_assets = conf[:embed_assets] || conf[:embed_images]
|
88
|
+
@compress_assets = !(conf[:compress_assets] == false)
|
89
|
+
@rewrite_relative_paths = !(conf[:rewrite_relative_paths] == false)
|
90
|
+
@gzip_assets = !(conf[:gzip_assets] == false)
|
91
|
+
@allow_debugging = !(conf[:allow_debugging] == false)
|
92
|
+
@mhtml_enabled = @embed_assets && @embed_assets != "datauri"
|
93
|
+
@compressor_options = symbolize_keys(conf[:compressor_options] || {})
|
94
|
+
@css_compressor_options = symbolize_keys(conf[:css_compressor_options] || {})
|
95
|
+
set_javascript_compressor(conf[:javascript_compressor])
|
96
|
+
set_css_compressor(conf[:css_compressor])
|
97
|
+
set_package_assets(conf[:package_assets])
|
98
|
+
set_template_function(conf[:template_function])
|
99
|
+
set_template_namespace(conf[:template_namespace])
|
100
|
+
set_template_extension(conf[:template_extension])
|
101
|
+
set_public_root(conf[:public_root]) if conf[:public_root]
|
102
|
+
symbolize_keys(conf[:stylesheets]) if conf[:stylesheets]
|
103
|
+
symbolize_keys(conf[:javascripts]) if conf[:javascripts]
|
104
|
+
check_for_deprecations
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.set_conf_dir(dir)
|
109
|
+
remove_const 'DEFAULT_CONFIG_PATH'
|
110
|
+
const_set 'DEFAULT_CONFIG_PATH', File.join(ASSET_ROOT, dir, 'assets.yml')
|
111
|
+
end
|
112
|
+
|
113
|
+
# Force a reload by resetting the Packager and reloading the configuration.
|
114
|
+
# In development, this will be called as a before_filter before every request.
|
115
|
+
def self.reload!
|
116
|
+
Thread.current[:jammit_packager] = nil
|
117
|
+
load_configuration(@config_path)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Keep a global (thread-local) reference to a @Jammit::Packager@, to avoid
|
121
|
+
# recomputing asset lists unnecessarily.
|
122
|
+
def self.packager
|
123
|
+
Thread.current[:jammit_packager] ||= Packager.new
|
124
|
+
end
|
125
|
+
|
126
|
+
# Generate the base filename for a version of a given package.
|
127
|
+
def self.filename(package, extension, suffix=nil)
|
128
|
+
suffix_part = suffix ? "-#{suffix}" : ''
|
129
|
+
"#{package}#{suffix_part}.#{extension}"
|
130
|
+
end
|
131
|
+
|
132
|
+
# Generates the server-absolute URL to an asset package.
|
133
|
+
def self.asset_url(package, extension, suffix=nil, mtime=nil)
|
134
|
+
timestamp = mtime ? "?#{mtime.to_i}" : ''
|
135
|
+
"/#{package_path}/#{filename(package, extension, suffix)}#{timestamp}"
|
136
|
+
end
|
137
|
+
|
138
|
+
# Convenience method for packaging up Jammit, using the default options.
|
139
|
+
def self.package!(options={})
|
140
|
+
options = {
|
141
|
+
:config_path => Jammit::DEFAULT_CONFIG_PATH,
|
142
|
+
:output_folder => nil,
|
143
|
+
:base_url => nil,
|
144
|
+
:public_root => nil,
|
145
|
+
:force => false
|
146
|
+
}.merge(options)
|
147
|
+
load_configuration(options[:config_path])
|
148
|
+
set_public_root(options[:public_root]) if options[:public_root]
|
149
|
+
packager.force = options[:force]
|
150
|
+
packager.package_names = options[:package_names]
|
151
|
+
packager.precache_all(options[:output_folder], options[:base_url])
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
# Allows command-line definition of `PUBLIC_ROOT`, for those using Jammit
|
157
|
+
# outside of Rails.
|
158
|
+
def self.set_public_root(public_root=nil)
|
159
|
+
@public_root = public_root if public_root
|
160
|
+
end
|
161
|
+
|
162
|
+
# Ensure that the JavaScript compressor is a valid choice.
|
163
|
+
def self.set_javascript_compressor(value)
|
164
|
+
value = value && value.to_sym
|
165
|
+
@javascript_compressor = javascript_compressors.include?(value) ? value : DEFAULT_JAVASCRIPT_COMPRESSOR
|
166
|
+
end
|
167
|
+
|
168
|
+
# Ensure that the CSS compressor is a valid choice.
|
169
|
+
def self.set_css_compressor(value)
|
170
|
+
value = value && value.to_sym
|
171
|
+
@css_compressor = css_compressors.include?(value) ? value : DEFAULT_CSS_COMPRESSOR
|
172
|
+
end
|
173
|
+
|
174
|
+
# Turn asset packaging on or off, depending on configuration and environment.
|
175
|
+
def self.set_package_assets(value)
|
176
|
+
package_env = !defined?(Rails) || (!Rails.env.development? && !Rails.env.test?)
|
177
|
+
@package_assets = value == true || value.nil? ? package_env :
|
178
|
+
value == 'always' ? true : false
|
179
|
+
end
|
180
|
+
|
181
|
+
# Assign the JST template function, unless explicitly turned off.
|
182
|
+
def self.set_template_function(value)
|
183
|
+
@template_function = value == true || value.nil? ? DEFAULT_JST_COMPILER :
|
184
|
+
value == false ? '' : value
|
185
|
+
@include_jst_script = @template_function == DEFAULT_JST_COMPILER
|
186
|
+
end
|
187
|
+
|
188
|
+
# Set the root JS object in which to stash all compiled JST.
|
189
|
+
def self.set_template_namespace(value)
|
190
|
+
@template_namespace = value == true || value.nil? ? DEFAULT_JST_NAMESPACE : value.to_s
|
191
|
+
end
|
192
|
+
|
193
|
+
# Set the extension for JS templates.
|
194
|
+
def self.set_template_extension(value)
|
195
|
+
@template_extension = (value == true || value.nil? ? DEFAULT_JST_EXTENSION : value.to_s).gsub(/\A\.?(.*)\Z/, '\1')
|
196
|
+
@template_extension_matcher = /\.#{Regexp.escape(@template_extension)}\Z/
|
197
|
+
end
|
198
|
+
|
199
|
+
# The YUI Compressor requires Java > 1.4, and Closure requires Java > 1.6.
|
200
|
+
def self.check_java_version
|
201
|
+
return true if @checked_java_version
|
202
|
+
java = @compressor_options[:java] || 'java'
|
203
|
+
@css_compressor_options[:java] ||= java if @compressor_options[:java]
|
204
|
+
version = (`#{java} -version 2>&1`)[/\d+\.\d+/]
|
205
|
+
disable_compression if !version ||
|
206
|
+
(@javascript_compressor == :closure && version < '1.6') ||
|
207
|
+
(@javascript_compressor == :yui && version < '1.4')
|
208
|
+
@checked_java_version = true
|
209
|
+
end
|
210
|
+
|
211
|
+
# If we don't have a working Java VM, then disable asset compression and
|
212
|
+
# complain loudly.
|
213
|
+
def self.disable_compression
|
214
|
+
@compress_assets = false
|
215
|
+
warn("Asset compression disabled -- Java unavailable.")
|
216
|
+
end
|
217
|
+
|
218
|
+
# Jammit 0.5+ no longer supports separate template packages.
|
219
|
+
def self.check_for_deprecations
|
220
|
+
if @configuration[:templates]
|
221
|
+
raise DeprecationError, "Jammit 0.5+ no longer supports separate packages for templates.\nPlease fold your templates into the appropriate 'javascripts' package instead."
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def self.warn(message)
|
226
|
+
message = "Jammit Warning: #{message}"
|
227
|
+
$stderr.puts message
|
228
|
+
end
|
229
|
+
|
230
|
+
# Clone of active_support's symbolize_keys, so that we don't have to depend
|
231
|
+
# on active_support in any fashion. Converts a hash's keys to all symbols.
|
232
|
+
def self.symbolize_keys(hash)
|
233
|
+
hash.keys.each do |key|
|
234
|
+
hash[(key.to_sym rescue key) || key] = hash.delete(key)
|
235
|
+
end
|
236
|
+
hash
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
require 'jammit/dependencies'
|
@@ -0,0 +1,84 @@
|
|
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_assets", 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.package!(@options)
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Make sure that we have a readable configuration file. The @jammit@
|
36
|
+
# command can't run without one.
|
37
|
+
def ensure_configuration_file
|
38
|
+
config = @options[:config_path]
|
39
|
+
return true if File.exists?(config) && File.readable?(config)
|
40
|
+
puts "Could not find the asset configuration file \"#{config}\""
|
41
|
+
exit(1)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Uses @OptionParser@ to grab the options: *--output*, *--config*, and
|
45
|
+
# *--base-url*...
|
46
|
+
def parse_options
|
47
|
+
@options = {
|
48
|
+
:config_path => Jammit::DEFAULT_CONFIG_PATH,
|
49
|
+
:output_folder => nil,
|
50
|
+
:base_url => nil,
|
51
|
+
:force => false
|
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('-f', '--force', 'force a rebuild of all assets') do |force|
|
64
|
+
@options[:force] = force
|
65
|
+
end
|
66
|
+
opts.on('-p', '--packages LIST', 'list of packages to build (ex: "core,ui", default: all)') do |package_names|
|
67
|
+
@options[:package_names] = package_names.split(/,\s*/).map {|n| n.to_sym }
|
68
|
+
end
|
69
|
+
opts.on('-P', '--public-root PATH', 'path to public assets (default: "public")') do |public_root|
|
70
|
+
puts "Option for PUBLIC_ROOT"
|
71
|
+
@options[:public_root] = public_root
|
72
|
+
end
|
73
|
+
opts.on_tail('-v', '--version', 'display Jammit version') do
|
74
|
+
puts "Jammit version #{Jammit::VERSION}"
|
75
|
+
exit
|
76
|
+
end
|
77
|
+
end
|
78
|
+
@option_parser.banner = BANNER
|
79
|
+
@option_parser.parse!(ARGV)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,253 @@
|
|
1
|
+
module Jammit
|
2
|
+
|
3
|
+
# Uses the YUI Compressor or Closure Compiler to compress JavaScript.
|
4
|
+
# Always uses YUI to compress CSS (Which means that Java must be installed.)
|
5
|
+
# Also knows how to create a concatenated JST file.
|
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
|
+
class Compressor
|
9
|
+
|
10
|
+
# Mapping from extension to mime-type of all embeddable assets.
|
11
|
+
EMBED_MIME_TYPES = {
|
12
|
+
'.png' => 'image/png',
|
13
|
+
'.jpg' => 'image/jpeg',
|
14
|
+
'.jpeg' => 'image/jpeg',
|
15
|
+
'.gif' => 'image/gif',
|
16
|
+
'.tif' => 'image/tiff',
|
17
|
+
'.tiff' => 'image/tiff',
|
18
|
+
'.ttf' => 'application/x-font-ttf',
|
19
|
+
'.otf' => 'font/opentype',
|
20
|
+
'.woff' => 'application/x-font-woff'
|
21
|
+
}
|
22
|
+
|
23
|
+
# Font extensions for which we allow embedding:
|
24
|
+
EMBED_EXTS = EMBED_MIME_TYPES.keys
|
25
|
+
EMBED_FONTS = ['.ttf', '.otf', '.woff']
|
26
|
+
|
27
|
+
# (32k - padding) maximum length for data-uri assets (an IE8 limitation).
|
28
|
+
MAX_IMAGE_SIZE = 32700
|
29
|
+
|
30
|
+
# CSS asset-embedding regexes for URL rewriting.
|
31
|
+
EMBED_DETECTOR = /url\(['"]?([^\s)]+\.[a-z]+)(\?\d+)?['"]?\)/
|
32
|
+
EMBEDDABLE = /[\A\/]embed\//
|
33
|
+
EMBED_REPLACER = /url\(__EMBED__(.+?)(\?\d+)?\)/
|
34
|
+
|
35
|
+
# MHTML file constants.
|
36
|
+
MHTML_START = "/*\r\nContent-Type: multipart/related; boundary=\"MHTML_MARK\"\r\n\r\n"
|
37
|
+
MHTML_SEPARATOR = "--MHTML_MARK\r\n"
|
38
|
+
MHTML_END = "\r\n--MHTML_MARK--\r\n*/\r\n"
|
39
|
+
|
40
|
+
# JST file constants.
|
41
|
+
JST_START = "(function(){"
|
42
|
+
JST_END = "})();"
|
43
|
+
|
44
|
+
JAVASCRIPT_COMPRESSORS = {
|
45
|
+
:jsmin => Jammit.javascript_compressors.include?(:jsmin) ? Jammit::JsminCompressor : nil,
|
46
|
+
:yui => Jammit.javascript_compressors.include?(:yui) ? YUI::JavaScriptCompressor : nil,
|
47
|
+
:closure => Jammit.javascript_compressors.include?(:closure) ? Closure::Compiler : nil,
|
48
|
+
:uglifier => Jammit.javascript_compressors.include?(:uglifier) ? Jammit::Uglifier : nil
|
49
|
+
}
|
50
|
+
|
51
|
+
CSS_COMPRESSORS = {
|
52
|
+
:cssmin => Jammit.css_compressors.include?(:cssmin) ? Jammit::CssminCompressor : nil,
|
53
|
+
:yui => Jammit.css_compressors.include?(:yui) ? YUI::CssCompressor : nil,
|
54
|
+
:sass => Jammit.css_compressors.include?(:sass) ? Jammit::SassCompressor : nil
|
55
|
+
}
|
56
|
+
|
57
|
+
JAVASCRIPT_DEFAULT_OPTIONS = {
|
58
|
+
:jsmin => {},
|
59
|
+
:yui => {:munge => true},
|
60
|
+
:closure => {},
|
61
|
+
:uglifier => {:copyright => false}
|
62
|
+
}
|
63
|
+
|
64
|
+
# CSS compression can be provided with YUI Compressor or sass. JS
|
65
|
+
# compression can be provided with YUI Compressor, Google Closure
|
66
|
+
# Compiler or UglifyJS.
|
67
|
+
def initialize
|
68
|
+
if Jammit.javascript_compressors.include?(:yui) || Jammit.javascript_compressors.include?(:closure) || Jammit.css_compressors.include?(:yui)
|
69
|
+
Jammit.check_java_version
|
70
|
+
end
|
71
|
+
|
72
|
+
css_flavor = Jammit.css_compressor || Jammit::DEFAULT_CSS_COMPRESSOR
|
73
|
+
@css_compressor = CSS_COMPRESSORS[css_flavor].new(Jammit.css_compressor_options || {})
|
74
|
+
js_flavor = Jammit.javascript_compressor || Jammit::DEFAULT_JAVASCRIPT_COMPRESSOR
|
75
|
+
@options = JAVASCRIPT_DEFAULT_OPTIONS[js_flavor].merge(Jammit.compressor_options || {})
|
76
|
+
@js_compressor = JAVASCRIPT_COMPRESSORS[js_flavor].new(@options)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Concatenate together a list of JavaScript paths, and pass them through the
|
80
|
+
# YUI Compressor (with munging enabled). JST can optionally be included.
|
81
|
+
def compress_js(paths)
|
82
|
+
if (jst_paths = paths.grep(Jammit.template_extension_matcher)).empty?
|
83
|
+
js = concatenate(paths)
|
84
|
+
else
|
85
|
+
js = concatenate(paths - jst_paths) + compile_jst(jst_paths)
|
86
|
+
end
|
87
|
+
Jammit.compress_assets ? @js_compressor.compress(js) : js
|
88
|
+
end
|
89
|
+
|
90
|
+
# Concatenate and compress a list of CSS stylesheets. When compressing a
|
91
|
+
# :datauri or :mhtml variant, post-processes the result to embed
|
92
|
+
# referenced assets.
|
93
|
+
def compress_css(paths, variant=nil, asset_url=nil)
|
94
|
+
@asset_contents = {}
|
95
|
+
css = concatenate_and_tag_assets(paths, variant)
|
96
|
+
css = @css_compressor.compress(css) if Jammit.compress_assets
|
97
|
+
case variant
|
98
|
+
when nil then return css
|
99
|
+
when :datauri then return with_data_uris(css)
|
100
|
+
when :mhtml then return with_mhtml(css, asset_url)
|
101
|
+
else raise PackageNotFound, "\"#{variant}\" is not a valid stylesheet variant"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Compiles a single JST file by writing out a javascript that adds
|
106
|
+
# template properties to a top-level template namespace object. Adds a
|
107
|
+
# JST-compilation function to the top of the package, unless you've
|
108
|
+
# specified your own preferred function, or turned it off.
|
109
|
+
# JST templates are named with the basename of their file.
|
110
|
+
def compile_jst(paths)
|
111
|
+
namespace = Jammit.template_namespace
|
112
|
+
paths = paths.grep(Jammit.template_extension_matcher).sort
|
113
|
+
base_path = find_base_path(paths)
|
114
|
+
compiled = paths.map do |path|
|
115
|
+
contents = read_binary_file(path)
|
116
|
+
contents = contents.gsub(/\r?\n/, "\\n").gsub("'", '\\\\\'')
|
117
|
+
name = template_name(path, base_path)
|
118
|
+
"#{namespace}['#{name}'] = #{Jammit.template_function}('#{contents}');"
|
119
|
+
end
|
120
|
+
compiler = Jammit.include_jst_script ? read_binary_file(DEFAULT_JST_SCRIPT) : '';
|
121
|
+
setup_namespace = "#{namespace} = #{namespace} || {};"
|
122
|
+
[JST_START, setup_namespace, compiler, compiled, JST_END].flatten.join("\n")
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
# Given a set of paths, find a common prefix path.
|
129
|
+
def find_base_path(paths)
|
130
|
+
return nil if paths.length <= 1
|
131
|
+
paths.sort!
|
132
|
+
first = paths.first.split('/')
|
133
|
+
last = paths.last.split('/')
|
134
|
+
i = 0
|
135
|
+
while first[i] == last[i] && i <= first.length
|
136
|
+
i += 1
|
137
|
+
end
|
138
|
+
res = first.slice(0, i).join('/')
|
139
|
+
res.empty? ? nil : res
|
140
|
+
end
|
141
|
+
|
142
|
+
# Determine the name of a JS template. If there's a common base path, use
|
143
|
+
# the namespaced prefix. Otherwise, simply use the filename.
|
144
|
+
def template_name(path, base_path)
|
145
|
+
return File.basename(path, ".#{Jammit.template_extension}") unless base_path
|
146
|
+
path.gsub(/\A#{Regexp.escape(base_path)}\/(.*)\.#{Jammit.template_extension}\Z/, '\1')
|
147
|
+
end
|
148
|
+
|
149
|
+
# In order to support embedded assets from relative paths, we need to
|
150
|
+
# expand the paths before contatenating the CSS together and losing the
|
151
|
+
# location of the original stylesheet path. Validate the assets while we're
|
152
|
+
# at it.
|
153
|
+
def concatenate_and_tag_assets(paths, variant=nil)
|
154
|
+
stylesheets = [paths].flatten.map do |css_path|
|
155
|
+
contents = read_binary_file(css_path)
|
156
|
+
end
|
157
|
+
stylesheets.join("\n")
|
158
|
+
end
|
159
|
+
|
160
|
+
# Re-write all enabled asset URLs in a stylesheet with their corresponding
|
161
|
+
# Data-URI Base-64 encoded asset contents.
|
162
|
+
def with_data_uris(css)
|
163
|
+
css.gsub(EMBED_REPLACER) do |url|
|
164
|
+
"url(\"data:#{mime_type($1)};charset=utf-8;base64,#{encoded_contents($1)}\")"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Re-write all enabled asset URLs in a stylesheet with the MHTML equivalent.
|
169
|
+
# The newlines ("\r\n") in the following method are critical. Without them
|
170
|
+
# your MHTML will look identical, but won't work.
|
171
|
+
def with_mhtml(css, asset_url)
|
172
|
+
paths, index = {}, 0
|
173
|
+
css = css.gsub(EMBED_REPLACER) do |url|
|
174
|
+
i = paths[$1] ||= "#{index += 1}-#{File.basename($1)}"
|
175
|
+
"url(mhtml:#{asset_url}!#{i})"
|
176
|
+
end
|
177
|
+
mhtml = paths.sort.map do |path, identifier|
|
178
|
+
mime, contents = mime_type(path), encoded_contents(path)
|
179
|
+
[MHTML_SEPARATOR, "Content-Location: #{identifier}\r\n", "Content-Type: #{mime}\r\n", "Content-Transfer-Encoding: base64\r\n\r\n", contents, "\r\n"]
|
180
|
+
end
|
181
|
+
[MHTML_START, mhtml, MHTML_END, css].flatten.join('')
|
182
|
+
end
|
183
|
+
|
184
|
+
# Return a rewritten asset URL for a new stylesheet -- the asset should
|
185
|
+
# be tagged for embedding if embeddable, and referenced at the correct level
|
186
|
+
# if relative.
|
187
|
+
def construct_asset_path(asset_path, css_path, variant)
|
188
|
+
public_path = absolute_path(asset_path, css_path)
|
189
|
+
return "__EMBED__#{public_path}" if embeddable?(public_path, variant)
|
190
|
+
source = asset_path.absolute? || ! Jammit.rewrite_relative_paths ? asset_path.to_s : relative_path(public_path)
|
191
|
+
end
|
192
|
+
|
193
|
+
# Get the site-absolute public path for an asset file path that may or may
|
194
|
+
# not be relative, given the path of the stylesheet that contains it.
|
195
|
+
def absolute_path(asset_pathname, css_pathname)
|
196
|
+
(asset_pathname.absolute? ?
|
197
|
+
Pathname.new(File.join(Jammit.public_root, asset_pathname)) :
|
198
|
+
css_pathname.dirname + asset_pathname).cleanpath
|
199
|
+
end
|
200
|
+
|
201
|
+
# CSS assets that are referenced by relative paths, and are *not* being
|
202
|
+
# embedded, must be rewritten relative to the newly-merged stylesheet path.
|
203
|
+
def relative_path(absolute_path)
|
204
|
+
File.join('../', absolute_path.sub(Jammit.public_root, ''))
|
205
|
+
end
|
206
|
+
|
207
|
+
# Similar to the AssetTagHelper's method of the same name, this will
|
208
|
+
# determine the correct asset id for a file.
|
209
|
+
def rails_asset_id(path)
|
210
|
+
asset_id = ENV["RAILS_ASSET_ID"]
|
211
|
+
return asset_id if asset_id
|
212
|
+
File.exists?(path) ? File.mtime(path).to_i.to_s : ''
|
213
|
+
end
|
214
|
+
|
215
|
+
# An asset is valid for embedding if it exists, is less than 32K, and is
|
216
|
+
# stored somewhere inside of a folder named "embed". IE does not support
|
217
|
+
# Data-URIs larger than 32K, and you probably shouldn't be embedding assets
|
218
|
+
# that large in any case. Because we need to check the base64 length here,
|
219
|
+
# save it so that we don't have to compute it again later.
|
220
|
+
def embeddable?(asset_path, variant)
|
221
|
+
font = EMBED_FONTS.include?(asset_path.extname)
|
222
|
+
return false unless variant
|
223
|
+
return false unless asset_path.to_s.match(EMBEDDABLE) && asset_path.exist?
|
224
|
+
return false unless EMBED_EXTS.include?(asset_path.extname)
|
225
|
+
return false unless font || encoded_contents(asset_path).length < MAX_IMAGE_SIZE
|
226
|
+
return false if font && variant == :mhtml
|
227
|
+
return true
|
228
|
+
end
|
229
|
+
|
230
|
+
# Return the Base64-encoded contents of an asset on a single line.
|
231
|
+
def encoded_contents(asset_path)
|
232
|
+
return @asset_contents[asset_path] if @asset_contents[asset_path]
|
233
|
+
data = read_binary_file(asset_path)
|
234
|
+
@asset_contents[asset_path] = Base64.encode64(data).gsub(/\n/, '')
|
235
|
+
end
|
236
|
+
|
237
|
+
# Grab the mime-type of an asset, by filename.
|
238
|
+
def mime_type(asset_path)
|
239
|
+
EMBED_MIME_TYPES[File.extname(asset_path)]
|
240
|
+
end
|
241
|
+
|
242
|
+
# Concatenate together a list of asset files.
|
243
|
+
def concatenate(paths)
|
244
|
+
[paths].flatten.map {|p| read_binary_file(p) }.join("\n")
|
245
|
+
end
|
246
|
+
|
247
|
+
# `File.read`, but in "binary" mode.
|
248
|
+
def read_binary_file(path)
|
249
|
+
File.open(path, 'rb:UTF-8') {|f| f.read }
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
end
|
@@ -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,59 @@
|
|
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
|
+
|
52
|
+
# Jammit Rails Integration:
|
53
|
+
if defined?(Rails)
|
54
|
+
require 'jammit/controller'
|
55
|
+
require 'jammit/helper'
|
56
|
+
require 'jammit/railtie'
|
57
|
+
require 'jammit/routes'
|
58
|
+
end
|
59
|
+
|
@@ -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)
|
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,201 @@
|
|
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)]
|
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 = get_file_list(globs.flatten.uniq).uniq.sort
|
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
|
+
puts name
|
169
|
+
end
|
170
|
+
packages
|
171
|
+
end
|
172
|
+
|
173
|
+
# Raise a PackageNotFound exception for missing packages...
|
174
|
+
def not_found(package, extension)
|
175
|
+
raise PackageNotFound, "assets.yml does not contain a \"#{package}\" #{extension.to_s.upcase} package"
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
def get_file_list(globs)
|
180
|
+
paths = []
|
181
|
+
globs.each do |glob|
|
182
|
+
if String === glob
|
183
|
+
paths.concat glob_files(glob)
|
184
|
+
elsif Hash === glob
|
185
|
+
paths = exclude_paths(paths, glob['exclude']) if glob['exclude']
|
186
|
+
#puts exclude_paths(paths, glob['exclude']).to_s
|
187
|
+
end
|
188
|
+
end
|
189
|
+
paths
|
190
|
+
end
|
191
|
+
|
192
|
+
def exclude_paths(paths, globs)
|
193
|
+
globs.each do |glob|
|
194
|
+
excluded_files = glob_files(glob)
|
195
|
+
paths.reject! { |path| excluded_files.include?(path) }
|
196
|
+
end
|
197
|
+
paths
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Rails 3 configuration via Railtie
|
2
|
+
|
3
|
+
if defined?(Rails::Railtie)
|
4
|
+
module Jammit
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
|
7
|
+
initializer :jammit_routes do |app|
|
8
|
+
# Add a Jammit route for the reloader.
|
9
|
+
app.routes_reloader.paths << File.join(File.dirname(__FILE__), "..", "..", "rails", "routes.rb")
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Jammit
|
2
|
+
|
3
|
+
# Rails 2.x routing module. Rails 3.x routes are in rails/routes.rb.
|
4
|
+
module Routes
|
5
|
+
|
6
|
+
# Jammit uses a single route in order to slow down Rails' routing speed
|
7
|
+
# by the absolute minimum. In your config/routes.rb file, call:
|
8
|
+
# Jammit::Routes.draw(map)
|
9
|
+
# Passing in the routing "map" object.
|
10
|
+
def self.draw(map)
|
11
|
+
map.jammit "/#{Jammit.package_path}/:package.:extension", {
|
12
|
+
:controller => 'jammit',
|
13
|
+
:action => 'package',
|
14
|
+
:requirements => {
|
15
|
+
# A hack to allow extension to include "."
|
16
|
+
:extension => /.+/
|
17
|
+
}
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Wraps sass' css compressor to use the same API as the rest of
|
2
|
+
# Jammit's compressors.
|
3
|
+
class Jammit::SassCompressor
|
4
|
+
# Creates a new sass compressor. Jammit::SassCompressor doesn't use
|
5
|
+
# any options, the +options+ parameter is there for API
|
6
|
+
# compatibility.
|
7
|
+
def initialize(options = {})
|
8
|
+
end
|
9
|
+
|
10
|
+
# Compresses +css+ using sass' CSS parser, and returns the
|
11
|
+
# compressed css.
|
12
|
+
def compress(css)
|
13
|
+
root_node = ::Sass::SCSS::CssParser.new(css, 'test.sass').parse
|
14
|
+
root_node.options = {:style => :compressed}
|
15
|
+
root_node.render.strip
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jammit-ebtd
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.6.6
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jeremy Ashkenas
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-09-04 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: cssmin
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.0.2
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.0.2
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: jsmin
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.0.1
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.0.1
|
46
|
+
description: ! ' Jammit-Ebtd is a fork of the Jammit asset packager, modified for
|
47
|
+
ebaytoday.ru
|
48
|
+
|
49
|
+
'
|
50
|
+
email: jeremy@documentcloud.org
|
51
|
+
executables: []
|
52
|
+
extensions: []
|
53
|
+
extra_rdoc_files:
|
54
|
+
- README
|
55
|
+
files:
|
56
|
+
- lib/jammit/uglifier.rb
|
57
|
+
- lib/jammit/routes.rb
|
58
|
+
- lib/jammit/compressor.rb
|
59
|
+
- lib/jammit/jsmin_compressor.rb
|
60
|
+
- lib/jammit/packager.rb
|
61
|
+
- lib/jammit/railtie.rb
|
62
|
+
- lib/jammit/dependencies.rb
|
63
|
+
- lib/jammit/command_line.rb
|
64
|
+
- lib/jammit/jst.js
|
65
|
+
- lib/jammit/controller.rb
|
66
|
+
- lib/jammit/sass_compressor.rb
|
67
|
+
- lib/jammit/cssmin_compressor.rb
|
68
|
+
- lib/jammit/helper.rb
|
69
|
+
- lib/jammit-ebtd.rb
|
70
|
+
- bin/jammit
|
71
|
+
- jammit-ebtd.gemspec
|
72
|
+
- LICENSE
|
73
|
+
- README
|
74
|
+
homepage: https://github.com/m16a1/jammit-ebtd
|
75
|
+
licenses: []
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options:
|
78
|
+
- --title
|
79
|
+
- Jammit
|
80
|
+
- --exclude
|
81
|
+
- test
|
82
|
+
- --main
|
83
|
+
- README
|
84
|
+
- --all
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 1.8.24
|
102
|
+
signing_key:
|
103
|
+
specification_version: 3
|
104
|
+
summary: Industrial Strength Asset Packaging for Rails
|
105
|
+
test_files: []
|