Ruby on Rails nous offre de précieux outils par défaut pour améliorer la performance de nos applications web.
L’asset pipeline en fait partie. En suivant quelques conventions, les fichiers JavaScript et CSS sont automatiquement compressés et minifiés en production.
Ce travail est réalisé par la gem sprockets.
Toutefois Sprockets est une application Rack à part entière et est maintenant complètement décorrélée de Ruby on Rails.
Rien ne nous empêche donc de l’utiliser dans un autre framework basé sur Rack comme Sinatra.
Sinatra est le micro framework que nous utilisons pour faire tourner ce site.
Pour de petits besoins applicatifs il est parfaitement adapté.
Pour commencer nous allons charger les gems qui vont bien :
# Gemfile
gem 'sprockets'
gem 'yui-compressor'
yui-compressor
va se charger de compresser le résultat en sortie.
Puis nous éditons notre fichier de rackup :
require 'sprockets'
require "yui/compressor"
map '/assets' do
environment = Sprockets::Environment.new
environment.append_path 'assets/javascripts'
environment.append_path 'assets/stylesheets'
environment.js_compressor = YUI::JavaScriptCompressor.new
environment.css_compressor = YUI::CssCompressor.new
run environment
end
Ensuite il reste à créer une arborescence assets
avec les sous-dossiers
javascripts
et stylesheets
et d’y placer les manifest et les fichiers.
On pourra également créer un sous dossier vendor
.
Un exemple de manifest:
//= require vendor/foo
//= require bar
C’est tout ce dont vous aurez besoin pour que vos assets soient servis compressés et minifiés.
Le problème est que cette compilation va se faire à chaud (à l’éxécution), ce qui n’est pas particulièrement performant et donc pas acceptable en production.
L’idéal serait que, comme en Rails, un fichier soit compilé au déploiement et automatiquement servi.
Commencons par définir sprockets dans le fichier de notre application,
pour nous synbioz.rb
:
APP_DIR = Dir.pwd
set :sprockets, (Sprockets::Environment.new(APP_DIR) { |env| env.logger = Logger.new(STDOUT) })
set :assets_prefix, 'compiled'
set :assets_path, File.join(APP_DIR, 'public', settings.assets_prefix)
configure do
settings.sprockets.append_path 'assets/javascripts'
settings.sprockets.append_path 'assets/stylesheets'
settings.sprockets.js_compressor = YUI::JavaScriptCompressor.new
settings.sprockets.css_compressor = YUI::CssCompressor.new
end
Nous allons maintenant adapter le bloc /assets
de notre rackup, qui
sera utilisé uniquement en mode development.
En effet les assets ne seront plus servis en invoquant directement sprockets en production.
map '/assets' do
run settings.sprockets
end
Dorénavant il nous faut une tâche pour compiler nos manifest. Créons cette tâche dans le Rakefile :
namespace :assets do
desc 'compile assets'
task :compile => [:assets_version, :compile_js, :compile_css]
desc 'compile javascript assets'
task :compile_js do
compile('js')
end
desc 'compile css assets'
task :compile_css do
compile('css')
end
desc 'create assets version'
task :assets_version do
set :assets_version, Time.now.to_i
File.open(File.join('public', 'ASSETS_VERSION'), 'w+') { |f| f.write(settings.assets_version) }
end
private
def compile(filetype)
sprockets = settings.sprockets
asset = sprockets["application.#{filetype}"]
outpath = File.join(settings.assets_path, filetype)
outfile = Pathname.new(outpath).join("application.#{settings.assets_version}.#{filetype}")
FileUtils.mkdir_p outfile.dirname
asset.write_to(outfile)
asset.write_to("#{outfile}.gz")
end
end
La tâche de compilation, disponible via rake assets:compile
va compiler
les CSS et les JS respectivement dans public/compiled/css
et public/compiled/js
en créant les dossiers s’ils n’existent pas.
Évidemment ces dossiers ne doivent pas être versionnés.
Nous créons également un fichier contenant la version des assets, sous forme d’un timestamp, le but étant d’éviter d’avoir toujours le même nom de fichier et que les navigateurs servent le fichier de leur cache et non le fichier à jour.
Le pipeline de Rails utilise un mécanisme de digest, ici nous utilisons un simple timestamp.
Les fichiers finaux auront ce type de chemin : public/compiled/js/application.1373894136.js.gz
Il nous reste maintenant à mettre en place des helpers pour que le contenu soit servi directement par sprockets en development et depuis les fichiers minifiés statiques autrement.
Par souci de clarté nos helpers sont encapsulés dans un module.
module AssetHelper
def digest
@digest ||= File.read(File.join("public", "ASSETS_VERSION"))
end
def js_tag
if ENV['RACK_ENV'] == 'development'
'<script src="/assets/application.js" type="text/javascript"></script>'
else
"<script src=\"/compiled/js/application.#{digest}.js\" type=\"text/javascript\"></script>"
end
end
def css_tag
if ENV['RACK_ENV'] == 'development'
'<link rel="stylesheet" href="/assets/application.css" type="text/css" media="all" />'
else
"<link rel=\"stylesheet\" href=\"/compiled/css/application.#{digest}.css\" type=\"text/css\" media=\"all\" />"
end
end
end
helpers AssetHelper
Un point important à prendre en compte, notamment pour vos CSS : faites attention à vos chemins.
Étant donné que public/compiled/css
possède plusieurs niveau de profondeur, les chemins
relatifs risquent d’être complexes à maintenir entre dev et production.
Mes images étant servies depuis /public
, un chemin absolu me convient bien, autrement
un helper, type asset_path
en Rails, peut être pratique.
Dernière chose à ne pas oublier, la compilation des assets au déploiement :
run "cd #{release_path}; RACK_ENV=#{rack_env} bin/rake assets:compile"
Et voilà, vous n’avez plus d’excuses pour faire 15 requêtes HTTP quand 2 sont possibles depuis votre site en Sinatra.
L’équipe Synbioz.
Libres d’être ensemble.