inossidabile/sprockets-preload

View on GitHub
lib/sprockets/preload.rb

Summary

Maintainability
A
25 mins
Test Coverage
require 'json'
require 'sprockets'
require 'sprockets/preload/directive_processor'
require 'sprockets/preload/context'
require 'sprockets/preload/version'
require 'sprockets/preload/errors'
require 'sprockets/preload/engine' if defined?(Rails)

Sprockets::DirectiveProcessor.send :include, Sprockets::Preload::DirectiveProcessor
Sprockets::Context.send :include, Sprockets::Preload::Context



module Sprockets
  module Preload
    class <<self
      attr_accessor :inline
      attr_accessor :environment
      attr_accessor :precompiles
    end

    def self.setup_sprockets(receiver)
      receiver.append_path File.expand_path('../../../assets', __FILE__)

      # Pass in current environment condition to mark if loading should be stubbed
      receiver.register_postprocessor 'application/javascript', :preload do |context, data|
        if context._assets_to_preload
          data << "SprocketsPreload.inline = true;" unless context.preload?
        end

        data
      end
    end

    #
    # Forks circular calls protection to allow cross-tree assets interactions
    #
    def self.[](path)
      calls = Thread.current[:sprockets_circular_calls]
      Thread.current[:sprockets_circular_calls] = Set.new
      environment[path]
    ensure
      Thread.current[:sprockets_circular_calls] = calls
    end

    #
    # Iterates through all JS base-level assets of an application
    #
    def self.each_logical_path(&block)
      paths = environment.each_logical_path(*precompiles).to_a +
        precompiles.flatten.select{ |fn| Pathname.new(fn).absolute? if fn.is_a?(String) }

      paths.each do |path|
        yield(path, environment) if environment.content_type_of(path) == 'application/javascript'
      end
    end

    #
    # Recursively collects #= preload directives from given logical path
    #
    def self.collect(environment, logical_path)
      pathname = environment.resolve logical_path
      context  = environment.context_class.new(environment, logical_path, pathname)
      template = Sprockets::DirectiveProcessor.new(pathname.to_s)
      template.render(context, {})

      to_preload = context._assets_to_preload || []

      # Files marked for preloading should not have nested preload directives
      to_preload.each do |dependency|
        nesteds = collect(environment, dependency)
        if nesteds.length > 0
          raise CircularPreloadError.new("Circular preloading detected: #{dependency} -> #{nesteds.join(',')}")
        end
      end

      # Going deeper
      dependencies = context._required_paths.select do |dependency|
        dependency != pathname.to_s
      end

      dependencies.map! do |dependency|
        environment.attributes_for(dependency).logical_path
      end

      dependencies -= context._stubbed_assets.to_a + [pathname.to_s]

      dependencies.each do |dependency|
        to_preload += collect(environment, dependency)
      end

      to_preload
    end
  end
end

Sprockets::Preload.setup_sprockets(Sprockets) if Sprockets.respond_to? :append_path