Changeset 1136
- Timestamp:
- 12/28/07 21:18:32 (1 year ago)
- Files:
-
- trunk/CHANGELOG (modified) (1 diff)
- trunk/app_generators/merb/templates/config/merb.yml (modified) (1 diff)
- trunk/lib/merb.rb (modified) (1 diff)
- trunk/lib/merb/assets.rb (added)
- trunk/lib/merb/mixins/view_context.rb (modified) (8 diffs)
- trunk/spec/merb/assets_spec.rb (added)
- trunk/spec/merb/view_context_spec.rb (modified) (7 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/CHANGELOG
r1107 r1136 1 1 == 0.5.0 "The big cleanup." 2008-01-31 2 * Added asset bundling for Javascript and stylesheet files 2 3 3 4 trunk/app_generators/merb/templates/config/merb.yml
r1087 r1136 32 32 # automatically in production mode. 33 33 #:cache_templates: true 34 35 # Uncomment to bundle assets in dev mode. Assets are automatically bundled in 36 # production mode. 37 #:bundle_assets: true 34 38 35 39 # this is true if you want mongrel to emulate the X-Sendfile header internally, trunk/lib/merb.rb
r1087 r1136 66 66 autoload :Plugins, 'merb/plugins' 67 67 autoload :Rack,'merb/rack_adapter' 68 autoload :Assets, 'merb/assets' 68 69 69 70 # Set up Merb::Server.config[] as an accessor for @@merb_opts trunk/lib/merb/mixins/view_context.rb
r1090 r1136 3 3 # linking to assets and other pages, dealing with JavaScript, and caching. 4 4 module ViewContextMixin 5 6 include Merb::Assets::AssetHelpers 7 5 8 # :section: Accessing Assets 6 9 # Merb provides views with convenience methods for links images and other assets. … … 163 166 # See each method's documentation for more information. 164 167 168 # :section: Bundling Asset Files 169 # 170 # The key to making a fast web application is to reduce both the amount of 171 # data transfered and the number of client-server interactions. While having 172 # many small, module Javascript or stylesheet files aids in the development 173 # process, your web application will benefit from bundling those assets in 174 # the production environment. 175 # 176 # An asset bundle is a set of asset files which are combined into a single 177 # file. This reduces the number of requests required to render a page, and 178 # can reduce the amount of data transfer required if you're using gzip 179 # encoding. 180 # 181 # Asset bundling is always enabled in production mode, and can be optionally 182 # enabled in all environments by setting the <tt>:bundle_assets</tt> value 183 # in <tt>config/merb.yml</tt> to +true+. 184 # 185 # ==== Examples 186 # 187 # In the development environment, this: 188 # 189 # js_include_tag :prototype, :lowpro, :bundle => true 190 # 191 # will produce two <script> elements. In the production mode, however, the 192 # two files will be concatenated in the order given into a single file, 193 # <tt>all.js</tt>, in the <tt>public/javascripts</tt> directory. 194 # 195 # To specify a different bundle name: 196 # 197 # css_include_tag :typography, :whitespace, :bundle => :base 198 # css_include_tag :header, :footer, :bundle => "content" 199 # css_include_tag :lightbox, :images, :bundle => "lb.css" 200 # 201 # (<tt>base.css</tt>, <tt>content.css</tt>, and <tt>lb.css</tt> will all be 202 # created in the <tt>public/stylesheets</tt> directory.) 203 # 204 # == Callbacks 205 # 206 # To use a Javascript or CSS compressor, like JSMin or YUI Compressor: 207 # 208 # Merb::Assets::JavascriptAssetBundler.add_callback do |filename| 209 # system("/usr/local/bin/yui-compress #{filename}") 210 # end 211 # 212 # Merb::Assets::StylesheetAssetBundler.add_callback do |filename| 213 # system("/usr/local/bin/css-min #{filename}") 214 # end 215 # 216 # These blocks will be run after a bundle is created. 217 # 218 # == Bundling Required Assets 219 # 220 # Combining the +require_css+ and +require_js+ helpers with bundling can be 221 # problematic. You may want to separate out the common assets for your 222 # application -- Javascript frameworks, common CSS, etc. -- and bundle those 223 # in a "base" bundle. Then, for each section of your site, bundle the 224 # required assets into a section-specific bundle. 225 # 226 # <b>N.B.: If you bundle an inconsistent set of assets with the same name, 227 # you will have inconsistent results. Be thorough and test often.</b> 228 # 229 # ==== Example 230 # 231 # In your application layout: 232 # 233 # js_include_tag :prototype, :lowpro, :bundle => :base 234 # 235 # In your controller layout: 236 # 237 # require_js :bundle => :posts 238 165 239 # The require_js method can be used to require any JavaScript 166 240 # file anywhere in your templates. Regardless of how many times … … 183 257 end 184 258 185 # The require_c cs method can be used to require any CSS259 # The require_css method can be used to require any CSS 186 260 # file anywhere in your templates. Regardless of how many times 187 261 # a single stylesheet is included with require_css, Merb will only include … … 205 279 # A method used in the layout of an application to create +<script>+ tags to include JavaScripts required in 206 280 # in templates and subtemplates using require_js. 207 # 281 # 282 # ==== Options 283 # bundle:: The name of the bundle the scripts should be combined into. 284 # If +nil+ or +false+, the bundle is not created. If +true+, a 285 # bundle named <tt>all.js</tt> is created. Otherwise, 286 # <tt>:bundle</tt> is treated as an asset name. 287 # 208 288 # ==== Examples 209 289 # # my_action.herb has a call to require_js 'jquery' … … 219 299 # # <script src="/javascripts/validation.js" type="text/javascript"></script> 220 300 # 221 def include_required_js 301 def include_required_js(options = {}) 222 302 return '' if @required_js.nil? 223 js_include_tag(* @required_js)303 js_include_tag(*(@required_js + [options])) 224 304 end 225 305 226 306 # A method used in the layout of an application to create +<link>+ tags for CSS stylesheets required in 227 307 # in templates and subtemplates using require_css. 228 # 308 # 309 # ==== Options 310 # bundle:: The name of the bundle the stylesheets should be combined into. 311 # If +nil+ or +false+, the bundle is not created. If +true+, a 312 # bundle named <tt>all.css</tt> is created. Otherwise, 313 # <tt>:bundle</tt> is treated as an asset name. 314 # 229 315 # ==== Examples 230 316 # # my_action.herb has a call to require_css 'style' … … 239 325 # # <link href="/stylesheets/ie-specific.css" media="all" rel="Stylesheet" type="text/css"/> 240 326 # 241 def include_required_css 327 def include_required_css(options = {}) 242 328 return '' if @required_css.nil? 243 css_include_tag(* @required_css)329 css_include_tag(*(@required_css + [options])) 244 330 end 245 331 … … 247 333 # +<include>+ tag for each script named in the arguments, appending 248 334 # '.js' if it is left out of the call. 249 # 335 # 336 # ==== Options 337 # bundle:: The name of the bundle the scripts should be combined into. 338 # If +nil+ or +false+, the bundle is not created. If +true+, a 339 # bundle named <tt>all.js</tt> is created. Otherwise, 340 # <tt>:bundle</tt> is treated as an asset name. 341 # 250 342 # ==== Examples 251 343 # js_include_tag 'jquery' 252 # # => <script src="/javascripts/jquery.js" type="text/javascript" ></script>344 # # => <script src="/javascripts/jquery.js" type="text/javascript" /> 253 345 # 254 346 # js_include_tag 'moofx.js', 'upload' 255 # # => <script src="/javascripts/moofx.js" type="text/javascript" ></script>256 # # <script src="/javascripts/upload.js" type="text/javascript" ></script>347 # # => <script src="/javascripts/moofx.js" type="text/javascript" /> 348 # # <script src="/javascripts/upload.js" type="text/javascript" /> 257 349 # 258 350 # js_include_tag :effects 259 # # => <script src="/javascripts/effects.js" type="text/javascript" ></script>351 # # => <script src="/javascripts/effects.js" type="text/javascript" /> 260 352 # 261 353 # js_include_tag :jquery, :validation 262 # # => <script src="/javascripts/jquery.js" type="text/javascript" ></script>263 # # <script src="/javascripts/validation.js" type="text/javascript" ></script>354 # # => <script src="/javascripts/jquery.js" type="text/javascript" /> 355 # # <script src="/javascripts/validation.js" type="text/javascript" /> 264 356 # 265 357 def js_include_tag(*scripts) 358 options = scripts.last.is_a?(Hash) ? scripts.pop : {} 266 359 return nil if scripts.empty? 267 include_tag = "" 268 scripts.each do |script| 269 script = script.to_s 270 url = "/javascripts/#{script =~ /\.js$/ ? script : script + '.js'}" 271 url = Merb::Server.config[:path_prefix] + url if Merb::Server.config[:path_prefix] 272 include_tag << %Q|<script src="#{url}" type="text/javascript">//</script>\n| 360 361 if (bundle_name = options[:bundle]) && Merb::Assets.bundle? && scripts.size > 1 362 bundler = Merb::Assets::JavascriptAssetBundler.new(bundle_name, *scripts) 363 bundled_asset = bundler.bundle! 364 return js_include_tag(bundled_asset) 273 365 end 274 include_tag 366 367 tags = "" 368 369 for script in scripts 370 attrs = { 371 :src => asset_path(:javascript, script), 372 :type => "text/javascript" 373 } 374 tags << %Q{<script #{attrs.to_xml_attributes} />} 375 end 376 377 return tags 275 378 end 276 379 … … 278 381 # +<link>+ tag for each stylesheet named in the arguments, appending 279 382 # '.css' if it is left out of the call. 383 # 384 # ==== Options 385 # bundle:: The name of the bundle the stylesheets should be combined into. 386 # If +nil+ or +false+, the bundle is not created. If +true+, a 387 # bundle named <tt>all.css</tt> is created. Otherwise, 388 # <tt>:bundle</tt> is treated as an asset name. 389 # media:: The media attribute for the generated link element. Defaults 390 # to <tt>:all</tt>. 280 391 # 281 392 # ==== Examples 282 393 # css_include_tag 'style' 283 # # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css" />394 # # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css" /> 284 395 # 285 396 # css_include_tag 'style.css', 'layout' 286 # # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css" />287 # # <link href="/stylesheets/layout.css" media="all" rel="Stylesheet" type="text/css" />397 # # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css" /> 398 # # <link href="/stylesheets/layout.css" media="all" rel="Stylesheet" type="text/css" /> 288 399 # 289 400 # css_include_tag :menu 290 # # => <link href="/stylesheets/menu.css" media="all" rel="Stylesheet" type="text/css" />401 # # => <link href="/stylesheets/menu.css" media="all" rel="Stylesheet" type="text/css" /> 291 402 # 292 403 # css_include_tag :style, :screen 293 # # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css"/> 294 # # <link href="/stylesheets/screen.css" media="all" rel="Stylesheet" type="text/css"/> 295 # 296 def css_include_tag(*scripts) 297 return nil if scripts.empty? 298 include_tag = "" 299 scripts.each do |script| 300 script = script.to_s 301 url = "/stylesheets/#{script =~ /\.css$/ ? script : script + '.css'}" 302 url = Merb::Server.config[:path_prefix] + url if Merb::Server.config[:path_prefix] 303 include_tag << %Q|<link href="#{url}" media="all" rel="Stylesheet" type="text/css"/>\n| 404 # # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css" /> 405 # # <link href="/stylesheets/screen.css" media="all" rel="Stylesheet" type="text/css" /> 406 # 407 # css_include_tag :style, :media => :print 408 # # => <link href="/stylesheets/style.css" media="print" rel="Stylesheet" type="text/css" /> 409 def css_include_tag(*stylesheets) 410 options = stylesheets.last.is_a?(Hash) ? stylesheets.pop : {} 411 return nil if stylesheets.empty? 412 413 if (bundle_name = options[:bundle]) && Merb::Assets.bundle? && stylesheets.size > 1 414 bundler = Merb::Assets::StylesheetAssetBundler.new(bundle_name, *stylesheets) 415 bundled_asset = bundler.bundle! 416 return css_include_tag(bundled_asset) 304 417 end 305 include_tag 418 419 tags = "" 420 421 for stylesheet in stylesheets 422 attrs = { 423 :href => asset_path(:stylesheet, stylesheet), 424 :type => "text/css", 425 :rel => "Stylesheet", 426 :media => options[:media] || :all 427 } 428 tags << %Q{<link #{attrs.to_xml_attributes} />} 429 end 430 431 return tags 306 432 end 307 433 trunk/spec/merb/view_context_spec.rb
r1118 r1136 57 57 :media => "all", 58 58 :rel => "Stylesheet", 59 :type => "text/css") 59 :type => "text/css", 60 :content => nil) 60 61 61 62 css_include_tag('foo').should == css_include_tag('foo.css') … … 63 64 css_include_tag('foo') + css_include_tag('bar') 64 65 end 66 67 it "should alter the 'media' attribute based on the provided options" do 68 tag = css_include_tag('foo.css', :media => :screen) 69 tag.should match_tag(:link, :href => "/stylesheets/foo.css", 70 :media => "screen", 71 :rel => "Stylesheet", 72 :type => "text/css", 73 :content => nil) 74 end 65 75 66 76 it "should render a link tag with a path_prefix" do … … 71 81 :media => "all", 72 82 :rel => "Stylesheet", 73 :type => "text/css") 83 :type => "text/css", 84 :content => nil) 74 85 75 86 Merb::Server.config.delete(:path_prefix) … … 87 98 end 88 99 100 describe "View Context", "css tag with bundles" do 101 102 include Merb::ViewContextMixin 103 104 before(:each) do 105 @bundler = mock(:bundler) 106 Merb::Assets.stub!(:bundle?).and_return(true) 107 Merb::Assets::StylesheetAssetBundler.stub!(:new).and_return(@bundler) 108 @bundler.stub!(:bundle!).and_return(:all) 109 end 110 111 it "should not bundle stylesheets if asset bundling is disabled" do 112 Merb::Assets.should_receive(:bundle?).and_return(false) 113 114 tag = css_include_tag(:fonts, :colors, :bundle => true) 115 tag.should == css_include_tag(:fonts) + css_include_tag(:colors) 116 end 117 118 it "should not bundle stylesheets if only a single stylesheet was provided" do 119 css_include_tag(:fonts, :bundle => true).should == css_include_tag(:fonts) 120 end 121 122 it "should bundle all stylesheets as 'all.css' if no bundle name is provided" do 123 Merb::Assets.should_receive(:bundle?).and_return(true) 124 Merb::Assets::StylesheetAssetBundler.should_receive(:new).with(true, :fonts, :colors).and_return(@bundler) 125 @bundler.should_receive(:bundle!).and_return(:all) 126 127 tag = css_include_tag(:fonts, :colors, :bundle => true) 128 tag.should match_tag(:link, :href => "/stylesheets/all.css", 129 :media => "all", 130 :rel => "Stylesheet", 131 :type => "text/css", 132 :content => nil) 133 end 134 135 it "should bundle all stylesheets whatever bundle name is provided" do 136 Merb::Assets.should_receive(:bundle?).and_return(true) 137 Merb::Assets::StylesheetAssetBundler.should_receive(:new).with(:base, :fonts, :colors).and_return(@bundler) 138 @bundler.should_receive(:bundle!).and_return(:base) 139 140 tag = css_include_tag(:fonts, :colors, :bundle => :base) 141 tag.should match_tag(:link, :href => "/stylesheets/base.css", 142 :media => "all", 143 :rel => "Stylesheet", 144 :type => "text/css", 145 :content => nil) 146 end 147 148 it "should not generate a stylesheet tag with the include_required_css" do 149 include_required_css(:bundle => true).clean.should == '' 150 end 151 152 it "should generate stylesheet tags with the include_required_css" do 153 Merb::Assets.should_receive(:bundle?).and_return(true) 154 Merb::Assets::StylesheetAssetBundler.should_receive(:new).with(true, :fonts, :colors).and_return(@bundler) 155 @bundler.should_receive(:bundle!).and_return(:all) 156 157 require_css(:fonts) 158 require_css(:colors) 159 tag = include_required_css(:bundle => true) 160 161 tag.should match_tag(:link, :href => "/stylesheets/all.css", 162 :media => "all", 163 :rel => "Stylesheet", 164 :type => "text/css", 165 :content => nil) 166 end 167 end 168 89 169 describe "View Context", "script tag" do 90 170 … … 95 175 tag.should match_tag(:script, :src => "/javascripts/foo.js", 96 176 :type => "text/javascript", 97 :content => '//')177 :content => nil) 98 178 99 179 js_include_tag('foo').should == js_include_tag('foo.js') … … 108 188 tag.should match_tag(:script, :src => "/inky/javascripts/foo.js", 109 189 :type => "text/javascript", 110 :content => '//')190 :content => nil) 111 191 112 192 Merb::Server.config.delete(:path_prefix) … … 124 204 end 125 205 206 describe "View Context", "script tag with bundles" do 207 208 include Merb::ViewContextMixin 209 210 before(:each) do 211 @bundler = mock(:bundler) 212 Merb::Assets.stub!(:bundle?).and_return(true) 213 Merb::Assets::JavascriptAssetBundler.stub!(:new).and_return(@bundler) 214 @bundler.stub!(:bundle!).and_return(:all) 215 end 216 217 it "should not bundle scripts if asset bundling is disabled" do 218 Merb::Assets.should_receive(:bundle?).and_return(false) 219 220 tag = js_include_tag(:prototype, :lowpro, :bundle => true) 221 tag.should == js_include_tag(:prototype) + js_include_tag(:lowpro) 222 end 223 224 it "should not bundle scripts if only a single script was provided" do 225 css_include_tag(:prototype, :bundle => true).should == css_include_tag(:prototype) 226 end 227 228 it "should bundle all scripts as 'all.js' if no bundle name is provided" do 229 Merb::Assets.should_receive(:bundle?).and_return(true) 230 Merb::Assets::JavascriptAssetBundler.should_receive(:new).with(true, :prototype, :lowpro).and_return(@bundler) 231 @bundler.should_receive(:bundle!).and_return(:all) 232 233 tag = js_include_tag(:prototype, :lowpro, :bundle => true) 234 tag.should match_tag(:script, :src => "/javascripts/all.js", 235 :type => "text/javascript", 236 :content => nil) 237 end 238 239 it "should bundle all stylesheets whatever bundle name is provided" do 240 Merb::Assets.should_receive(:bundle?).and_return(true) 241 Merb::Assets::JavascriptAssetBundler.should_receive(:new).with(:base, :prototype, :lowpro).and_return(@bundler) 242 @bundler.should_receive(:bundle!).and_return(:base) 243 244 tag = js_include_tag(:prototype, :lowpro, :bundle => :base) 245 tag.should match_tag(:script, :src => "/javascripts/base.js", 246 :type => "text/javascript", 247 :content => nil) 248 end 249 250 it "should not generate a script tag with the include_required_js" do 251 include_required_js(:bundle => true).clean.should == '' 252 end 253 254 it "should generate script tags with the include_required_js" do 255 Merb::Assets.should_receive(:bundle?).and_return(true) 256 Merb::Assets::JavascriptAssetBundler.should_receive(:new).with(true, :prototype, :lowpro).and_return(@bundler) 257 @bundler.should_receive(:bundle!).and_return(:all) 258 259 require_js(:prototype) 260 require_js(:lowpro) 261 262 tag = include_required_js(:bundle => true) 263 tag.should match_tag(:script, :src => "/javascripts/all.js", 264 :type => "text/javascript", 265 :content => nil) 266 end 267 end 268 126 269 describe "View Context", "throw_content, catch_content" do 127 270
