dynamic_assets 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +225 -0
- data/app/controllers/assets_controller.rb +4 -0
- data/app/helpers/dynamic_assets_helpers.rb +63 -0
- data/config/routes.rb +12 -0
- data/lib/dynamic_assets/config.rb +109 -0
- data/lib/dynamic_assets/controller.rb +40 -0
- data/lib/dynamic_assets/core_extensions.rb +29 -0
- data/lib/dynamic_assets/engine.rb +11 -0
- data/lib/dynamic_assets/manager.rb +10 -0
- data/lib/dynamic_assets/reference/javascript_reference.rb +19 -0
- data/lib/dynamic_assets/reference/stylesheet_reference.rb +84 -0
- data/lib/dynamic_assets/reference.rb +118 -0
- data/lib/dynamic_assets.rb +12 -0
- data/spec/dummy_rails_app/app/controllers/application_controller.rb +3 -0
- data/spec/dummy_rails_app/app/helpers/application_helper.rb +2 -0
- data/spec/dummy_rails_app/config/application.rb +42 -0
- data/spec/dummy_rails_app/config/boot.rb +6 -0
- data/spec/dummy_rails_app/config/environment.rb +5 -0
- data/spec/dummy_rails_app/config/environments/development.rb +26 -0
- data/spec/dummy_rails_app/config/environments/production.rb +49 -0
- data/spec/dummy_rails_app/config/environments/test.rb +35 -0
- data/spec/dummy_rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy_rails_app/config/initializers/inflections.rb +10 -0
- data/spec/dummy_rails_app/config/initializers/mime_types.rb +5 -0
- data/spec/dummy_rails_app/config/initializers/secret_token.rb +7 -0
- data/spec/dummy_rails_app/config/initializers/session_store.rb +8 -0
- data/spec/dummy_rails_app/config/routes.rb +58 -0
- data/spec/dummy_rails_app/db/seeds.rb +7 -0
- data/spec/dummy_rails_app/spec/spec_helper.rb +27 -0
- data/spec/dummy_rails_app/test/performance/browsing_test.rb +9 -0
- data/spec/dummy_rails_app/test/test_helper.rb +13 -0
- data/spec/helpers/dynamic_assets_helpers_spec.rb +69 -0
- data/spec/lib/dynamic_assets/config_spec.rb +148 -0
- data/spec/lib/dynamic_assets/manager_spec.rb +9 -0
- data/spec/lib/dynamic_assets/stylesheet_reference_spec.rb +58 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/matchers/string_matchers.rb +61 -0
- metadata +219 -0
data/README.rdoc
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
= dynamic_assets
|
|
2
|
+
|
|
3
|
+
DynamicAssets allows a Rails 3.0 app to serve its JavaScript and CSS assets
|
|
4
|
+
dynamically instead of statically, which makes all kinds of things possible.
|
|
5
|
+
Out of the box it can (optionally):
|
|
6
|
+
|
|
7
|
+
* Combine all CSS files into one for faster downloading.
|
|
8
|
+
* Combine all JavaScript files into one.
|
|
9
|
+
* Minify assets to make them smaller.
|
|
10
|
+
* Run your CSS or JS assets through ERB, like views.
|
|
11
|
+
* Run your CSS assets through a {Sass}[http://sass-lang.com/] pre-processor (sass or scss).
|
|
12
|
+
* Run them through ERB then Sass, what the heck. (Actually, this can be useful, like to allow
|
|
13
|
+
your app to set some Sass variables.)
|
|
14
|
+
* Eliminate the need for deploy-time rake tasks that combine and minify your assets.
|
|
15
|
+
* Combine, minify, and pre-process in memory instead of on disk, to accommodate read-only
|
|
16
|
+
filesystems (e.g. Heroku).
|
|
17
|
+
* Cache the result using Rails action caching, and set the headers for far-future expiration,
|
|
18
|
+
allowing browsers and front-end caches like Varnish to hold assets for a long time.
|
|
19
|
+
* Allow assets to be grouped, much like
|
|
20
|
+
{Scott Becker's venerable asset_packager}[http://synthesis.sbecker.net/pages/asset_packager].
|
|
21
|
+
(Example: You may want a set of stylesheets for your main interface, and another set for your admin
|
|
22
|
+
interface, maybe with some overlap. With DynamicAssets, your normal users won't pay the
|
|
23
|
+
penalty of downloading your admin styles.)
|
|
24
|
+
* Allow CSS assets to refer to static images through relative URLs. That is, it doesn't break CSS urls.
|
|
25
|
+
* Honor Rails' scheme for asset hosts.
|
|
26
|
+
* Honor Rails' scheme for cache-busting.
|
|
27
|
+
|
|
28
|
+
It seems that Rails 3.1 will offer many of these features off-the-shelf, which is
|
|
29
|
+
cool. DynamicAssets allows you to get some of those features today in 3.0, but it
|
|
30
|
+
wasn't intended as a back port or a stopgap. It just happens that serving assets
|
|
31
|
+
dynamically is useful enough that multiple people have thought of implementing it.
|
|
32
|
+
(See also: {Shoebox}[https://github.com/ddollar/shoebox])
|
|
33
|
+
|
|
34
|
+
== How To
|
|
35
|
+
|
|
36
|
+
1. Add this to your Gemfile:
|
|
37
|
+
|
|
38
|
+
gem "dynamic_assets"
|
|
39
|
+
|
|
40
|
+
2. Put your CSS files in <tt>app/views/stylesheets</tt> and your JS files in
|
|
41
|
+
<tt>app/views/javascripts</tt>. Each filename's extension triggers an
|
|
42
|
+
optional pre-processor:
|
|
43
|
+
|
|
44
|
+
.css = raw
|
|
45
|
+
.js = raw
|
|
46
|
+
.css.erb = process with ERB
|
|
47
|
+
.js.erb = process with ERB
|
|
48
|
+
.css.sass = process with Sass and assume sass syntax
|
|
49
|
+
.css.scss = process with Sass and assume scss syntax
|
|
50
|
+
.css.sass.erb = process with ERB then Sass (sass)
|
|
51
|
+
.css.scss.erb = process with ERB then Sass (scss)
|
|
52
|
+
|
|
53
|
+
(Note that each file can be processed differently. You could stick one toe
|
|
54
|
+
into the Sass world by renaming one of your .css files to .scss.)
|
|
55
|
+
|
|
56
|
+
3. Create a config/assets.yml file that looks something like this:
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
config:
|
|
61
|
+
production, staging, test:
|
|
62
|
+
combine_asset_groups: true
|
|
63
|
+
minify: true
|
|
64
|
+
cache: true
|
|
65
|
+
development:
|
|
66
|
+
combine_asset_groups: true
|
|
67
|
+
minify: false
|
|
68
|
+
cache: false
|
|
69
|
+
|
|
70
|
+
javascripts:
|
|
71
|
+
- base:
|
|
72
|
+
- foo
|
|
73
|
+
- bar
|
|
74
|
+
- baz
|
|
75
|
+
- third-party/widget
|
|
76
|
+
- admin
|
|
77
|
+
- foo
|
|
78
|
+
- qux
|
|
79
|
+
- quux
|
|
80
|
+
|
|
81
|
+
stylesheets:
|
|
82
|
+
- app:
|
|
83
|
+
- reset
|
|
84
|
+
- application
|
|
85
|
+
- admin
|
|
86
|
+
- reset
|
|
87
|
+
- application
|
|
88
|
+
- admin
|
|
89
|
+
|
|
90
|
+
The assets.yml file sets some config values and then lists your assets. Don't be shy
|
|
91
|
+
about listing your assets; it's a good way to get noticed. This sample config
|
|
92
|
+
file says that in production, foo.js, bar.js, baz.js, and widget.js (which is
|
|
93
|
+
in app/views/javascripts/third-party) should be combined into one file,
|
|
94
|
+
minified, and served by your app as /assets/javascripts/base.js in such a way
|
|
95
|
+
that it'd be cached. In development, those files would be combined but not minified
|
|
96
|
+
or cached.
|
|
97
|
+
|
|
98
|
+
4. In your layout, replace your usual CSS and JS references with
|
|
99
|
+
|
|
100
|
+
<%= stylesheet_asset_tag :app %>
|
|
101
|
+
|
|
102
|
+
wherever you want your stylesheet tags to appear and
|
|
103
|
+
|
|
104
|
+
<%= javascript_asset_tag :base %>
|
|
105
|
+
|
|
106
|
+
wherever you want your script tags to appear. The symbol argument to each
|
|
107
|
+
helper is the name of the group you defined in assets.yml. The helper will
|
|
108
|
+
produce one tag if the assets are combined, or multiple tags if they're not,
|
|
109
|
+
like the cowboy in <i>Mulholland Dr.</i>
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
== Variables for ERB
|
|
113
|
+
|
|
114
|
+
By default, assets are served by a small controller whose routes are added to
|
|
115
|
+
your app automatically when the gem is loaded, but you can easily create your
|
|
116
|
+
own controller if you prefer. One reason to do this would be to inject variables
|
|
117
|
+
into an asset via ERB, like this:
|
|
118
|
+
|
|
119
|
+
class AssetsController < ApplicationController
|
|
120
|
+
include DynamicAssets::Controller
|
|
121
|
+
|
|
122
|
+
def show_stylesheet
|
|
123
|
+
@background_color = '#FFE'
|
|
124
|
+
render_asset :stylesheets, params[:name], "text/css"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
Now in app/views/stylesheets/application.css.erb you could do this:
|
|
129
|
+
|
|
130
|
+
body {
|
|
131
|
+
background-color: <%= @background_color %>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
== Static Images Embedded in CSS
|
|
136
|
+
|
|
137
|
+
Sometimes you'll install a JavaScript plugin that was created by someone
|
|
138
|
+
else, and it will come with a stylesheet and images. The stylesheet may
|
|
139
|
+
reference an image like this:
|
|
140
|
+
|
|
141
|
+
div.thing {
|
|
142
|
+
background: url(fancy_background.png);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
Browsers will find the image by looking in the same URL path as the
|
|
146
|
+
stylesheet, so in a typical Rails environment, you might add these two
|
|
147
|
+
files to your app as public/stylesheets/thing.css and
|
|
148
|
+
public/stylesheets/fancy_background.png.
|
|
149
|
+
|
|
150
|
+
With DynamicAssets, you'll put them here instead:
|
|
151
|
+
|
|
152
|
+
app/views/stylesheets/thing.css
|
|
153
|
+
public/stylesheets/thing/fancy_background.png
|
|
154
|
+
|
|
155
|
+
and the processor will make sure the embedded URL is turned into a fully
|
|
156
|
+
qualified one that will allow the browser to find
|
|
157
|
+
/stylesheets/thing/fancy_background.png.
|
|
158
|
+
|
|
159
|
+
== CSS Media Types
|
|
160
|
+
|
|
161
|
+
If you have different CSS files for printing than for the screen, create
|
|
162
|
+
separate groups in your assets.yml. Then include both groups in your layout:
|
|
163
|
+
|
|
164
|
+
<%= stylesheet_asset_tag :app %>
|
|
165
|
+
<%= stylesheet_asset_tag :app_printing, :media => "print" %>
|
|
166
|
+
|
|
167
|
+
== Performance
|
|
168
|
+
|
|
169
|
+
In production, assets can typically be cached aggressively. Like Rails,
|
|
170
|
+
dynamic_assets adds a URL parameter to the asset path that will change
|
|
171
|
+
if any of the underlying assets is modified, forcing clients to reload
|
|
172
|
+
the asset. In production, I find dynamic assets to be quite speedy.
|
|
173
|
+
|
|
174
|
+
But dynamic assets can sometimes be annoying during development. The
|
|
175
|
+
sweet spot for my dev environment is to combine assets but not to
|
|
176
|
+
minify or cache them (see assets.yml above). Here's why:
|
|
177
|
+
|
|
178
|
+
=== Usually set assets not to be minified in development
|
|
179
|
+
|
|
180
|
+
I usually leave minification off in development, because it does add some
|
|
181
|
+
overhead to each asset request, and it makes them difficult to read if you
|
|
182
|
+
need to debug them (like with Firebug). In production, caching minimizes
|
|
183
|
+
the overhead, but you typically won't cache your assets in development,
|
|
184
|
+
unless you're some sort of nut.
|
|
185
|
+
|
|
186
|
+
=== Set assets to be combined, even in development and especially in test
|
|
187
|
+
|
|
188
|
+
By default Rails reloads all classes with each new request in development
|
|
189
|
+
mode. If you're not combining all of your assets, a single page load
|
|
190
|
+
will result in an additional request to your app for each asset, which
|
|
191
|
+
may result in a dozen requests to your dev server for one page, and each
|
|
192
|
+
one will reload all of your classes. Combining assets in dev reduces the
|
|
193
|
+
number of requests, shrinking your page load time. And unlike minification,
|
|
194
|
+
using combined assets in dev is usually not a problem because the concatenated
|
|
195
|
+
files are still quite readable. The one exception is that it makes it more
|
|
196
|
+
difficult to find out which asset file includes the problem you're hunting
|
|
197
|
+
down.
|
|
198
|
+
|
|
199
|
+
<em>Note that one advantage of using DynamicAssets</em> instead of a
|
|
200
|
+
deploy-time task is that you can get more exposure to your processed JavaScript.
|
|
201
|
+
Concatenation and minification can sometimes uncover bugs in your scripts.
|
|
202
|
+
(Example: a forgotten semicolon may be forgiven by a browser if it's at the
|
|
203
|
+
end of a script file but it may cause problems if it's immediately followed by
|
|
204
|
+
more code, tacked on from another file.)
|
|
205
|
+
|
|
206
|
+
Call me silly, but I prefer to find out about that kind of error before I
|
|
207
|
+
deploy my app, and with DynamicAssets I can easily set my test environment to
|
|
208
|
+
combine and minify, so any full-stack testing will expose the problem.
|
|
209
|
+
|
|
210
|
+
=== Or, to eliminate the biggest bottleneck, turn off class caching in development
|
|
211
|
+
|
|
212
|
+
If you don't mind to restart your server every time you change a
|
|
213
|
+
bit of Ruby code, you could edit your config/environments/development.rb
|
|
214
|
+
to do this
|
|
215
|
+
|
|
216
|
+
config.cache_classes = true
|
|
217
|
+
|
|
218
|
+
which would eliminate the biggest chunk of overhead in dev, class-reloading
|
|
219
|
+
on every request.
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
== Copyright
|
|
223
|
+
|
|
224
|
+
Copyright (c) 2011 Robert Davis. See MIT-LICENSE for further details.
|
|
225
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
|
|
2
|
+
module DynamicAssetsHelpers
|
|
3
|
+
|
|
4
|
+
def stylesheet_asset_tag(group_key, http_attributes = {})
|
|
5
|
+
DynamicAssets::Manager.asset_references_for_group_key(:stylesheets, group_key).map do |asset_ref|
|
|
6
|
+
path = stylesheet_asset_path asset_ref.name
|
|
7
|
+
path << "?#{asset_ref.mtime.to_i.to_s}" if asset_ref.mtime.present?
|
|
8
|
+
|
|
9
|
+
tag :link, {
|
|
10
|
+
:type => "text/css",
|
|
11
|
+
:rel => "stylesheet",
|
|
12
|
+
:media => "screen",
|
|
13
|
+
:href => asset_url_for_path(path)
|
|
14
|
+
}.merge!(http_attributes)
|
|
15
|
+
|
|
16
|
+
end.join.html_safe
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def javascript_asset_tag(group_key, http_attributes = {})
|
|
20
|
+
DynamicAssets::Manager.asset_references_for_group_key(:javascripts, group_key).map do |asset_ref|
|
|
21
|
+
path = javascript_asset_path asset_ref.name
|
|
22
|
+
path << "?#{asset_ref.mtime.to_i.to_s}" if asset_ref.mtime.present?
|
|
23
|
+
|
|
24
|
+
content_tag :script, "", {
|
|
25
|
+
:type => "text/javascript",
|
|
26
|
+
:src => asset_url_for_path(path)
|
|
27
|
+
}.merge!(http_attributes)
|
|
28
|
+
|
|
29
|
+
end.join.html_safe
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
protected
|
|
34
|
+
|
|
35
|
+
def asset_url_for_path(path)
|
|
36
|
+
raise "expected a path, not a full URL: #{path}" unless path.relative_url?
|
|
37
|
+
path = "/" + path unless path[0,1] == "/"
|
|
38
|
+
host = compute_asset_host path
|
|
39
|
+
|
|
40
|
+
if host
|
|
41
|
+
"#{host}#{path}"
|
|
42
|
+
else
|
|
43
|
+
path
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Extracted from Rails' AssetTagHelper, where it's private
|
|
48
|
+
def compute_asset_host(source)
|
|
49
|
+
if host = config.asset_host
|
|
50
|
+
if host.is_a?(Proc) || host.respond_to?(:call)
|
|
51
|
+
case host.is_a?(Proc) ? host.arity : host.method(:call).arity
|
|
52
|
+
when 2
|
|
53
|
+
request = controller.respond_to?(:request) && controller.request
|
|
54
|
+
host.call(source, request)
|
|
55
|
+
else
|
|
56
|
+
host.call(source)
|
|
57
|
+
end
|
|
58
|
+
else
|
|
59
|
+
(host =~ /%d/) ? host % (source.hash % 4) : host
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
|
|
2
|
+
Rails.application.routes.draw do
|
|
3
|
+
|
|
4
|
+
match '/assets/javascripts/:name.js' => 'assets#show_javascript', :as => :javascript_asset,
|
|
5
|
+
:format => "js", # Important for action-caching non-HTML resources
|
|
6
|
+
:constraints => { :name => /[^ ]+/ } # By default, segments can't have dots. We allow all but space.
|
|
7
|
+
|
|
8
|
+
match '/assets/stylesheets/:name.css' => 'assets#show_stylesheet', :as => :stylesheet_asset,
|
|
9
|
+
:format => "css", # Important for action-caching non-HTML resources
|
|
10
|
+
:constraints => { :name => /[^ ]+/ } # By default, segments can't have dots. We allow all but space.
|
|
11
|
+
|
|
12
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
|
|
3
|
+
module DynamicAssets
|
|
4
|
+
module Config
|
|
5
|
+
|
|
6
|
+
CONFIG_VARS = %w(combine_asset_groups minify cache)
|
|
7
|
+
|
|
8
|
+
def init(yml_path)
|
|
9
|
+
@yml_path = yml_path
|
|
10
|
+
assets_hash.present?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
#
|
|
15
|
+
# Configuration
|
|
16
|
+
#
|
|
17
|
+
|
|
18
|
+
def combine_asset_groups?
|
|
19
|
+
@combine_asset_groups.nil? ? true : @combine_asset_groups
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def minify?
|
|
23
|
+
@minify.nil? ? true : @minify
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def cache?
|
|
27
|
+
@cache.nil? ? true : @cache
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
#
|
|
32
|
+
# Methods
|
|
33
|
+
#
|
|
34
|
+
|
|
35
|
+
def asset_references_for_group_key(type, group_key)
|
|
36
|
+
assets_hash[type].if_present { |gs| gs[:by_group][group_key] }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def asset_reference_for_name(type, name)
|
|
40
|
+
assets_hash[type].if_present { |gs| gs[:by_name][name] }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
protected
|
|
45
|
+
|
|
46
|
+
def yml
|
|
47
|
+
return @yml if @yml
|
|
48
|
+
|
|
49
|
+
if File.exists? @yml_path
|
|
50
|
+
@yml = YAML.load_file @yml_path
|
|
51
|
+
@yml.delete("config").if_present { |c| configure c }
|
|
52
|
+
else
|
|
53
|
+
@yml = {}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
@yml
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def configure(values)
|
|
60
|
+
values.each do |env_string, config_values|
|
|
61
|
+
next unless env_string.split(/ *, */).include? Rails.env
|
|
62
|
+
|
|
63
|
+
config_values.each do |name, value|
|
|
64
|
+
raise "unknown config variable: #{name}" unless CONFIG_VARS.include? name
|
|
65
|
+
instance_variable_set "@#{name}", value
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def assets_hash
|
|
71
|
+
return @assets if @assets
|
|
72
|
+
|
|
73
|
+
assets = {}
|
|
74
|
+
yml.each do |key, values|
|
|
75
|
+
next if key == "config" || key.blank?
|
|
76
|
+
|
|
77
|
+
type = key.to_sym
|
|
78
|
+
groups = values
|
|
79
|
+
|
|
80
|
+
typed_assets = assets[type] = {
|
|
81
|
+
:by_name => {},
|
|
82
|
+
:by_group => {}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
groups.map do |group_hash|
|
|
86
|
+
group_key = group_hash.keys.first
|
|
87
|
+
group_names = group_hash.values.first
|
|
88
|
+
|
|
89
|
+
assets_in_group = if combine_asset_groups?
|
|
90
|
+
# Create a single AssetReference for the group
|
|
91
|
+
name = group_key
|
|
92
|
+
a = typed_assets[:by_name][name] ||=
|
|
93
|
+
Reference.new_for_type(type, :name => name, :member_names => group_names)
|
|
94
|
+
[a]
|
|
95
|
+
else
|
|
96
|
+
# Create an AssetReference for each member of the group
|
|
97
|
+
group_names.map do |name|
|
|
98
|
+
typed_assets[:by_name][name] ||= Reference.new_for_type type, :name => name
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
typed_assets[:by_group][group_key.to_sym] = assets_in_group
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
@assets = assets
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
|
|
2
|
+
module DynamicAssets
|
|
3
|
+
module Controller
|
|
4
|
+
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.caches_action :show_stylesheet, :show_javascript, :expires_in => 60.minutes,
|
|
7
|
+
:if => Proc.new { Manager.cache? }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
#
|
|
12
|
+
# Actions
|
|
13
|
+
#
|
|
14
|
+
|
|
15
|
+
def show_stylesheet
|
|
16
|
+
render_asset :stylesheets, params[:name], "text/css"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def show_javascript
|
|
20
|
+
render_asset :javascripts, params[:name], "text/javascript"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
protected
|
|
25
|
+
|
|
26
|
+
def render_asset(type, name, mime_type)
|
|
27
|
+
asset = Manager.asset_reference_for_name type, name
|
|
28
|
+
raise ActionController::RoutingError.new "No route matches \"#{request.path}\"" unless asset
|
|
29
|
+
|
|
30
|
+
if Manager.cache?
|
|
31
|
+
response.cache_control[:public] = true
|
|
32
|
+
response.cache_control[:max_age] = 365.days
|
|
33
|
+
headers["Expires"] = (Time.now + 365.days).utc.httpdate
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
render :layout => false, :text => asset.content(binding), :content_type => mime_type
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
|
|
2
|
+
class String
|
|
3
|
+
|
|
4
|
+
# Returns true iff the receiver seems to be a relative, not a full, URL.
|
|
5
|
+
# By "relative url" we mean a URL with no host info, although it may
|
|
6
|
+
# begin with a slash.
|
|
7
|
+
def relative_url?
|
|
8
|
+
regexp = /^[^:\/]*:\/\/[^\/]*/
|
|
9
|
+
self[regexp].nil?
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Object
|
|
15
|
+
|
|
16
|
+
def if_present(*value)
|
|
17
|
+
raise ArgumentError, "Specify either a value or a block but not both" if value.empty? != block_given?
|
|
18
|
+
raise ArgumentError, "Too many arguments. Expected one." if value.length > 1
|
|
19
|
+
|
|
20
|
+
if !self.present?
|
|
21
|
+
self
|
|
22
|
+
elsif block_given?
|
|
23
|
+
yield self
|
|
24
|
+
else
|
|
25
|
+
value.first
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
|
|
2
|
+
module DynamicAssets
|
|
3
|
+
|
|
4
|
+
class StylesheetReference < Reference
|
|
5
|
+
|
|
6
|
+
# CSS style sheets can contain relative urls like this:
|
|
7
|
+
#
|
|
8
|
+
# background: url(something.png)
|
|
9
|
+
#
|
|
10
|
+
# The browser will look for the resource in the same location as
|
|
11
|
+
# the CSS file. However, we serve static resources like images
|
|
12
|
+
# from a different location, so the StylesheetReference will prepend
|
|
13
|
+
# RELATIVE_URL_ROOT to each such relative url in a stylesheet.
|
|
14
|
+
|
|
15
|
+
RELATIVE_URL_ROOT = "/stylesheets"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def formats
|
|
19
|
+
%w(sass scss css)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def type
|
|
23
|
+
:stylesheets
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def minify(content_string)
|
|
27
|
+
# From asset_packager. PENDING: replace with github.com/rgrove/cssmin ?
|
|
28
|
+
content_string.gsub(/\s+/, " ").# collapse space
|
|
29
|
+
gsub(/\/\*(.*?)\*\//, ""). # remove comments
|
|
30
|
+
gsub(/\} /, "}\n"). # add line breaks
|
|
31
|
+
gsub(/\n$/, ""). # remove last break
|
|
32
|
+
gsub(/ \{ /, " {"). # trim inside brackets
|
|
33
|
+
gsub(/; \}/, "}") # trim inside brackets
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
protected
|
|
38
|
+
|
|
39
|
+
# Overridden to transform URLs embedded in the CSS
|
|
40
|
+
def read_member(member_name)
|
|
41
|
+
content_string = super
|
|
42
|
+
format = format_for_member_name member_name
|
|
43
|
+
|
|
44
|
+
content_string = case format
|
|
45
|
+
when :css
|
|
46
|
+
content_string
|
|
47
|
+
when :sass,:scss
|
|
48
|
+
location = File.dirname path_for_member_name(member_name)
|
|
49
|
+
Sass::Engine.new(content_string, :syntax => format, :load_paths => [location],
|
|
50
|
+
:cache => false).render
|
|
51
|
+
else raise "unknown format #{format}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# PENDING: we could do something similar to insert the asset host,
|
|
55
|
+
# although we'd need to pass some context (namely the request) down
|
|
56
|
+
# from the controller to compute the asset host in the same way Rails
|
|
57
|
+
# does.
|
|
58
|
+
transform_urls member_name, content_string
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def transform_urls(member_name, content_string)
|
|
63
|
+
|
|
64
|
+
# Prepend url_root to each relative url that doesn't start with a slash.
|
|
65
|
+
#
|
|
66
|
+
# Inside fancy.css, any of these:
|
|
67
|
+
# url(one/thing.png)
|
|
68
|
+
# url('one/thing.png')
|
|
69
|
+
# url( "one/thing.png" )
|
|
70
|
+
# url(../one/thing.png)
|
|
71
|
+
# will become
|
|
72
|
+
# url(/stylesheets/fancy/one/thing.png)
|
|
73
|
+
#
|
|
74
|
+
|
|
75
|
+
content_string.gsub( /url\( *['"]?([^)]*[^'"])['"]? *\)/ ) do |s|
|
|
76
|
+
url = $1
|
|
77
|
+
url.gsub! /^(\.\.|\.)\//, ''
|
|
78
|
+
(url !~ /^\// && url.relative_url?) ? "url(#{RELATIVE_URL_ROOT}/#{member_name}/#{url})" : s
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
end
|