dynamic_assets 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|