Instructions manual
rack-pagespeed is a Rack middleware that helps reduce the time it takes for your web page/app to load.
How does it do that? It runs the output HTML through filters that apply a series of best practices automatically for you.
Inlines external JavaScripts that are smaller than 2 kilobytes.
inline_javascripts
Inlines external stylesheets that are smaller than 2 kilobytes.
inline_css
Merges neighbouring JavaScripts together.
combine_javascripts
Merges neighbouring stylesheets together.
combine_css
Minifies (compresses) JavaScripts.
minify_javascripts
Inlines small images using data URI.
inline_images
Applies awesome caching techniques to images.
Install via rubygems:
$ gem install rack-pagespeed
Example usage with Sinatra (in a rackup file, a.k.a. config.ru)
require 'rack/pagespeed'
require 'myapp'
use Rack::PageSpeed, :public => "/app/public/dir" do
store :disk => Dir.tmpdir # require 'tmpdir'
inline_javascript :max_size => 4000
inline_css
combine_javascripts
end
run Sinatra::Application
Usage with Rails:
require 'rack/pagespeed' # somewhere
class Application < Rails::Application
config.middleware.use Rack::PageSpeed, :public => Rails.public_path do
store :disk => Dir.tmpdir # require 'tmpdir'
inline_javascript :max_size => 4000
inline_css
combine_javascripts
end
# ...
In a nutshell: invoke the filters you want and pass them parameters. rack-pagespeed will then figure out the best order of execution and run the filters you invoked against the document.
Filters modify the document in some way. The default filters are heavily inspired (or borrowed even, if you will) from Google's modpagespeed, for the Apache web server. I believe though that, as much as this middleware is slower than it, it's useful for at least 2 scenarios: when you don't have control over the web server, or when your web server isn't Apache. I can also come up with customized excuses if you need any.
Rolling your own filter is easy: subclass Rack::PageSpeed::Filter, define a #execute! method
on your class which takes a Nokogiri HTML document as parameter, and you're set. For example:
class RemovesImages < Rack::PageSpeed::Filter
def execute! document
document.css('img').remove if rand(99) > @options[:chance] - 1
end
end
It doesn't matter where you define the class, as long as you do it before invoking it, perhaps in a separate file
which you'll #require or even inside the block where you invoke the filters. Wherever it is, rack-pagespeed
will know where to find it.
So after declaring, you can call it like so:
use Rack::PageSpeed, :public => "/app/public/dir" do
remove_images :chance => 50
end
rack-pagespeed will create a method out of the class name that looks much like what comes out of Rails' #underscore
method. Any options that you pass (do pass a Hash), if any, will be accessible via @options.
There's two statements you can declare in a filter that are of note: priority and requires_store. The former, well, defines
how high priority the filter is. The filter execution order is determined by this value. For example:
class ExampleFilter < Rack::PageSpeed::Filter
priority 5 # will run before filters with priority <= 4
priority '5th' # also works
...
Calling requires_store means your filter will need at some stage to use the storage mechanism provided in the
#store declaration (see next section). Upon calling it, a @store variable will be made available
to the class. Like so:
class ExampleFilter < Rack::PageSpeed::Filter
requires_store
def execute! document
@store['foo'] = 'bar'
end
end
Some filters may require some kind of storage where they can keep the result of their process. For example, the
combine_javascripts filter, in order to make the results of it's process available, it will:
/rack-pagespeed-098f6bcd4621d373cade4e832627b4f6.js.
So now instead of, say, 4 separate HTTP requests (one for each script), the browser will make 1, which rack-pagespeed will intercept and serve the bundle instead.
There are 3 types of storage available by default: disk storage (file system), Memcached, and Redis. You can use them like this:
use Rack::PageSpeed, :public => "/app/public/dir" do
store :disk # defaults to Dir.tmpdir
# or
store :disk => '/path/to/some/dir'
# or
store :memcached # defaults to localhost:11211
# or
store :memcached => 'address:port'
# or
store :redis
# or
store :redis => { :host => 'hostname', :port => 6379 }
Here's a neat trick: if your app can write to it's public dir, if you call store :disk => "app's public dir",
requests for bundled/compressed assets won't even hit the application stack. Meaning, huge performance gains.
You can override the hash generation mechanism by specifying a hash (or unique string) that the combine_javascripts and
combine_css filters can use when finding certain files. For example:
use Rack::PageSpeed, :public => "/app/public/dir" do
combine_javascripts :hash => {
%w(/javascripts/jquery-1.4.4.min.js /javascripts/myapp.js) => "myjavascripts"
}
end
This tells the filter that when it finds 2 scripts nodes referencing those scripts, that it should bundle them together using "myjavascripts" as a hash. The bundled script file would then look like this:
<script src="/rack-pagespeed-myjavascripts.js"><script>
The above is handy when you want bundles to be referenced in a predictable fashion, so you can add the URL safely to a manifest file, for instance.
Make sure you do at least a cursory check to ensure everything's working after you include this middleware in your stack. I'll happily look into issues reported via GitHub or email.
Stuff to bear in mind:
text/html is issued by your application. While that might
concern some, I found them to be pretty cheap on the memory/CPU side, so well worth using even if you're not caching
things properly...
304 Not Modified in subsequent requests
for a document. In this case, rack-pagespeed won't even see, and hence, won't run any filters on it.
rack-pagespeed is made with ♥ by @julio_ody. All code is public domain. This page's design however, is not. Source is, as usual, hosted on GitHub.