Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How Sprockets works

How Sprockets works

Almost all applications have assets like CSS, JavaScript and others. That means the asset pipeline is an integral part of the Ruby on Rails framework. In this talk we'll show you how the asset pipeline works, and how you can take full advantage of the asset pipeline's features. Ever wondered how to convert an SVG to PNG automatically? Wanted to know what exactly happens to your CoffeeScript files? We'll explore that, and more.

Rafael França

May 04, 2016
Tweet

More Decks by Rafael França

Other Decks in Programming

Transcript

  1. Spring Turbolinks jquery-ujs Action View Active Record Active Job Action

    Controller Action Mailer web-console Sprockets rails-html-sanitizer
  2. Agenda • Why do we need an assets pipeline? •

    The gems responsible • How it works in a Rails application • How to extend it
  3. Assets names are generated with digest to cache busting •

    public/assets/application- ddbd4593b22ac054471df143715c8ce65ef84938965c7db19d8a322 950ec65b6.js
  4. Sprockets' key components • processors • transformers • compressors •

    directives • environment • manifest • pipelines
  5. Input hash • :data - String asset contents • :environment

    - Current Sprockets::Environment instance. • :cache - A Sprockets::Cache instance. • :uri - String Asset URI. • :source_path - String full path to original file. • :load_path - String current load path for filename. • :name - String logical path for filename. • :content_type - String content type of the output asset. • :metadata - Hash of processor metadata.
  6. Return hash • :data - Replaces the assets input[:data] to

    the next processor in the chain. • :required - A Set of String Asset URIs that the Bundle processor should concatenate together. • :stubbed - A Set of String Asset URIs that will be omitted from the :required set. • :links - A Set of String Asset URIs that should be compiled along with this asset. • :dependencies - A Set of String Cache URIs that should be monitored for caching. • :map - An Array of source maps for the asset. • :charset - The mime charset for an asset.
  7. Bundle processor takes a single file asset and prepends all

    the `:required` URIs to the contents.
  8. module CoffeeScriptProcessor # ... def self.call(input) data = input[:data] js,

    map = input[:cache].fetch([self.cache_key, data]) do result = CoffeeScript.compile(data, sourceMap: true, sourceFiles: [input[:source_path]]) [result['js'], decode_source_maps(result['v3SourceMap'])] end map = SourceMapUtils.combine_source_maps(input[:metadata][:map], map) { data: js, map: map } end end
  9. module CoffeeScriptProcessor # ... def self.call(input) data = input[:data] js,

    map = input[:cache].fetch([self.cache_key, data]) do result = CoffeeScript.compile(data, sourceMap: true, sourceFiles: [input[:source_path]]) [result['js'], decode_source_maps(result['v3SourceMap'])] end map = SourceMapUtils.combine_source_maps(input[:metadata][:map], map) { data: js, map: map } end end
  10. module CoffeeScriptProcessor # ... def self.call(input) data = input[:data] js,

    map = input[:cache].fetch([self.cache_key, data]) do result = CoffeeScript.compile(data, sourceMap: true, sourceFiles: [input[:source_path]]) [result['js'], decode_source_maps(result['v3SourceMap'])] end map = SourceMapUtils.combine_source_maps(input[:metadata][:map], map) { data: js, map: map } end end
  11. LOOSE_APP_ASSETS = lambda do |logical_path, filename| filename.start_with?(::Rails.root.join("app/assets").to_s) && !['.js', '.css',

    ''].include?(File.extname(logical_path)) end config.assets.precompile = [LOOSE_APP_ASSETS, /(?:\/|\\|\A)application\.(css|js)$/]
  12. // app/assets/config/manifest.js //= link_tree ../images //= link_directory ../javascripts .js //=

    link_directory ../stylesheets .css //= link my_engine // my_engine/app/assets/config/my_engine.js //= link_tree ../images/bootstrap
  13. Directives • require • require_directory • require_tree • require_self •

    link • link_directory • link_tree • depend_on • depend_on_asset • stub
  14. Logs the contents of assets compiled to a single directory.

    It records basic attributes about the asset for fast lookup without having to compile. A pointer from each logical path indicates which fingerprinted asset is the current one.
  15. • therubyracer - Google V8 embedded within Ruby • therubyrhino

    - Mozilla Rhino embedded within JRuby • Duktape.rb - Duktape JavaScript interpreter • Node.js • Apple JavaScriptCore - Included with Mac OS X • Microsoft Windows Script Host (JScript) • Google V8
  16. require "execjs" require "open-uri" source = open("http://coffeescript.org/extras/coffee-script.js").read context = ExecJS.compile(source)

    context.call("CoffeeScript.compile", "square = (x) -> x * x", bare: true) # => "var square;\nsquare = function(x) {\n return x * x;\n};"
  17. def self_processors_for(type, file_type) processors = [] processors.concat config[:postprocessors][type] if type

    != file_type && processor = config[:transformers][file_type][type] processors << processor end processors.concat config[:preprocessors][file_type] if processors.any? || mime_type_charset_detecter(type) processors << FileReader end processors end
  18. def self_processors_for(type, file_type) processors = [] processors.concat config[:postprocessors][type] if type

    != file_type && processor = config[:transformers][file_type][type] processors << processor end processors.concat config[:preprocessors][file_type] if processors.any? || mime_type_charset_detecter(type) processors << FileReader end processors end
  19. def self_processors_for(type, file_type) processors = [] processors.concat config[:postprocessors][type] if type

    != file_type && processor = config[:transformers][file_type][type] processors << processor end processors.concat config[:preprocessors][file_type] if processors.any? || mime_type_charset_detecter(type) processors << FileReader end processors end
  20. def self_processors_for(type, file_type) processors = [] processors.concat config[:postprocessors][type] if type

    != file_type && processor = config[:transformers][file_type][type] processors << processor end processors.concat config[:preprocessors][file_type] if processors.any? || mime_type_charset_detecter(type) processors << FileReader end processors end
  21. def self_processors_for(type, file_type) processors = [] processors.concat config[:postprocessors][type] if type

    != file_type && processor = config[:transformers][file_type][type] processors << processor end processors.concat config[:preprocessors][file_type] if processors.any? || mime_type_charset_detecter(type) processors << FileReader end processors end
  22. In production all of this happens in the precompile task

    and only a static asset is returned
  23. class NpmDirectiveProcessor < Sprockets::DirectiveProcessor def process_npm_directive(path) dirs = node_modules_paths(@filename) uri,

    deps = @environment.resolve!( path, accept: @content_type, pipeline: :self, load_paths: dirs ) @dependencies.merge(deps) @required << uri end # ... end
  24. require 'rmagick' class SVGTransformer def self.call(input) image_list = Magick::Image.from_blob(input[:data]) {

    self.format = 'SVG' } image = image_list.first image.format = 'PNG' { data: image.to_blob } end end
  25. require 'rmagick' class SVGTransformer def self.call(input) image_list = Magick::Image.from_blob(input[:data]) {

    self.format = 'SVG' } image = image_list.first image.format = 'PNG' { data: image.to_blob } end end
  26. require 'rmagick' class SVGTransformer def self.call(input) image_list = Magick::Image.from_blob(input[:data]) {

    self.format = 'SVG' } image = image_list.first image.format = 'PNG' { data: image.to_blob } end end
  27. require 'rmagick' class SVGTransformer def self.call(input) image_list = Magick::Image.from_blob(input[:data]) {

    self.format = 'SVG' } image = image_list.first image.format = 'PNG' { data: image.to_blob } end end
  28. We’re hiring! 70+ open positions in a wide range of

    disciplines Ottawa, Montreal, Toronto, Waterloo, Remote https://shopify.com/careers
  29. Testing Rails at Scale Emil Stolarsky May 4, 4:30PM Room

    3501 AB Rails 5 Features You Haven't Heard About Sean Griffin May 5, 11:40AM Room 3501 DC