ee_jammit 0.6.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,10 @@
1
+ # Wraps CSSMin compressor to use the same API as the rest of
2
+ # Jammit's compressors.
3
+ class Jammit::CssminCompressor
4
+ def initialize(options = {})
5
+ end
6
+
7
+ def compress(css)
8
+ CSSMin.minify(css)
9
+ end
10
+ 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)
@@ -0,0 +1,10 @@
1
+ # Wraps JSMin compressor to use the same API as the rest of
2
+ # Jammit's compressors.
3
+ class Jammit::JsminCompressor
4
+ def initialize(options = {})
5
+ end
6
+
7
+ def compress(js)
8
+ JSMin.minify(js)
9
+ end
10
+ end
@@ -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