class Sprockets::DirectiveProcessor
env.register_processor(‘text/css’, MyProcessor)
Then inject your own preprocessor:
env.unregister_processor(‘application/javascript’, Sprockets::DirectiveProcessor)
env.unregister_processor(‘text/css’, Sprockets::DirectiveProcessor)
To remove the processor entirely:
‘Environment#processors` includes `DirectiveProcessor` by default.
directive syntax.
you’d like. You could add your own custom directives or invent your own
This makes it possible to disable or modify the processor to do whatever
*/
*= require “baz”
/* CSS
#= require “bar”
# CoffeeScript
//= require “foo”
// JavaScript
then the directive name, then any arguments.
A directive comment starts with a comment prefix, followed by an “=”,
directive comments in a source file.
The ‘DirectiveProcessor` is responsible for parsing and evaluating
def self.call(input)
def self.call(input) instance.call(input) end
def self.instance
def self.instance @instance ||= new( # Deprecated: Default to C and Ruby comment styles comments: ["//", ["/*", "*/"]] + ["#", ["###", "###"]] ) end
def _call(input)
def _call(input) @environment = input[:environment] @uri = input[:uri] @filename = input[:filename] @dirname = File.dirname(@filename) @content_type = input[:content_type] @required = Set.new(input[:metadata][:required]) @stubbed = Set.new(input[:metadata][:stubbed]) @links = Set.new(input[:metadata][:links]) @dependencies = Set.new(input[:metadata][:dependencies]) data, directives = process_source(input[:data]) process_directives(directives) { data: data, required: @required, stubbed: @stubbed, links: @links, dependencies: @dependencies } end
def call(input)
def call(input) dup._call(input) end
def compile_header_pattern(comments)
Directives in comments after the first non-whitespace line
Ruby (#) comments are supported.
of the source file. C style (/* */), JavaScript (//), and
Directives will only be picked up if they are in the header
def compile_header_pattern(comments) re = comments.map { |c| case c when String "(?:#{Regexp.escape(c)}.*\\n?)+" when Array "(?:#{Regexp.escape(c[0])}(?m:.*?)#{Regexp.escape(c[1])})" else raise TypeError, "unknown comment type: #{c.class}" end }.join("|") Regexp.compile("\\A(?:(?m:\\s*)(?:#{re}))+") end
def expand_accept_shorthand(accept)
def expand_accept_shorthand(accept) if accept.nil? nil elsif accept.include?("/") accept elsif accept.start_with?(".") @environment.mime_exts[accept] else @environment.mime_exts[".#{accept}"] end end
def expand_relative_dirname(directive, path)
def expand_relative_dirname(directive, path) if @environment.relative_path?(path) path = File.expand_path(path, @dirname) stat = @environment.stat(path) if stat && stat.directory? path else raise ArgumentError, "#{directive} argument must be a directory" end else # The path must be relative and start with a `./`. raise ArgumentError, "#{directive} argument must be a relative path" end end
def extract_directives(header)
[[1, "require", "foo"], [2, "require", "bar"]]
arguments.
directive name as the second element, followed by any
is an Array with the line number as the first element, the
Returns an Array of directive structures. Each structure
def extract_directives(header) processed_header = "" directives = [] header.lines.each_with_index do |line, index| if directive = line[DIRECTIVE_PATTERN, 1] name, *args = Shellwords.shellwords(directive) if respond_to?("process_#{name}_directive", true) directives << [index + 1, name, *args] # Replace directive line with a clean break line = "\n" end end processed_header << line end return processed_header.chomp, directives end
def initialize(options = {})
def initialize(options = {}) @header_pattern = compile_header_pattern(Array(options[:comments])) end
def link_paths(paths, deps, accept)
def link_paths(paths, deps, accept) resolve_paths(paths, deps, accept: accept) do |uri| @links << load(uri).uri end end
def load(uri)
def load(uri) asset = @environment.load(uri) @dependencies.merge(asset.metadata[:dependencies]) asset end
def process_depend_on_asset_directive(path)
//= depend_on_asset "bar.js"
Unlike `depend_on`, the path must be a requirable asset.
source file.
invalid the asset dependency will invalidate the cache our the
This is used for caching purposes. Any changes that would
it.
Allows you to state a dependency on an asset without including
def process_depend_on_asset_directive(path) load(resolve(path)) end
def process_depend_on_directive(path)
//= depend_on "foo.png"
in contents from another file.
This is useful if you are using ERB and File.read to pull
source file.
the dependency file will invalidate the cache of the
This is used for caching purposes. Any changes made to
including it.
Allows you to state a dependency on a file without
def process_depend_on_directive(path) resolve(path) end
def process_directives(directives)
env.register_processor('text/css', DirectiveProcessor)
env.unregister_processor('text/css', Sprockets::DirectiveProcessor)
Replace the current processor on the environment with your own:
end
end
end
require(filename)
Dir["#{dirname}/#{glob}"].sort.each do |filename|
def process_require_glob_directive
class DirectiveProcessor < Sprockets::DirectiveProcessor
`process_require_glob_directive`.
`Sprockets::DirectiveProcessor`, then add a method called
To implement a custom directive called `require_glob`, subclass
processor.
automatically be available. This makes it easy to extend the
Any directive method matching `process_*_directive` will
Gathers comment directives in the source and processes them.
def process_directives(directives) directives.each do |line_number, name, *args| begin send("process_#{name}_directive", *args) rescue Exception => e e.set_backtrace(["#{@filename}:#{line_number}"] + e.backtrace) raise e end end end
def process_link_directive(path)
/*= link "logo.png" */
current.
bundle. Any linked assets will automatically be compiled along with the
The `path` must be a valid asset and should not already be part of the
Declares a linked dependency on the target asset.
def process_link_directive(path) @links << load(resolve(path)).uri end
def process_link_directory_directive(path = ".", accept = nil)
//= link_directory "./scripts" .js
extension or content type in these cases
Use caution when linking against JS or CSS assets. Include an explicit
//= link_directory "./fonts"
nested directories.
directory. It's similar to `path/*` since it does not follow
`link_directory` links all the files inside a single
def process_link_directory_directive(path = ".", accept = nil) path = expand_relative_dirname(:link_directory, path) accept = expand_accept_shorthand(accept) link_paths(*@environment.stat_directory_with_dependencies(path), accept) end
def process_link_tree_directive(path = ".", accept = nil)
//= link_tree "./styles" .css
extension or content type in these cases
Use caution when linking against JS or CSS assets. Include an explicit
//= link_tree "./images"
Its glob equivalent is `path/**/*`.
`link_tree` links all the nested files in a directory.
def process_link_tree_directive(path = ".", accept = nil) path = expand_relative_dirname(:link_tree, path) accept = expand_accept_shorthand(accept) link_paths(*@environment.stat_sorted_tree_with_dependencies(path), accept) end
def process_require_directive(path)
//= require "./bar"
path:
Relative paths work too. Use a leading `./` to denote a relative
//= require "foo"
assumes you are requiring another ".js".
Extensions are optional. If your source file is ".js", it
//= require "foo.js"
`require` works with files in the environment path:
and ensures its only loaded once before the source file.
It provides a way to declare a dependency on a file in your path
The `require` directive functions similar to Ruby's own `require`.
def process_require_directive(path) @required << resolve(path, accept: @content_type, pipeline: :self) end
def process_require_directory_directive(path = ".")
//= require_directory "./javascripts"
nested directories.
directory. It's similar to `path/*` since it does not follow
`require_directory` requires all the files inside a single
def process_require_directory_directive(path = ".") path = expand_relative_dirname(:require_directory, path) require_paths(*@environment.stat_directory_with_dependencies(path)) end
def process_require_self_directive
*/
*= require_tree .
*= require_self
/*= require "reset"
be defined before other dependencies are loaded.
it's common for the index file to contain global styles that need to
before any subsequent `require` directives. Useful in CSS files, where
`require_self` causes the body of the current file to be inserted
def process_require_self_directive if @required.include?(@uri) raise ArgumentError, "require_self can only be called once per source file" end @required << @uri end
def process_require_tree_directive(path = ".")
//= require_tree "./public"
Its glob equivalent is `path/**/*`.
`require_tree` requires all the nested files in a directory.
def process_require_tree_directive(path = ".") path = expand_relative_dirname(:require_tree, path) require_paths(*@environment.stat_sorted_tree_with_dependencies(path)) end
def process_source(source)
def process_source(source) header = source[@header_pattern, 0] || "" body = $' || source header, directives = extract_directives(header) data = "" data.force_encoding(body.encoding) data << header << "\n" unless header.empty? data << body # Ensure body ends in a new line data << "\n" if data.length > 0 && data[-1] != "\n" return data, directives end
def process_stub_directive(path)
//= stub "jquery"
can't be brought back by any other `require`.
be part of the bundle. Once stubbed, it is blacklisted and
The `path` must be a valid asset and may or may not already
Allows dependency to be excluded from the asset bundle.
def process_stub_directive(path) @stubbed << resolve(path, accept: @content_type, pipeline: :self) end
def require_paths(paths, deps)
def require_paths(paths, deps) resolve_paths(paths, deps, accept: @content_type, pipeline: :self) do |uri| @required << uri end end
def resolve(path, options = {})
def resolve(path, options = {}) # Prevent absolute paths in directives if @environment.absolute_path?(path) raise FileOutsidePaths, "can't require absolute file: #{path}" end uri, deps = @environment.resolve!(path, options.merge(base_path: @dirname)) @dependencies.merge(deps) uri end
def resolve_paths(paths, deps, options = {})
def resolve_paths(paths, deps, options = {}) @dependencies.merge(deps) paths.each do |subpath, stat| next if subpath == @filename || stat.directory? uri, deps = @environment.resolve(subpath, options.merge(compat: false)) @dependencies.merge(deps) yield uri if uri end end